关于不可知的语言:什么是好的单元测试?

关于不可知的语言:什么是好的单元测试?

What Makes a Good Unit Test?

我敢肯定,你们中的大多数人都在编写大量的自动化测试,并且在进行单元测试时也遇到了一些常见的陷阱。

我的问题是,您是否遵循任何编写测试的行为准则,以避免将来出现问题? 更具体而言:好的单元测试的特性是什么?如何编写测试?

鼓励与语言无关的建议。


让我首先插入源代码-使用JUnit用Java进行实用单元测试(也有一个带有C#-Nunit的版本..但是我有这个版本。.在很大程度上与它无关。建议。)

好的测试应该是一次旅行(首字母缩略词不够粘稠-我在书中有一份速查表的打印输出,为了确保我理解正确,我不得不将其抽出。)

  • 自动:测试的通过以及通过/失败的检查结果应该是自动的
  • 彻底:覆盖;尽管错误倾向于聚集在代码中的某些区域,但是请确保测试所有关键路径和方案。如果必须了解未测试的区域,请使用工具
  • 可重复:每次测试应产生相同的结果。测试不应依赖不可控制的参数。
  • 独立:非常重要。

    • 测试一次只能测试一件事。只要它们都在测试一种功能/行为,就可以有多个断言。如果测试失败,则应查明问题的位置。
    • 测试不应相互依赖-隔离。没有关于测试执行顺序的假设。每次测试前,请通过适当使用设置/拆卸来确保"干净"
  • 专业人士:从长远来看,您将拥有与生产一样多的测试代码(如果不是更多的话),因此对于您的测试代码,请遵循相同的良好设计标准。精心分解的方法类-具有意图揭示名称,无重复,具有良好名称的测试等。

  • 好的测试也可以快速运行。任何需要半秒以上才能运行的测试都需要进行。测试套件运行的时间越长..运行频率越低。开发人员将尝试在两次运行之间进行更多的更改..如果有任何中断..将花费更长的时间来找出哪个更改是罪魁祸首。

2010-08更新:

  • 可读:可以认为这是Professional的一部分-但是压力不够大。严峻的考验是找到不属于您团队的人,并要求他/她在几分钟内找出被测行为。测试需要像生产代码一样进行维护-因此即使花费更多的精力也可以使其易于阅读。测试应该对称(遵循模式)并且简洁(一次测试一种行为)。使用一致的命名约定(例如TestDox样式)。避免使"偶然的细节"使测试混乱。

除此之外,其他大多数都是减少低效益工作的准则:例如"不要测试您不拥有的代码"(例如第三方DLL)。不要去测试吸气剂和吸气剂。留意成本效益比或缺陷概率。


  • 不要编写繁琐的测试。正如"单元测试"中的"单元"所暗示的那样,使每个单元尽可能原子且隔离。如果需要,请使用模拟对象创建前提条件,而不是手动重新创建太多的典型用户环境。
  • 不要测试明显起作用的东西。避免测试第三方供应商提供的类,尤其是提供您所编码框架的核心API的类。例如,请勿测试将任何项目添加到供应商的Hashtable类中。
  • 考虑使用诸如NCover之类的代码覆盖工具来帮助发现尚未测试的极端情况。
  • 尝试在实施之前编写测试。将测试视为您的实现将遵循的更多规范。 cf.也是行为驱动的开发,这是测试驱动开发的更具体的分支。
  • 始终如一。如果只为某些代码编写测试,则几乎没有用。如果您在团队中工作,而有些或所有其他人都不编写测试,那么它也不是很有用。让自己和其他所有人相信测试的重要性(以及节省时间的属性),或者不要打扰。

  • 这里的大多数答案似乎都是针对一般的单元测试最佳实践(何时,何地,为什么以及什么),而不是实际编写测试(如何)。由于问题似乎在"如何"部分上非常具体,因此我认为应该从我在公司进行的"棕色袋子"演示文稿中发布。

    Womp的五项写作定律:

    1.使用描述性长的测试方法名称。

    1
    2
    3
       - Map_DefaultConstructorShouldCreateEmptyGisMap()
       - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
       - Dog_Object_Should_Eat_Homework_Object_When_Hungry()

    2.以"编配/执行/声明"样式编写测试。

    • 虽然这种组织策略
      已经存在了一段时间
      叫了很多东西,介绍
      的" AAA"首字母缩写
      是实现这一目标的好方法。
      使所有测试与
      AAA风格使它们易于阅读和
      保持。

    3.始终向您的断言提供失败消息。

    1
    2
    Assert.That(x == 2 && y == 2,"An incorrect number of begin/end element
    processing events was raised by the XElementSerializer");
    • 一种简单而有益的做法,使您的运行者应用程序可以清楚地发现失败了。如果不提供消息,则故障输出中通常会出现" Expected true,was false"之类的信息,这使您必须实际阅读测试以找出问题所在。

    4.评论测试的原因–业务假设是什么?

    1
    2
    3
    4
    5
    6
      /// A layer cannot be constructed with a null gisLayer, as every function
      /// in the Layer class assumes that a valid gisLayer is present.
      [Test]
      public void ShouldNotAllowConstructionWithANullGisLayer()
      {
      }
    • 这看起来似乎很明显,但这
      实践将保护诚信
      来自那些没有的人的测试
      了解测试背后的原因
      首先。我看过很多
      测试被删除或修改
      很好,只是因为
      这个人不明白
      假设测试是
      验证。
    • 如果测试很简单或方法
      名称具有足够的描述性
      可以允许离开
      评论关闭。

    5.每个测试都必须始终还原其接触的任何资源的状态

    • 尽可能使用模拟以避免
      处理实际资源。
    • 清理必须在测试中完成
      水平。测试不得有任何内容
      依赖执行顺序。

    牢记这些目标(改编自Meszaros的xUnit Test Patterns)

    • 测试应该降低风险,而不是
      介绍一下。
    • 测试应该易于运行。
    • 测试应易于维护,因为
      系统围绕着他们发展

    一些使这更容易的事情:

    • 测试应仅因以下原因而失败
      一个理由。
    • 测试只能测试一件事
    • 最小化测试依赖性(否
      对数据库,文件,UI的依赖
      等等。)

    不要忘记,您也可以使用xUnit框架进行集成测试,但要将集成测试和单元测试分开


    测试应隔离。一种测试不应依赖于另一种。更进一步,测试不应依赖外部系统。换句话说,请测试您的代码,而不是代码所依赖的代码。您可以在集成或功能测试中测试这些交互。


    出色的单元测试的一些属性:

    • 如果测试失败,应该立即发现问题所在。如果您必须使用调试器来查找问题,则您的测试不够精细。每个测试只有一个断言可以帮助您。

    • 重构时,任何测试都不应失败。

    • 测试应该运行得如此之快,以至于您可以毫不犹豫地运行它们。

    • 所有测试应始终通过;没有不确定的结果。

    • 单元测试应该像您的生产代码一样精心设计。

    @Alotor:如果您建议一个库仅在其外部API上进行单元测试,那么我不同意。我想要每个类的单元测试,包括我不公开给外部调用者的类。 (但是,如果我觉得需要为私有方法编写测试,则需要重构。)

    编辑:关于"每个测试一个断言"引起的重复的评论。具体来说,如果您有一些代码来设置场景,然后想要对其进行多个声明,但每个测试只有一个声明,则可以在多个测试之间复制设置。

    我不采取这种方法。相反,我针对每种情况使用测试夹具。这是一个粗糙的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    [TestFixture]
    public class StackTests
    {
        [TestFixture]
        public class EmptyTests
        {
            Stack<int> _stack;

            [TestSetup]
            public void TestSetup()
            {
                _stack = new Stack<int>();
            }

            [TestMethod]
            [ExpectedException (typeof(Exception))]
            public void PopFails()
            {
                _stack.Pop();
            }

            [TestMethod]
            public void IsEmpty()
            {
                Assert(_stack.IsEmpty());
            }
        }

        [TestFixture]
        public class PushedOneTests
        {
            Stack<int> _stack;

            [TestSetup]
            public void TestSetup()
            {
                _stack = new Stack<int>();
                _stack.Push(7);
            }

            // Tests for one item on the stack...
        }
    }


    您所追求的是描述被测类的行为。

  • 验证预期行为。
  • 验证错误案例。
  • 该类中所有代码路径的覆盖范围。
  • 行使课程中的所有成员函数。
  • 基本目的是增强您对课堂行为的信心。

    这在查看重构代码时特别有用。 Martin Fowler在他的网站上有一篇有趣的文章,关于测试。

    HTH。

    干杯,


    测试最初应该失败。然后,您应该编写使它们通过的代码,否则,您将冒编写有漏洞且始终通过的测试的风险。


    好的测试需要维护。

    我还没有弄清楚如何在复杂的环境中执行此操作。

    当您的代码库开始覆盖时,所有教科书都开始脱胶
    成千上万或几百万行代码。

    • 团队互动激增
    • 测试用例数量激增
    • 组件之间的相互作用会爆炸。
    • 建立所有单元测试的时间成为构建时间的重要部分
    • API更改可能会影响到数百个测试用例。即使生产代码更改很容易。
    • 将进程排序为正确状态所需的事件数量增加,从而增加了测试执行时间。

    好的架构可以控制一些交互爆炸,但不可避免的是
    系统变得越来越复杂,自动化测试系统也随之发展。

    这是您必须权衡的地方:

    • 仅测试外部API,否则重构内部会导致大量的测试用例返工。
    • 随着封装子系统保留更多状态,每个测试的设置和拆卸都会变得更加复杂。
    • 夜间编译和自动测试执行需要数小时才能完成。
    • 增加的编译和执行时间意味着设计人员不会或不会运行所有测试
    • 为了减少测试执行时间,您考虑对顺序测试进行操作以减少设置和拆卸

    您还需要确定:

    您将测试用例存储在代码库中的什么位置?

    • 您如何记录测试用例?
    • 测试夹具可以重新使用以节省测试用例维护吗?
    • 夜间测试用例执行失败时会发生什么?谁进行分类?
    • 您如何维护模拟对象?如果您有20个模块都使用它们自己的模拟日志记录API风格,则快速更改API会产生波动。测试用例不仅会更改,而且20个模拟对象也会更改。这20个模块是由许多不同的团队在几年内编写的。这是一个经典的重用问题。
    • 个人及其团队了解自动化测试的价值,而他们只是不喜欢其他团队的工作方式。 :-)

    我可以永远继续下去,但是我的意思是:

    测试必须是可维护的。


    我喜欢上述实用单元测试书中的Right BICEP缩写:

    • 正确:结果正确吗?
    • B:所有边界条件都正确吗?
    • I:我们可以检查逆关系吗?
    • C:我们可以使用其他方式对结果进行交叉检查吗?
    • E:我们可以强制发生错误情况吗?
    • 警:性能特征是否在范围之内?

    我个人认为,可以通过检查是否得到正确的结果(在加法函数中1 + 1应该返回2),尝试所有可以想到的边界条件(例如使用两个数字之和来求和),可以走得很远。大于add函数中的整数最大值),并强制出现错误条件,例如网络故障。


    我在《 MSDN杂志》的这篇文章中介绍了这些原则,我认为这对于任何开发人员来说都是重要的。

    我定义"良好"单元测试的方式是,它们是否具有以下三个属性:

    • 它们是可读的(命名,断言,变量,长度,复杂度..)
    • 它们是可维护的(无逻辑,未过度指定,基于状态,已重构..)
    • 他们是值得信赖的(测试正确的东西,孤立的,而不是集成测试..)


    • 单元测试只是测试单元的外部API,而不应该测试内部行为。
    • TestCase的每个测试都应在此API中测试一个(并且只有一个)方法。

      • 失败案例应包括附加测试案例。
    • 测试您的测试范围:测试完一个单元后,应该已经执行了该单元内部100%的行。

    杰伊·菲尔德斯(Jay Fields)在编写单元测试方面有很多很好的建议,并且他在帖子中总结了最重要的建议。您将在此处看到,您应该认真考虑自己的情况,并判断建议是否对您有价值。您可以在此处获得大量令人惊奇的答案,但是由您决定哪个最适合您的情况。尝试一下,如果气味难闻,请进行重构。

    亲切的问候


    我第二次回答" A TRIP",只是测试应该相互依赖!!!

    为什么?

    干-不要重复自己-也适用于测试!测试相关性可以帮助1)节省设置时间,2)节省夹具资源,3)查明故障。当然,仅考虑到您的测试框架支持一流的依赖关系。否则,我承认,它们是不好的。

    跟进http://www.iam.unibe.ch/~scg/Research/JExample/


    永远不要认为琐碎的2行方法会起作用。编写快速的单元测试是防止遗漏的空测试,放负号和/或细微的范围错误咬住您的唯一方法,这是不可避免的,因为与现在相比,处理时间更少了。


    考虑一下这两种测试,并以不同的方式对待它们-功能测试和性能测试。

    分别使用不同的输入和指标。您可能需要针对每种类型的测试使用不同的软件。


    我使用Roy Osherove的单元测试命名标准描述的一致的测试命名约定。给定测试用例类中的每个方法都具有以下命名方式MethodUnderTest_Scenario_ExpectedResult。

      第一个测试名称部分是被测系统中方法的名称。
      接下来是正在测试的特定方案。
      最后是该方案的结果。

    每个部分均使用上驼峰式保护套,并由得分下限分隔。

    当运行测试时,按测试方法的名称对测试分组是很有用的。并且有一个约定允许其他开发人员了解测试意图。

    如果被测方法已重载,我还将参数附加到方法名称上。


    通常,单元测试基于模拟对象或模拟数据。
    我喜欢编写三种单元测试:

    • "瞬态"单元测试:它们创建自己的模拟对象/数据并使用其测试其功能,但销毁所有内容并且不留下任何痕迹(例如测试数据库中没有数据)
    • "持久"单元测试:它们在代码中测试创建对象/数据的功能,以后更高端的功能将需要这些功能来进行自己的单元测试(避免那些高级功能每次在其自己的一组模拟对象/数据中重新创建)
    • "基于持久"的单元测试:使用持久单元测试已经存在的模拟对象/数据(由于在另一个单元测试会话中创建)的单元测试。

    关键是要避免重播所有内容以便能够测试每个功能。

    • 我经常运行第三种,因为所有模拟对象/数据都已经存在。
    • 每当我的模型改变时,我都会运行第二种。
    • 我运行第一个脚本,不时检查非常基本的功能,以检查基本回归。

    推荐阅读

      探探语言设置|探探怎么设置语言

      探探语言设置|探探怎么设置语言,,1. 探探怎么设置语言打开探探软件,然后就有消息提示的红点,点开就行了!其实这些软件都是挺简单的操作的,都是

      git设置编码|git语言设置

      git设置编码|git语言设置,,git设置编码点击cap4j搜索从git直接链接上拉代码。git语言设置Git是一个开源的分布式版本控制系统,可以有效、高

      区域语言设置|区域语言设置工具

      区域语言设置|区域语言设置工具,,区域语言设置工具你好,大致的方法如下,可以参考:1、按下键盘的windows 图标,再开始菜单中单击“设置”;出现的

      c4d语言设置|c4d汉语设置

      c4d语言设置|c4d汉语设置,,1. c4d汉语设置mac版的C4D是这样的,中文字体是有的,但是是以拼音的形式存在,比如黑体就是ht。中文字体以拼音方式

      电脑宣传语|电脑宣传语言

      电脑宣传语|电脑宣传语言,,1. 电脑宣传语言1.我做好了与你过一辈子的打算,也做好了你随时要走的准备,2.每段青春都会苍老,但我希望记忆里的你

      office语言设置|微软office语言设置

      office语言设置|微软office语言设置,,微软office语言设置一、首先点击桌面左下角“WIN键”。二、弹出选项内点击“所有程序”。三、接着点

      小米设置日语|小米设置日语语言

      小米设置日语|小米设置日语语言,,1. 小米设置日语语言MIUI系统文字目前只支持简体中文、繁体中文、英文、藏文和维吾尔文,不支持日文 2. 小

      易语言开发电脑系统|易语言电脑版

      易语言开发电脑系统|易语言电脑版,,1. 易语言电脑版首先编译——是将程序编译为exe文件,只能在有易语言的机子上运行,独立编译——是将程序