单元测试有多深?

时间:2020-03-06 14:55:28  来源:igfitidea点击:

我发现的有关TDD的事情是,它需要花费一些时间来设置测试,并且自然而然地变得懒惰,我一直想编写尽可能少的代码。我似乎要做的第一件事是测试我的构造函数是否设置了所有属性,但是这太过分了吗?

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

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

解决方案

经典的答案是"测试任何可能会损坏的东西"。我将其解释为意味着测试setter和getter除了set或者get以外不执行任何其他操作的测试可能是过多的测试,无需花费时间。除非IDE为我们编写了这些代码,否则我们也可能会这样做。

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

通常,我从小做起,以我知道必须起作用的输入和输出。然后,在修复错误时,我添加了更多测试,以确保对已修复的内容进行测试。它是有机的,对我来说效果很好。

你可以测试太多吗?可能吧,但是总体上谨慎一点可能会更好,尽管这将取决于应用程序对任务的关键程度。

Everything should be made as simple as
  possible, but not simpler. - A. Einstein

关于TDD的最令人误解的事情之一就是其中的第一个单词。测试。这就是BDD出现的原因。因为人们并不真正了解第一个D是重要的一个,即Driven。我们都倾向于对测试多加考虑,而对设计驱动力则少加考虑。而且我想这是对问题的一个模糊的答案,但是我们可能应该考虑如何驱动代码,而不是实际测试的代码。 Coverage工具可以为我们提供帮助。设计是一个更大,更成问题的问题。

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

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

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

我们没有说过为什么还要编码架构。但是对于Java,我使用Maven 2,JUnit,DbUnit,Cobertura和EasyMock。

我编写测试以涵盖将要编写的类的假设。测试执行要求。本质上,例如,如果x永远不能为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甚至对这种东西有一些本机支持。不过,关键是上面的代码捕获了属性的意图。

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

现在跳过简单测试的部分问题在于将来,重构可能会使该简单属性变得非常复杂,并带有大量逻辑。我认为最好的主意是我们可以使用"测试"来验证模块的要求。如果在传递X时应该让Y回来,那么这就是我们要测试的内容。然后,当我们稍后更改代码时,可以验证X是否给我们Y,并且可以在以后添加该要求时为A给我们添加测试B。

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

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

基于这种哲学,不同的人将有不同的测试策略,但是鉴于我对测试如何最好地适合于编码内循环的理解还不成熟,这对我来说似乎是合理的。从现在起的十到二十年,我们可能会有一个更为通用的理论,即要编写哪些测试,不编写测试以及如何区分两者之间的差异。同时,实验似乎是有序的。

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

关于过多的测试,这是值得商bat的。有些人会说应该对所有内容进行健壮性测试,而其他人则说为了进行有效的测试,仅应测试可能破坏的事物(即逻辑)。

我会更倾向于第二个营地,仅凭个人经验,但是如果有人确实决定测试所有东西,我不会说这太多了……也许对我来说有点过大,但对他们来说并不过分。

因此,不,我不会说一般意义上的"太多"测试仅针对个人。

因此,我越是通过编写测试来推动编程,就越不用担心测试的粒度级别。回顾过去,看来我正在做最简单的事情来实现验证行为的目标。这意味着我对我的代码正在执行我要求做的事情产生信心,但是,这不能绝对保证我的代码没有错误。我认为正确的平衡点是测试标准行为,也许是一两个极端的案例,然后继续进行设计的下一部分。

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

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

当我们需要测试琐碎的getter是否实际返回正确的值时,这是因为我们可能会混用getter名称和成员变量名称。输入ruby的'attr_reader:name',这将不再发生。只是在Java中是不可能的。

如果吸气剂变得不平凡,我们仍然可以为其添加测试。

对于那些建议进行"一切"测试的人:认识到"全面测试"" int square(int x)"之类的方法需要使用通用语言和典型环境的约40亿个测试用例。

实际上,这甚至比这更糟糕:方法void setX(int newX)也必须不改变x'之外的其他成员的值-我们是否正在测试obj.yobj.z 等。在调用obj.setX(42);`之后所有都保持不变。

测试"一切"的子集是唯一可行的。一旦我们接受了这一点,考虑不对令人难以置信的基本行为进行测试就变得更加可口了。每个程序员都有错误位置的概率分布。明智的方法是将精力集中在我们估计错误概率很高的测试区域上。

测试使我们担心的源代码。

只要我们不会出错,对测试我们非常有信心的部分代码都没有用。

测试错误修正,以便它是我们第一次和最后一次修正错误。

测试以获取晦涩的代码部分的可信度,以便我们创建知识。

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

这个答案更多的是要弄清楚由于给定的方法的重要性/重要性,我们要对给定的方法使用多少个单元测试。使用McCabe的基础路径测试技术,我们可以执行以下操作来定量地获得比简单的"语句覆盖率"或者"分支覆盖率"更好的代码覆盖率置信度:

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