88

我发现关于 TDD 的一点是,设置测试需要时间,而且我天生懒惰,我总是希望编写尽可能少的代码。我似乎做的第一件事是测试我的构造函数是否设置了所有属性,但这是否矫枉过正?

我的问题是您编写单元测试的粒度级别是多少?

..是否存在测试过多的情况?

4

17 回答 17

221

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

基于这种理念,不同的人会有不同的测试策略,但考虑到对如何最好地适应编码内部循环的理解还不成熟,这对我来说似乎是合理的。十年或二十年后,我们可能会有一个更普遍的理论来说明哪些测试可以写,哪些测试不可以写,以及如何区分。与此同时,实验似乎是有序的。

于 2008-09-30T15:30:47.823 回答
20

为您希望破坏的事物和边缘情况编写单元测试。在那之后,测试用例应该随着错误报告的出现而被添加——在为错误编写修复之前。然后,开发人员可以确信:

  1. 该错误已修复;
  2. 该错误不会再次出现。

根据附加的评论 - 我猜这种编写单元测试的方法可能会导致问题,如果随着时间的推移在给定的类中发现了很多错误。这可能是自由裁量权有用的地方——只为可能再次发生的错误添加单元测试,或者这些错误再次发生会导致严重问题。我发现单元测试中的集成测试度量在这些场景中可能会有所帮助 - 测试代码路径较高的代码可以覆盖较低的代码路径。

于 2008-09-30T14:42:26.503 回答
19

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

关于 TDD 最容易被误解的事情之一就是它的第一个词。测试。这就是 BDD 出现的原因。因为人们并没有真正理解第一个 D 是重要的,即 Driven。我们都倾向于对测试思考得有点多,而对设计的驱动思考得有点少。而且我想这是对您的问题的模糊回答,但是您可能应该考虑如何驱动代码,而不是实际测试的内容;这是 Coverage-tool 可以帮助您解决的问题。设计是一个更大、更成问题的问题。

于 2008-09-30T14:26:39.397 回答
15

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

事实上,它甚至比这更糟糕:一个方法void setX(int newX)也必须改变任何其他成员的值,除了x- 你是否测试obj.y,obj.z等在调用之后都保持不变obj.setX(42);

只有测试“一切”的一个子集才实用。 一旦你接受了这一点,考虑不测试难以置信的基本行为就变得更容易接受了。每个程序员都有一个错误位置的概率分布;聪明的方法是将你的精力集中在你估计错误概率很高的测试区域上。

于 2009-04-17T05:29:44.707 回答
9

经典答案是“测试任何可能破坏的东西”。我将其解释为意味着测试除了 set 或 get 之外不做任何事情的 setter 和 getter 可能是太多的测试,不需要花时间。除非您的 IDE 为您编写这些,否则您也可以这样做。

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

于 2008-09-30T14:23:13.973 回答
5

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

总是,如果我不写一个测试来覆盖一个条件,它会在稍后的“人类”测试中出现。我当然会写一篇,但我宁愿早点赶上。我认为关键是测试很乏味(也许)但很有必要。我写了足够多的测试来完成,但仅此而已。

于 2008-09-30T14:38:35.373 回答
5

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

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

于 2008-09-30T15:26:17.083 回答
4

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

关于过多的测试,这是值得商榷的。有人会说应该测试所有东西的健壮性,其他人说为了有效的测试,只有可能破坏的东西(即逻辑)才应该被测试。

仅从个人经验来看,我更倾向于第二个阵营,但如果有人真的决定测试所有东西,我不会说这太过分了……对我来说可能有点矫枉过正,但对他们来说不是太多。

所以,不 - 我会说一般意义上的“太多”测试并不存在,仅针对个人。

于 2008-09-30T15:46:59.170 回答
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 甚至对这样的事情有一些本机支持。关键是上面的代码捕获了属性的意图。

PS:PropertyTest 可能还应该有一种方法来检查对象上的其他属性是否没有改变。嗯..回到绘图板。

于 2008-09-30T14:58:44.467 回答
1

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

完成盲写测试后,我通常会编写一个测试用例来重现每个错误

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

于 2008-09-30T14:47:41.637 回答
1

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

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

于 2008-12-28T12:39:25.337 回答
0

通常,我从小处着手,输入和输出我知道必须有效。然后,当我修复错误时,我会添加更多测试以确保对我修复的内容进行测试。它是有机的,对我来说效果很好。

你可以测试太多吗?可能,但总的来说,最好还是谨慎行事,尽管这取决于您的应用程序的任务关键程度。

于 2008-09-30T14:25:10.523 回答
0

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

于 2008-09-30T14:27:48.253 回答
0

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

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

你也没有说你为什么要编码架构。但对于 Java,我使用Maven 2JUnitDbUnitCoberturaEasyMock

于 2008-09-30T14:31:02.163 回答
0

我读得越多,我就越觉得一些单元测试就像一些模式:语言不足的味道。

当您需要测试您的普通 getter 是否真的返回正确的值时,这是因为您可能会混合 getter 名称和成员变量名称。输入 'attr_reader :name' of ruby​​,这种情况就不会再发生了。在java中是不可能的。

如果你的吸气剂变得不平凡,你仍然可以为它添加一个测试。

于 2009-02-06T18:54:22.690 回答
0

测试让你担心的源代码。

对测试您非常有信心的部分代码没有用处,只要您不犯错误即可。

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

测试以获得对晦涩代码部分的信心,以便您创造知识。

在重重构和中重构之前进行测试,以免破坏现有功能。

于 2009-07-17T12:09:19.803 回答
0

这个答案更多地用于确定由于其重要性/重要性而知道要对给定方法使用多少单元测试。使用McCabe 的基础路径测试技术,您可以执行以下操作以定量地获得比简单的“语句覆盖”或“分支覆盖”更好的代码覆盖置信度:

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