2

我是单元测试的新手——我只使用 Testmethods 完成了基本的断言测试(我的最后一个模块,我创建了大约 50 个)。

我目前正在阅读一本关于单元测试的书,书中的众多示例之一让我为每个单独的测试创建一个新类。下面是仅为一个测试用例创建的示例对象之一。我的问题是有必要这样做吗?或者什么时候应该应用这种方法,什么时候不需要?

  public class and_saving_an_invalid_item_type : when_working_with_the_item_type_repository
    {
        private Exception _result;

        protected override void Establish_context()
        {
            base.Establish_context();

            _session.Setup(s => s.Save(null)).Throws(new ArgumentNullException());
        }

        protected override void Because_of()
        {
            try
            {
                _itemTypeRepository.Save(null);
            }
            catch (Exception exception)
            {
                _result = exception;
            }
        }

        [Test]
        public void then_an_argument_null_exception_should_be_raised()
        {
            _result.ShouldBeInstanceOfType(typeof(ArgumentNullException));
        }
    }
4

2 回答 2

3

您是否需要为每个单独的测试创建一个新类?我会说不,你当然不会。我不知道这本书为什么这么说,或者他们是否只是为了帮助说明他们的例子。

为了回答你的问题,我建议为每组测试使用一个类......但它实际上比这更复杂一些,因为你如何定义“组”是不同的,并且取决于你当时在做什么.

以我的经验,一组测试在逻辑上确实像文档一样结构化,它可以包含一组或多组测试,按某些共同方面分组(有时嵌套)在一起。测试面向对象代码的自然分组是按类分组,然后按方法分组。

这是一个例子

  • 1级测试
    • 测试方法 1
      • 方法 1 的主要行为
      • 方法 1 的替代行为
    • 测试方法 2
      • 方法 2 的主要行为
      • 方法 2 的替代行为

不幸的是,在 C# 或 java(或类似语言)中,您只有两个级别的结构可以使用(而不是您真正想要的 3 或 4 个),因此您必须修改一些东西以适应。

这样做的常用方法是使用一个类将一组测试组合在一起,并且不要在方法级别对任何东西进行分组,如下所示:

class TestsForClass1 {
  void Test_method1_primary()
  void Test_method1_alternate()

  void Test_method2_primary()
  void Test_method2_alternate()
}

如果您的方法 1 和方法 2 都具有相同的设置/拆卸,那么这很好,但有时它们没有,导致这种故障:

class TestsForClass1_method1 {
  void Test_primary()
  void Test_alternate()
}

class TestsForClass1_method2 {
  void Test_primary()
  void Test_alternate()
}

如果您有更复杂的需求(假设您对 method_1 进行了 10 个测试,前 5 个具有设置要求 X,接下来的 5 个具有不同的设置要求),那么人们通常最终会制作越来越多的类名,如下所示:

class TestsForClass1_method1_withRequirementX {  ... }
class TestsForClass1_method1_withRequirementY {  ... }

这很糟糕,但是嘿 - 方钉,圆孔等。

就个人而言,我喜欢在方法中使用 lambda 函数来为您提供第三级分组。NSpec展示了一种可以做到这一点的方法......我们有一个稍微不同的内部测试框架,它看起来有点像这样:

class TestsForClass1 {
   void TestsForMethod1() {
      It.Should("perform it's primary function", () => {
         // ....
      });

      It.Should("perform it's alternate function", () => {
         // ....
      });
   }
}

这有一些缺点(如果第一个 It 语句失败,其他语句不会运行),但我认为这种权衡是值得的。)


- 最初的问题是:“真的有必要为我想要执行的每个测试创建一个对象吗?”。根据这个解释,答案是(大部分)是的。

通常,单元测试涉及两个部分的交互

  • 被测对象。通常这是您编写的类或函数的实例
  • 环境。通常这是您传递给函数的任何参数,以及对象可能引用的任何其他依赖项。

为了使单元测试可靠,这两个部分对于每个测试都需要“新鲜”,以确保系统的状态是健全和可靠的。

  • 如果每次测试都没有刷新被测事物,那么一个函数可能会改变对象的内部状态,并导致下一次测试错误地失败

  • 如果每次测试都没有刷新环境,那么一个函数可能会改变环境(例如:在外部数据库中设置一些变量或其他东西),这可能会导致下一次测试错误地失败。

显然有很多情况并非如此 - 例如,您可能有一个纯数学函数,它只将整数作为参数并且不涉及任何外部状态,然后您可能不想费心重新创建对象测试或测试环境......但一般来说,任何面向对象系统中的大多数东西都需要刷新,所以这就是为什么这样做是“标准做法”。

于 2012-06-04T23:27:11.920 回答
2

我不太能效仿你的例子,但理想情况下,任何测试用例都应该能够独立于其他任何东西运行 - 真的独立于其他任何东西。

于 2012-06-04T23:27:19.660 回答