你的单位测试有多深?

时间:2008-09-30 14:19:15

标签: unit-testing tdd

我发现关于TDD的事情是它需要时间来设置你的测试并且自然是懒惰的我总是希望尽可能少编写代码。我似乎做的第一件事是测试我的构造函数已经设置了所有的属性,但这是否有点过分?

我的问题是你在单元测试中写出了什么级别的粒度?

..是否有过多测试的情况?

17 个答案:

答案 0 :(得分:221)

我得到的代码是有效的,而不是测试代码,所以我的理念是尽可能少地测试以达到给定的置信水平(我怀疑这种信心水平与行业标准相比很高,但这可能只是狂妄自大)。如果我通常不会犯一个错误(比如在构造函数中设置错误的变量),我不会测试它。我确实倾向于理解测试错误,所以当我有复杂条件的逻辑时我会格外小心。在团队编码时,我会修改我的策略,仔细测试我们总体上会出错的代码。

根据这一理念,不同的人会有不同的测试策略,但这对我来说似乎是合理的,因为对于测试最适合编码内循环的理解是不成熟的。从现在起十年或二十年后,我们可能会有一个更普遍的理论,即哪些测试要写,哪些测试不写,以及如何区分。与此同时,实验似乎是有序的。

答案 1 :(得分:20)

为您希望破坏的事物和边缘情况编写单元测试。之后,应该在bug报告进入时添加测试用例 - 在编写bug修复之前。然后,开发人员可以确信:

  1. 错误已修复;
  2. 该错误不会再出现。
  3. 根据附带的评论 - 我想这种编写单元测试的方法可能会导致问题,如果在给定的类中发现了很多错误。这可能是自由裁量权有用的地方 - 仅针对可能重新发生的错误或重新发生会导致严重问题的错误添加单元测试。我发现单元测试中的集成测试测量在这些场景中会有所帮助 - 测试更高代码路径的代码可以覆盖更低的代码路径。

答案 2 :(得分:19)

  

一切都应该如此简单   可能,但并不简单。 - A.爱因斯坦

关于TDD最容易被误解的事情之一就是其中的第一个字。测试。这就是BDD出现的原因。因为人们并不真正理解第一个D是重要的,即Driven。我们都倾向于对测试有所了解,对设计的驱动有点关注。我想这是对你的问题的一个模糊的答案,但你应该考虑如何驱动你的代码,而不是你实际测试的;这是Coverage工具可以帮助您的东西。设计是一个更大,更有问题的问题。

答案 3 :(得分:15)

对于那些建议测试“一切”的人:意识到“完全测试”像int square(int x)这样的方法需要大约40亿个常见语言和典型环境的测试用例。

事实上,情况甚至更糟:方法void setX(int newX)也有义务来改变除x之外的任何其他成员的价值 - 你是否正在测试在致电obj.y后,obj.zobj.setX(42);等都保持不变?

测试“一切”的子集是切实可行的。一旦接受了这一点,考虑不测试不可思议的基本行为就变得更加可口了。每个程序员都有一个概率分布的bug位置;聪明的方法是将精力集中在测试区域,在那里您可以估算出错误概率很高。

答案 4 :(得分:9)

经典的答案是“测试任何可能破坏的东西”。我认为这意味着测试setter和getter除了set或get之外什么也不做,可能是太多的测试,不需要花时间。除非你的IDE为你写那些,否则你也可以。

如果您的构造函数设置属性可能会导致以后出错,那么测试它们的设置并不过分。

答案 5 :(得分:5)

我编写测试来涵盖我将要编写的类的假设。测试强制执行要求。基本上,例如,如果x永远不会是3,那么我将确保有一个涵盖该要求的测试。

总是,如果我不编写测试来覆盖某个条件,它将在“人类”测试期间稍后出现。我当然会写一个,但我宁愿早点抓住它们。我认为关键在于测试是乏味的(可能)但是必要的。我写了足够多的测试来完成,但不过是。

答案 6 :(得分:5)

现在跳过简单测试的部分问题是将来的重构可能会使这个简单的属性变得非常复杂,并且有很多逻辑。我认为最好的想法是您可以使用测试来验证模块的要求。如果当你通过X时你应该回Y,那就是你要测试的。然后,当您稍后更改代码时,您可以验证X为您提供了Y,并且您可以为A添加测试,以便在以后添加该要求时为您提供B.

我发现在初始开发编写测试期间花费的时间在第一次或第二次错误修复中得到了回报。能够获取你在3个月内没有看过的代码并且合理地确定你的修复程序涵盖了所有的情况,并且“可能”不会破坏任何东西是非常有价值的。您还会发现,单元测试将有助于对错误进行分类,而不仅仅是堆栈跟踪等。查看应用程序的各个部分如何工作和失败,可以深入了解它们为什么工作或整体失败。

答案 7 :(得分:4)

在大多数情况下,我会说,如果有逻辑,请测试它。这包括构造函数和属性,尤其是当属性中设置了多个东西时。

关于太多的测试,这是值得商榷的。有些人会说应该测试所有内容的稳健性,其他人则说,为了进行有效的测试,只应该测试可能会破坏的东西(即逻辑)。

我更倾向于第二阵营,仅仅是出于个人经验,但如果有人决定测试一切,我不会说这太多了......对我来说可能有点过分,但不是太多对他们来说。

所以,不 - 我会说在一般意义上没有“太多”测试这样的事情,只针对个人。

答案 8 :(得分:3)

测试驱动开发意味着您在所有测试通过后停止编码。

如果您没有对房产进行测试,那么为什么要实施?如果您在“非法”分配的情况下不测试/定义预期的行为,该财产应该做什么?

因此,我完全是为了测试一个班级应该展示的每一个行为。包括“原始”属性。

为了简化这项测试,我创建了一个简单的NUnit TestFixture,它提供了设置/获取值的扩展点,并获取了有效值和无效值的列表,并进行了一次测试以检查属性是否正常。测试单个属性可能如下所示:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

使用lambdas和属性甚至可以更紧凑地编写。我收集MBUnit甚至有一些本地支持这样的事情。但重点是上面的代码捕获了属性的意图。

P.S。:也许PropertyTest还应该检查对象上的其他属性是否没有改变。嗯..回到绘图板。

答案 9 :(得分:1)

我进行单元测试以达到最大可行覆盖率。如果我无法获得某些代码,我会重构,直到覆盖范围尽可能充分

完成盲目写入测试后,我通常会编写一个复制每个bug的测试用例

我习惯于将代码测试和集成测试分开。在集成测试期间(也是单元测试但是在组件组上,所以不完全是单元测试的用途)我将测试要正确实现的要求。

答案 10 :(得分:1)

因此,通过编写测试我的编程越多,我就越不担心测试的粒度级别。回顾过去,我似乎在做最简单的事情来实现验证行为的目标。这意味着我产生了一层信心,我的代码正在做我要求做的事情,但是这不被认为是我的代码没有bug的绝对保证。我觉得正确的平衡是测试标准行为,也许是一两个边缘情况,然后转到我设计的下一部分。

我接受这不会涵盖所有错误并使用其他传统测试方法来捕获这些错误。

答案 11 :(得分:0)

这个答案更多的是用于确定用于您知道要进行单元测试的给定方法的单元测试的数量,因为它的重要性/重要性。使用McCabe的基础路径测试技术,您可以执行以下操作来定量地获得比简单“语句覆盖”或“分支覆盖”更好的代码覆盖率信心:

  1. 确定要进行单元测试的方法的Cyclomatic Complexity值(例如,Visual Studio 2010 Ultimate可以使用静态分析工具为您计算;否则,您可以通过flowgraph方法手动计算它 - http://users.csc.calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html
  2. 列出通过您的方法流动的独立路径的基础集 - 请参阅上面的流程图示例链接
  3. 为步骤2中确定的每个独立基准路径准备单元测试

答案 12 :(得分:0)

测试让您担心的源代码。

测试你非常自信的代码部分是没用的,只要你不犯错误。

测试错误修正,以便它是您第一次也是最后一次修复错误。

测试以获得模糊代码部分的信心,以便您创建知识。

在重型和中型重构之前进行测试,这样您就不会破坏现有功能。

答案 13 :(得分:0)

我读的越多,我认为一些单元测试就像某些模式一样:语言不足的气味。

当您需要测试您的普通getter是否实际返回正确的值时,这是因为您可以混合使用getter名称和成员变量名称。输入ruby的'attr_reader:name',这不会再发生了。只是在java中不可能。

如果你的getter变得非常重要,那么你仍然可以为它添加一个测试。

答案 14 :(得分:0)

我没有单元测试没有副作用的简单setter / getter方法。但我会对其他所有公共方法进行单元测试。我尝试在我的算法中为所有边界条件创建测试,并检查我的单元测试的覆盖范围。

它做了很多工作,但我认为它值得。我宁愿编写代码(甚至测试代码)而不是在调试器中逐步执行代码。我发现代码构建 - 部署 - 调试周期非常耗时,并且我已经集成到构建中的单元测试越详尽,我花费的时间就越少花在代码构建 - 部署 - 调试周期上。

你没有说你为什么编码建筑。但对于Java,我使用Maven 2JUnitDbUnitCobertura和&amp; EasyMock

答案 15 :(得分:0)

我认为您必须在业务逻辑的“核心”中测试所有内容。 Getter ans Setter也是因为他们可以接受您可能不想接受的负值或空值。如果你有时间(总是依赖你的老板),那么测试其他业务逻辑和调用这些对象的所有控制器(你从单元测试慢慢地进行集成测试)是很好的。

答案 16 :(得分:0)

一般来说,我从小开始,我知道输入和输出必须工作。然后,当我修复错误时,我添加了更多测试以确保我已经修复的东西都经过测试。它是有机的,对我来说效果很好。

你能测试太多吗?可能,但一般来说,谨慎行事可能会更好,尽管这取决于您的应用程序的关键任务。