我发现的有关TDD的事情是,它需要花费一些时间来设置您的测试,并且自然而然地变得懒惰,我一直想编写尽可能少的代码。我似乎要做的第一件事是测试我的构造函数是否设置了所有属性,但是这太过分了吗?
我的问题是编写单元测试的粒度级别是多少?
..是否有太多测试案例?
Answers:
我从有效的代码而不是测试中获得报酬,所以我的理念是尽可能少地测试以达到给定的置信度(我怀疑与行业标准相比,此置信度高,但这可能只是自负) 。如果我通常不会犯某种错误(例如在构造函数中设置错误的变量),那么我不会对其进行测试。我确实倾向于理解测试错误,所以当我有复杂条件的逻辑时,我要格外小心。在团队中进行编码时,我会修改策略以仔细测试我们共同容易出错的代码。
基于这种哲学,不同的人将有不同的测试策略,但是鉴于我对测试如何最好地适合于编码内循环的理解还不成熟,这对我来说似乎是合理的。从现在起的十到二十年,我们可能会有一个更为通用的理论,即要编写哪些测试,不编写测试以及如何区分差异。同时,实验似乎是有序的。
编写单元测试以了解您可能会遇到的问题以及极端情况。此后,应在引入错误报告时添加测试用例-在编写该错误的修复程序之前。然后,开发人员可以确信:
根据附带的注释-如果随着时间的推移在给定的类中发现大量错误,我猜这种编写单元测试的方法可能会引起问题。这可能是在谨慎处理方面有所帮助的地方-仅对可能再次发生的错误添加单元测试,或者重新发生可能导致严重问题的错误。我发现,在这些情况下,对单元测试中的集成测试进行度量可能会有所帮助-在较高的代码路径中测试代码可以覆盖较低的代码路径。
一切都应该尽可能简单,但不要简单。-爱因斯坦
关于TDD的最令人误解的事情之一就是其中的第一个单词。测试。这就是BDD出现的原因。因为人们并不真正了解第一个D是重要的一个,即Driven。我们都倾向于对测试多加考虑,而对设计驱动力则少加考虑。而且我想这是对您的问题的一个模糊的答案,但是您可能应该考虑如何驱动代码,而不是实际测试的代码。Coverage工具可以为您提供帮助。设计是一个更大,更成问题的问题。
对于那些建议进行“一切”测试的人:意识到“全面测试”这样的方法int square(int x)
需要使用通用语言和典型环境的大约40亿个测试用例。
事实上,它甚至更糟的是:一种方法void setX(int newX)
也有责任不改变之外的任何其他成员的价值观x
-你测试的是obj.y
,obj.z
等所有调用后保持不变obj.setX(42);
?
测试“一切”的子集是唯一可行的。 一旦您接受了这一点,考虑不测试难以置信的基本行为就变得更加可口了。每个程序员都有错误位置的概率分布。明智的方法是将精力集中在您估计错误概率很高的测试区域上。
现在跳过简单测试的部分问题在于将来,重构可能会使这个简单的属性变得非常复杂,并带有很多逻辑。我认为最好的主意是您可以使用“测试”来验证模块的要求。如果您通过X时应该将Y退回,那么这就是您要测试的内容。然后,当您稍后更改代码时,可以验证X是否为Y,并且以后添加此要求时,可以为A添加测试以为B。
我发现我在最初的开发中花费的时间用于编写测试会在第一个或第二个错误修复中获得回报。能够拾起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 }; }
}
使用lambda和属性,甚至可以更紧凑地编写。我搜集到的MBUnit甚至对这种东西有一些本机支持。不过,关键是上面的代码捕获了属性的意图。
PS:也许PropertyTest还应该具有一种检查对象上其他属性是否不变的方法。嗯..回到绘图板上。
我进行单元测试以达到最大可行范围。如果我无法找到某些代码,则将重构直到覆盖范围尽可能完整为止
在完成了盲目的编写测试之后,我通常会编写一个测试用例来重现每个错误
我习惯于在代码测试和集成测试之间进行区分。在集成测试期间(这也是单元测试,但在组件组上,因此不完全是单元测试的目的),我将测试正确实现的需求。
通常,我从小做起,以我知道必须起作用的输入和输出。然后,在修复错误时,我添加了更多测试,以确保对已修复的内容进行测试。它是有机的,对我来说效果很好。
你可以测试太多吗?可能吧,但是总体上谨慎一点可能会更好,尽管它取决于应用程序的关键任务。
我认为您必须测试业务逻辑“核心”中的所有内容。Getter和Setter也是如此,因为它们可以接受您可能不想接受的负值或空值。如果您有时间(总是取决于您的老板),最好测试其他业务逻辑和调用这些对象的所有控制器(您从单元测试缓慢地转到集成测试)。
我读得越多,我就越认为某些单元测试就像一些模式:语言不足的味道。
当您需要测试琐碎的getter是否实际返回正确的值时,这是因为您可能会混用getter名称和成员变量名称。输入ruby的'attr_reader:name',这将不再发生。只是在Java中是不可能的。
如果您的吸气剂变得不平凡,您仍然可以为其添加测试。
这个答案更多的是要弄清楚由于给定的方法的重要性/重要性,您知道要对给定方法使用多少个单元测试。使用McCabe的基础路径测试技术,您可以执行以下操作来定量地获得比简单的“语句覆盖率”或“分支覆盖率”更好的代码覆盖率置信度: