zoukankan      html  css  js  c++  java
  • 行为驱动开发之道

    用Context/Specification风格编写单元测试,可以使用一种更加自然的方式反映出用户故事、客户需求。BDD更为注重从需求的角度将测试用例分为若干关注点Concerns,在类的层次上把Context, Action和Observations抽象到不同的方法中,从而可以很好的应用Arrange, Act, Assert模式进行单元测试的编写。

    The core principles of BDD are:

    • Expressing behaviour in terms that show the value to the system actors
    • Expressing behaviours / scenarios in a format that clearly separates the context, the action and the observations

    为了阐述方便,先看下面这个TDD风格的测试用例(上篇文章中的测试用例):

    [Test]
    public void TestRetrieveOrders() {
        // arrange
        var mockView = MockRepository.GenerateMock<IOrdersView>();
        var mockRepository = MockRepository.GenerateStub<IRepository<Order>>();
    
        List<Order> defaultOrders = new List<Order> { new Order("moq"), new Order("RhinoMock") };
    
        mockRepository.Stub(ir => ir.FindAll()).Return(defaultOrders);
        var presenter = new OrdersPresenter(mockView, mockRepository);
        // act
        presenter.OnInit();
        // assert
        mockView.AssertWasCalled(v => v.Orders = defaultOrders);
    }

    这是平时很常见的书写测试用例的传统方法,下面我将使用介绍的BDD框架进行重写,以实现Context/Specifications风格的单元测试。将上述测试代码以Context/Specifications方式表示出来,如下所示: 

    Concern: Orders retrieve

    Context: when initializing the presentation

    Observation: should retrieve all the orders

    SpecUnit.Net

    基于.NET单元测试框架NUnit提供了BDD风格的语法。简单来说就是将初始化、执行代码部分抽象出来封装进ContextSpecification,然后在子类具体实现其中的Context, Because方法(Template Method),从而在一个测试类中很好的实现了Context, Action和Observation的分离:

    展开

    现在我们用SpecUnit来实现BDD风格的单元测试:

    [Concern("Orders retrieve")]
    public class when_initializing_the_presentation : ContextSpecification
    {
        protected IOrdersView _view;
        protected IRepository<Order> _repository;
        protected OrdersPresenter _presenter;
        // mocking orders
        protected readonly List<Order> DefaultOrders = new List<Order> { new Order("moq"), new Order("RhinoMock") };
        // arrange
        protected override void Context() {
            _view = MockRepository.GenerateMock<IOrdersView>();
            _repository = MockRepository.GenerateStub<IRepository<Order>>();
            _repository.Stub(ir => ir.FindAll()).Return(DefaultOrders);
            _presenter = new OrdersPresenter(_view, _repository);
        }      
        // act
        protected override void Because() {
            _presenter.OnInit();
        }
        // assert
        [Observation]
        public void should_retrieve_all_the_orders() {
            _view.AssertWasCalled(v => v.Orders = DefaultOrders);
        }
    }

    其中Concern标示出本次测试的关注点,class标示出Context,表示了我们的一个测试用例;Because包含的是我们需要进行测试的行为,是具体的执行逻辑,而用Observation标记出来的方法代表了Specifications,表示我们期望得到的结果;另外,我们可以看到上述例子的类名、方法名都是自解释的,可以很容易明白所需要测试的内容。

    在实际开发过程中会经常碰到多个Context重用相同的行为测试,显然可以通过继承的方式以达到行为重用的目的。 

    此外SpecUnit.Net自带一个工具可以使用它来生成测试报告页面,直观的看到最后的测试结果:SpecUnit.Report.exe <assembly name>

    specunit

    Machine.Specifications(MSpec)

    Aaron Jensen受到SpecUnit与RSpec的灵感启发而开发出来的。MSpec利用委托和匿名方法定义了以下几个委托:

    Establish:

    • 进行Context(Class)里的初始化工作
    • 一个Context(Class)里至多只有一个Establish
    • 运行于Because之前

    Because:

    • 需要测试的行为
    • 一个Context(Class)里只能有一个Because

    It:

    • 测试期望得到的结果
    • 一个Context(Class)里可以有多个It委托
    • 运行在Because之后

    Behaves_like:

    • 封装好的、可重用的行为

    Cleanup:

    • Context(Class)里执行TearDown的工作
    • 运行于It委托之后

    这些委托是我们编写MSpec测试代码时经常需要用到的:

    namespace Machine.Specifications
    {
      public delegate void Establish();
    
      public delegate void Because();
    
      public delegate void It();
      public delegate void Behaves_like<TBehavior>();
    
      public delegate void Cleanup();
    }

    上述测试例子的相应MSpec写法如下:

    [Subject("Orders retrieve")]
    public class when_initializing_the_presentation
    {
        protected static IOrdersView _view;
        protected static IRepository<Order> _repository;
        protected static OrdersPresenter _presenter;
        // mocking orders
        protected static readonly List<Order> DefaultOrders = new List<Order> { new Order("moq"), new Order("RhinoMock") };
        // arrange
        Establish context = () => {
            _view = MockRepository.GenerateMock<IOrdersView>();
            _repository = MockRepository.GenerateStub<IRepository<Order>>();
            _repository.Stub(ir => ir.FindAll()).Return(DefaultOrders);
            _presenter = new OrdersPresenter(_view, _repository);  
        };
        // act
        Because of = ()=> _presenter.OnInit();
        // assert
        It should_retrieve_all_the_orders = () => _view.AssertWasCalled(v => v.Orders = DefaultOrders);
    }
    

    注意到这个例子,由于Context中的匿名方法需要访问_view, _repository等字段,所以其中的所有字段必须都是static。

    为了可以将同样的行为应用到多个Context里,MSpec提供了[Behaviors]以达到重用的目的:

    [Behaviors]
    public class DateTimeParsingBehavior
    {
      protected static DateTime ParsedDate;
    
      It should_parse_the_expected_date = () => ParsedDate.ShouldEqual(new DateTime(2009, 1, 21));
    }

    这样我们就可以在多个Context里使用该Behavior:

    展开

    同样,Machine.Specifications.Console.exe可以生成最后的测试报告页面。

    xUnit BDD Extensions

    随着使用xUnit测试框架的流行,有许多人希望可以基于xUnit构建自己的BDD单元测试用例,其中xUnit.Samples中就有很不错的例子,大家有兴趣可以下载看看,这里就不再详细解释。

    Conclusion:

    在.NET世界里常见的BDD框架还有:NBehave, NSpec等,目的都是以贴近自然语言的方式描述软件系统的行为过程。与TDD相比,BDD更侧重于从客户的角度来表达需求,方便需求方与实现方的交流与沟通。而从开发人员角度看,BDD相比TDD在组织测试代码的结构上有很大不同。

    下面列出SpecUnit与MSpec的主要特点:

      TDD MSpec SpecUnit
    [Category] [Category] [Subject] [Concern]
    Arrange SetUp/TestFixtureSetUp Establish Context
    Act Run code under test Because Because
    Assert [Test] It [Observation]

    References:

    1. machine.specifications - GitHub
    2. specunit-net - Google Code
    3. An Evolution of Test-Specification Styles – My Journey to MSpec
    4. Behaviors with MSpec
    5. Starting with BDD vs. starting with TDD
    6. Test Driven Requirements with MSpec
  • 相关阅读:
    Java代码是怎么运行的
    Java单例模式
    redis分布式锁实现
    zuul2.0
    配置ssh免密钥登陆多台从机
    Nifi-install-config
    Configure Access to Multiple Clusters
    kubernetes集群搭建(kubeadm,kubelet)
    shell 编程
    系统管理
  • 原文地址:https://www.cnblogs.com/huyh/p/1760399.html
Copyright © 2011-2022 走看看