【问题标题】:Writing standards for unit testing编写单元测试标准
【发布时间】:2010-10-05 03:41:48
【问题描述】:

我计划在我的团队中引入一套编写单元测试的标准。但是要包括什么?

这两个帖子(Unit test naming best practicesBest practices for file system dependencies in unit/integration tests)已经让我深思了。

我的标准中应该涵盖的其他领域应该是如何设置测试类以及如何组织它们。例如,如果您有一个名为 OrderLineProcessor 的类,则应该有一个名为 OrderLineProcessorTest 的测试类。如果该类上有一个名为 Process() 的方法,那么应该有一个名为 ProcessTest 的测试(可能更多用于测试不同的状态)。

还有什么要包括的吗?

贵公司有单元测试标准吗?

编辑:我正在使用 Visual Studio Team System 2008,并使用 C#.Net 进行开发

【问题讨论】:

  • @Gerrie 刚刚为这个老问题添加了答案,请查看

标签: c# visual-studio-2008 unit-testing coding-style


【解决方案1】:

看看Michael Feathers on what is a unit test(或者是什么让单元测试成为糟糕的单元测试)

看看“Arrange, Act, Assert”的概念,即测试只做三件事,以固定的顺序:

  • 安排测试所需的任何输入数据和处理类
  • 在测试中执行动作
  • 用一个或多个断言测试结果。是的,它可以有多个断言,只要它们都致力于测试所执行的操作。

查看Behaviour Driven Development 以了解使测试用例与需求保持一致的方法。

此外,我对今天的标准文档的看法是,除非必须,否则不应编写它们 - 已经编写了很多可用资源。链接到他们,而不是重新散列他们的内容。为想要了解更多信息的开发者提供阅读清单。

【讨论】:

    【解决方案2】:

    您可能应该看看“实用单元测试”系列。 This 是 C# 版本,但还有另一个用于 Java。

    关于你的规范,我不会过分。你有一个很好的开始——命名约定非常重要。我们还要求目录结构与原始项目匹配。覆盖范围还需要扩展到边界情况和非法值(检查异常)。这很明显,但是您的规范是为您将来不可避免地与不想测试传递非法值的人的那个人之间的争论写下来的地方。但是不要让规范超过几页,否则没有人会将它用于与上下文相关的任务。

    更新:我不同意 Potato Head 先生关于每个单元测试只有一个断言的观点。这在理论上听起来不错,但在实践中,它会导致大量冗余测试,或者人们在设置和拆卸方面做大量工作,本身应该被测试。

    【讨论】:

    • 我认为一套简单的标准可以很容易地放在一个页面上(没有示例)。也许两个。
    • 当你说你的“多于几页”时,它是 3 页吗? :)
    • 好吧,我的不是一个页面。我的观点是,在两页之后,我会预测合规率会急剧下降。开发人员只是不喜欢被告知如何进行开发,而不是笼统地讲。他们是(或应该是)专业人士。
    【解决方案3】:

    我遵循 TDD 的 BDD 风格。看: http://blog.daveastels.com/files/BDD_Intro.pdf http://dannorth.net/introducing-bdd http://behaviour-driven.org/Introduction

    简而言之,这意味着

    • 测试不被认为是“测试”,而是系统行为的规范(以下称为“规范”)。规范的目的不是验证系统在任何情况下都能正常工作。他们的目的是指定行为并推动系统的设计。

    • 规范方法名称写成完整的英文句子。例如,球的规格可能包括“球是圆形的”和“当球击中地板时它会反弹”。

    • 生产类和规范类之间没有强制的 1:1 关系(为每个生产方法生成测试方法会很疯狂)。相反,系统行为与规范之间存在 1:1 的关系。

    前段时间我写了 TDD 教程(您开始使用提供的测试编写俄罗斯方块游戏),其中显示了这种编写测试的风格作为规范。您可以从http://www.orfjackal.net/tdd-tutorial/tdd-tutorial_2008-09-04.zip 下载它。该教程中仍然缺少有关如何执行 TDD/BDD 的说明,但示例代码已准备好,因此您可以查看测试的组织方式并编写通过测试的代码。

    您会注意到,在本教程中,生产类被命名为 Board、Block、Piece 和 Tetrominoe,它们以俄罗斯方块游戏的概念为中心。但测试类以俄罗斯方块游戏的行为为中心:FallingBlocksTest、RotatingPiecesOfBlocksTest、RotatingTetrominoesTest、FallingPiecesTest、MovingAFallingPieceTest、RotatingAFallingPieceTest 等。

    【讨论】:

      【解决方案4】:
      1. 尽量在每个测试方法中使用尽可能少的断言语句。这可以确保测试的目的得到明确定义。
      2. 我知道这会引起争议,但不要测试编译器 - 花时间测试 Java Bean 访问器和修改器最好花在编写其他测试上。
      3. 尽可能尝试使用TDD,而不是在代码之后编写测试。

      【讨论】:

      • 1 似乎有点苛刻。假设我想测试一个新构建的 Vector3 是否所有组件都初始化为零?你真的会让我为每个组件编写一个单独的测试,还是更喜欢 assert(v.x()==0.0 && v.y()==0.0 && v.z()==0.0) 为每个组件单独断言?
      • 也不,你应该做 assertEquals(new Vector3(0.0, 0.0, 0.0), (vector)) - 传递给构造函数的值可从 x(), y() 获得的测试和 z() 方法应该由微软的某个人来完成......
      • 首先,在微软有什么关系?如果 Vector3 是一个自定义类(即不是语言运行时的一部分),您是说通过创建一个新实例然后访问属性以提取每个部分并验证来测试构造函数?
      • 如果您的属性错误会怎样? “正确”执行此类操作的唯一方法是确保在测试构造函数之前测试您的属性,除非属性是可设置的并且意味着您的测试具有不同的顺序,否则这很难做到。
      • @Scott:我提到微软是因为这是我能找到的唯一 Vector3 实例——我想我想说的是,如果你使用的是别人编写的类,那么你通常不会不考虑测试他们的代码。
      【解决方案5】:

      我发现大多数测试约定可以通过对所有测试使用标准基类来实施。强制测试人员重写方法,使它们都具有相同的名称。

      我还提倡Arrange-Act-Assert (AAA) 测试风格,因为您可以从测试中生成相当有用的文档。由于命名风格,它还迫使您考虑您所期望的行为

      【讨论】:

      • 你有更多关于这个AAA风格的信息吗?有文章链接吗?
      【解决方案6】:

      您可以在标准中添加的另一项是尽量保持您的单元测试规模较小。那是实际的测试方法本身。除非您正在进行完整的集成单元测试,否则通常不需要大型单元测试,比如超过 100 行。如果您有很多设置可以进行一项测试,我会给您这么多。但是,如果你这样做,你应该重构它。

      人们还谈论重构那里的代码,以确保人们意识到单元测试也是代码。所以重构,重构,重构。

      我发现我所看到的使用中最大的问题是人们往往不会认识到您希望保持单元测试轻巧敏捷。毕竟,您不想要一个整体的野兽来进行测试。考虑到这一点,如果您有一种方法要尝试测试,则不应在一个单元测试中测试所有可能的路径。您应该有多个单元测试来说明通过该方法的每条可能路径。

      是的,如果您正确地进行单元测试,您的单元测试代码行数平均应该比您的应用程序多。虽然这听起来像是很多工作,但最终会在不可避免的业务需求变化时为您节省大量时间。

      【讨论】:

        【解决方案7】:

        功能齐全的 IDE 的用户会发现“其中一些”对以特定模式创建测试有非常详细的支持。给定这个类:

        public class MyService {
            public String method1(){
                return "";
            }
        
            public void method2(){
        
            }
        
            public void method3HasAlongName(){
        
            }
        }
        

        当我在 intellij IDEA 中按 ctrl-shift-T 时,我在回答 1 个对话框后得到了这个测试类:

        public class MyServiceTest {
            @Test
            public void testMethod1() {
                // Add your code here
            }
        
            @Test
            public void testMethod2() {
                // Add your code here
            }
        
            @Test
            public void testMethod3HasAlongName() {
                // Add your code here
            }
        }
        

        因此,您可能需要在编写标准之前仔细查看工具支持。

        【讨论】:

          【解决方案8】:

          我的单元测试函数名称几乎使用简单的英语。有助于定义他们的确切工作:

          TEST( TestThatVariableFooDoesNotOverflowWhenCalledRecursively )
          {
          /* do test */
          }  
          

          我使用 C++,但命名约定可以在任何地方使用。

          【讨论】:

            【解决方案9】:

            确保包含不是单元测试的内容。见:What not to test when it comes to Unit Testing?

            包括一个指南,以便清楚地识别集成测试,并且可以与单元测试分开运行。这很重要,因为如果单元测试与其他类型的测试混合使用,您可能会以一组非常慢的“单元”测试结束。

            查看更多信息:How can I improve my junit tests ...特别是第二次更新。

            【讨论】:

              【解决方案10】:

              如果您使用的是 Junit 系列的工具(OCunit、SHunit、...),那么测试的名称已经遵循一些规则。

              对于我的测试,我使用自定义的doxygen 标记以便在特定页面中收集他们的文档。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2014-08-05
                • 2012-02-03
                • 2019-01-28
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多