【问题标题】:How do I write context/specification style unit tests with an MSTest/xUnit framework?如何使用 MSTest/xUnit 框架编写上下文/规范样式的单元测试?
【发布时间】:2011-01-13 10:13:54
【问题描述】:

我一直在使用 MSpec 来编写我的单元测试,并且非常喜欢 BDD 风格,我认为它更具可读性。我现在正在使用 MSpec 不支持的 Silverlight,所以我不得不使用 MSTest,但仍想保持 BDD 风格,所以我正在尝试找出一种方法来做到这一点。

只是为了解释我想要实现的目标,这是我编写 MSpec 测试的方式

[Subject(typeof(Calculator))]    
public class when_I_add_two_numbers : with_calculator
{
  Establish context = () => this.Calculator = new Calculator();
  Because I_add_2_and_4 = () => this.Calculator.Add(2).Add(4);
  It should_display_6 = () => this.Calculator.Result.ShouldEqual(6);
}

public class with_calculator
{
  protected static Calculator;
}

所以使用 MSTest,我会尝试像这样编写测试(虽然你可以看到它不起作用,因为我已经放入了 2 个 TestInitialize 属性,但你得到了我想要做的......)

[TestClass]
public class when_I_add_two_numbers : with_calculator
{
   [TestInitialize]
   public void GivenIHaveACalculator()
   {
      this.Calculator = new Calculator();
   }

   [TestInitialize]
   public void WhenIAdd2And4()
   {
      this.Calculator.Add(2).Add(4);
   }

   [TestMethod]
   public void ThenItShouldDisplay6()
   {
      this.Calculator.Result.ShouldEqual(6);
   }
}

public class with_calculator
{
  protected Calculator Calculator {get;set;}
}

谁能提出一些更优雅的建议来使用 MSTest 以这种方式编写测试?

【问题讨论】:

  • 也许我错过了,但是当您明确想要 MSpec(一个 Spec 系列工具)时,为什么还要使用 MSTest(一个 xUnit 工具)呢?您可以让 MSTest 的行为有点像 MSpec,但它会是一个杂项。例如你将如何从 MSpec 风格的 xUnit 测试中生成可读的规范?
  • 我是 MSpec(和 Machine.Fakes)的忠实粉丝,但是当我问这个问题时,我在一个项目中被告知要使用 MSTest(我自己永远不会选择那个!) .考虑到工具限制,下面答案中概述的这种方法效果很好。你会很高兴听到我从那以后继续前进并再次愉快地进行 MSpecing!
  • 非常高兴!对你有益。我对这种解决方案的不满是,两个阵营的维护者都很难理解这种妥协。
  • 几年后,我强烈建议尝试NSpec(免责声明:我最近为它做出了贡献)

标签: c# unit-testing mstest bdd mspec


【解决方案1】:

你对这个的看法:

[TestClass]
public class when_i_add_two_numbers : with_calculator
{
    public override void When()
    {
        this.calc.Add(2, 4);
    }

    [TestMethod]
    public void ThenItShouldDisplay6()
    {
        Assert.AreEqual(6, this.calc.Result);
    }

    [TestMethod]
    public void ThenTheCalculatorShouldNotBeNull()
    {
        Assert.IsNotNull(this.calc);
    }
}

public abstract class with_calculator : SpecificationContext
{
    protected Calculator calc;

    public override void Given()
    {
        this.calc = new Calculator();
    }
}

public abstract class SpecificationContext
{
    [TestInitialize]
    public void Init()
    {
        this.Given();
        this.When();
    }

    public virtual void Given(){}
    public virtual void When(){}
}

public class Calculator
{
    public int Result { get; private set; }
    public void Add(int p, int p_2)
    {
        this.Result = p + p_2;
    }
}

【讨论】:

  • 如果我们要添加另一个 SpecificationContext with_different_calculator,并且您还想在该上下文中测试 when_i_add_two_numbers,您建议调用该 TestClass 什么?
  • 删除GivenWhen 并只使用with_calculatorwhen_i_add_two_numbers 类中的构造函数怎么样?
【解决方案2】:

Mark Nijhofan example 在他的Fohjin.DDD github repository 中使用 NUnit 进行 Given-When-Then 样式测试。

这是上面引用的示例的摘录:

public class When_registering_an_domain_event : BaseTestFixture<PreProcessor>
{
    /* ... */

    protected override void When()
    {
        SubjectUnderTest.RegisterForPreProcessing<ClientMovedEvent>();
        SubjectUnderTest.Process();
    }

    [Then]
    public void Then_the_event_processors_for_client_moved_event_will_be_registered()
    {
        IEnumerable<EventProcessor> eventProcessors;
        EventProcessorCache.TryGetEventProcessorsFor(typeof(ClientMovedEvent), out eventProcessors);
        eventProcessors.Count().WillBe(1);
    }
}

你可以在base class implementation看到Given:

[Given]
public void Setup()
{
    CaughtException = new NoExceptionWasThrownException();
    Given();

    try
    {
        When();
    }
    catch (Exception exception)
    {
        CaughtException = exception;
    }
    finally
    {
        Finally();
    }
}

【讨论】:

    【解决方案3】:

    我最近经常提出这类问题。有很多合理的选择,您可以轻松创建自己的选项,如本文中的一些答案所示。我一直在研究 BDD 测试框架,其目的是使其轻松扩展到任何单元测试框架。我目前支持 MSTest 和 NUnit。它被称为Given,它是开源的。基本思想非常简单,Given 为通用功能集提供了包装器,然后可以为每个测试运行器实现这些功能。

    以下是 NUnit Given 测试的示例:

    [Story(AsA = "car manufacturer",
           IWant = "a factory that makes the right cars",
           SoThat = "I can make money")]
    public class when_building_a_toyota : Specification
    {
        static CarFactory _factory;
        static Car _car;
    
        given a_car_factory = () =>
                                  {
                                      _factory = new CarFactory();
                                  };
    
        when building_a_toyota = () => _car = _factory.Make(CarType.Toyota);
    
        [then]
        public void it_should_create_a_car()
        {
            _car.ShouldNotBeNull();
        }
    
        [then]
        public void it_should_be_the_right_type_of_car()
        {
            _car.Type.ShouldEqual(CarType.Toyota);
        }
    }
    

    我尽力忠实于 Dan North's Introducting BDD 博客中的概念,因此,一切都是使用给定的、何时的、然后的规范样式完成的。它的实现方式允许您拥有多个给定甚至多个何时,并且它们应该按顺序执行(仍在检查这一点)。

    此外,Given 中直接包含一整套 Should 扩展。这启用了上面看到的ShouldEqual() 调用之类的东西,但是充满了用于集合比较和类型比较等的好方法。对于那些熟悉 MSpec 的人,我基本上将它们撕掉并进行了一些修改以使它们在外部工作规格。

    不过,我认为回报在于报告。测试运行器充满了您创建的场景,因此您可以一目了然地获得有关每个测试实际执行的详细信息,而无需深入研究代码:

    此外,使用 t4 模板根据每个程序集的测试结果创建 HTML 报告。具有匹配故事的类都嵌套在一起,并且打印了每个场景名称以供快速参考。对于上述测试,报告将如下所示:

    失败的测试将显示为红色,并且可以单击以查看异常详细信息。

    差不多就是这样。我在我正在从事的几个项目中使用它,所以它仍在积极开发中,但我认为核心非常稳定。我正在寻找一种通过组合而不是继承来共享上下文的方法,因此这很可能是接下来的变化之一。提出批评。 :)

    【讨论】:

      【解决方案4】:

      您可以使用NUnit.Specifications 并编写如下测试:

      using NUnit.Specifications;
      using Should;
      
      public class OrderSpecs
      {
          [Component]
          public class when_a_customer_places_an_order : ContextSpecification
          {
              static OrderService _orderService;
              static bool _results;
              static Order _order;
      
              Establish context = () =>
              {
                  _orderService = new OrderService();
                  _order = new Order();
              };
      
              Because of = () => _results = _orderService.PlaceOrder(_order);
      
              It should_successfully_place_the_order = () => _results.ShouldBeTrue();
          }
      }
      

      【讨论】:

        【解决方案5】:

        MSTestEnhancer可以帮到你,你可以通过NuGet.org获取包裹。


        这里是示例代码:

        [TestClass]
        public class TheTestedClassTest
        {
            [ContractTestCase]
            public void TheTestedMethod()
            {
                "When Xxx happens, results in Yyy.".Test(() =>
                {
                    // Write test case code here...
                });
        
                "When Zzz happens, results in Www.".Test(() =>
                {
                    // Write test case code here...
                });
            }
        }
        

        当你看到你的测试结果时,你会在下面得到这个:

        我写了一篇文章来介绍有关它的更多信息。详情请见Introducing MSTestEnhancer to make unit test result easy to read - walterlv

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-29
          相关资源
          最近更新 更多