应该为 getter 和 setter 编写单元测试吗?

我们是应该为我们的 getter 和 setter 编写测试,还是过度了?

107355 次浏览

如果 getter 和/或 setter 的 循环复杂度是1(它们通常是1) ,那么答案是否定的,您不应该这样做。

因此,除非您的 SLA 需要100% 的代码覆盖率,否则不要费心,而是专注于测试软件的重要方面。

另外,记住要区分 getter 和 setter,即使在像 C # 这样的语言中,属性可能看起来是相同的。Setter 的复杂度可能高于 getter,因此可以验证单元测试。

我会说没有。

@ Will 说你的目标应该是100% 的代码覆盖率,但在我看来这是个危险的干扰。您可以编写覆盖率为100% 的单元测试,但是完全不测试任何东西。

单元测试是用来以一种富有表现力和意义的方式测试代码的行为,而 getter/setter 只是达到目的的一种手段。如果您测试使用 getter/setter 来实现测试“真正的”功能的目标,那么这就足够好了。

另一方面,如果 getter 和 setter 所做的不仅仅是 get 和 set (也就是说,它们是相当复杂的方法) ,那么是的,它们应该被测试。但是不要编写单元测试用例 只是来测试 getter 或 setter,那是浪费时间。

罗伊•奥舍罗夫(Roy Osherove)在其著作《单元测试的艺术》(The Art Of Unit Test)中写道:

属性(Java 中的 getters/setter)是通常不包含任何逻辑并且不需要测试的代码的好例子。但是要注意: 一旦您在属性中添加了任何检查,您将希望确保正在测试逻辑。

虽然有正当的理由为属性,有一个共同的面向对象设计信念,通过属性暴露成员国是糟糕的设计。罗伯特 · 马丁关于开闭原理的文章对此进行了扩展,声明 Properties 鼓励耦合,因此限制了从修改中关闭类的能力——如果修改了属性,类的所有使用者也都需要进行修改。他认为公开成员变量并不一定是糟糕的设计,可能只是风格不佳。但是,如果属性是只读的,那么滥用和副作用的机会就会减少。

对于单元测试,我能提供的最佳方法(这可能看起来很奇怪)是尽可能多地创建受保护的或内部的属性。这将防止耦合,同时阻止为 getter 和 setter 编写愚蠢的测试。

使用读/写属性的原因很明显,比如绑定到输入字段的 ViewModel 属性等等。

更实际地说,单元测试应该通过公共方法来驱动功能。如果您正在测试的代码恰好使用了这些属性,那么您可以免费获得代码覆盖率。如果事实证明这些属性从未被代码覆盖所突出显示,那么很有可能:

  1. 缺少间接使用属性的测试
  2. 这些房产都是未使用的

如果您为 getter 和 setter 编写测试,那么您将得到一个错误的覆盖率感觉,并且将无法确定这些属性是否实际被函数行为所使用。

Dr: 是的 you 应该,而对于 OpenPojo,它是微不足道的。

  1. 您应该在 getter 和 setter 中进行一些验证,以便对其进行测试。例如,setMom(Person p)不应该允许将任何比自己年轻的人设置为他们的母亲。

  2. 即使你现在什么都不做,将来你也有可能这么做,那么这对回归分析来说也是一件好事。如果你想让设置母亲的 null你应该有一个测试,如果有人改变后,这将加强你的假设。

  3. 一个常见的错误是 void setFoo( Object foo ){ foo = foo; },它应该是 void setFoo( Object foo ){ this.foo = foo; }。(在第一种情况下,写入的 foo参数 没有对象上的 foo字段)。

  4. 如果要返回一个数组或集合,那么应该测试 getter 在返回之前是否要执行传递给 setter 的数据的 防御性副本

  5. 否则,如果您拥有最基本的 setter/getter,那么对它们进行单元测试最多只能为每个对象增加大约10分钟的时间,那么损失是多少呢?如果你加上行为,你已经有了一个骨骼测试,而且你还可以免费得到这个回归测试。如果您正在使用 Java,您没有任何借口,因为有 OpenPojo。您可以启用一组现有的规则,然后使用它们扫描整个项目,以确保它们在代码中得到一致的应用。

来自他们的 例子:

final Validator pojoValidator = ValidatorBuilder.create()
.with(
new NoPublicFieldsRule  (),
new NoPrimitivesRule    (),
new GetterMustExistRule (),
new SetterMustExistRule ()
)
.with(
new DefaultValuesNullTester (),
new SetterTester            (),
new GetterTester            ()
)
.build();




pojoValidator.validate(  PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() )  );

一个幽默,但明智的采取: 特斯提乌斯之路

“今天写你能写的测试”

如果您是一个有经验的测试人员,并且这是一个小项目,那么测试 getter/setter 可能有些过头了。然而,如果您刚刚开始学习如何进行单元测试,或者这些 getter/setter 可能包含逻辑(如@ArtB 的 setMom()示例) ,那么编写测试将是一个好主意。

我做了一点 对 JUnit 代码本身实现的覆盖率的分析

一类没有被发现的代码是 “简单到无法测试”。这包括简单的 getter 和 setter,JUnit 的开发人员对它们进行 没有测试。

另一方面,JUnit 没有任何超过3行的(非弃用的)方法,这些方法没有被任何测试覆盖。

带有 TDD 的响亮的“是”


注意 : 这个答案不断得到赞同,尽管可能是个糟糕的建议。


争议是存在的,但是我认为任何对这个问题回答“不”的人都忽略了 TDD 的基本概念。

对我来说,如果您遵循 TDD,那么答案是一个响亮的 是的。如果你不是,那么“不”是一个合理的答案。

TDD 中的 DDD

TDD 经常被引用为具有三个主要的好处。

  • 辩护律师
    • 确保 代码可能会改变但是 而不是它的行为
    • 这使得 重构的重要实践成为可能。
    • 你到底能不能拿到 TDD。
  • 设计
    • 具体说明什么东西应该做,它应该如何行为 在实施前它。
    • 这通常意味着更加知情的 执行决定
  • 文件
    • 测试套件应该作为 规格(需求)文档。
    • 为此目的使用测试意味着文档和实现始终处于一致的状态——对一个测试的更改意味着对另一个测试的更改。与单独文档的保存要求和设计进行比较。

将责任与实施分开

作为程序员,将属性视为重要的东西,将 getter 和 setter 视为某种开销,这种想法极具诱惑力。

但是属性是一个实现细节,而 setter 和 getter 是实际使程序工作的契约接口。

更重要的是拼写一个物体应该:

允许其客户端更改其状态

还有

允许其客户端查询其状态

那么这个状态实际上是如何存储的(对于这个状态,属性是最常见的,但不是唯一的方法)。

这样的测试

(The Painter class) should store the provided colour

对 TDD 的 文件部分很重要。

在编写测试时,您应该不知道最终的实现是微不足道的(属性)并且没有 辩护律师的好处。

缺乏往返工程..。

系统开发领域的一个关键问题是缺乏 往返工程学1——系统的开发过程被分割成不连续的子过程,其中的工件(文档、代码)通常是不一致的。

《概念建模: 基础与应用》 ,Springer Berlin Heidelberg,2009.1-9。

以及 TDD 如何解决它

TDD 的 文件部分确保了系统的规范及其代码始终保持一致。

先设计,后实现

在 TDD 中,我们首先编写失败验收测试,然后才编写代码让它们通过。

在较高级别的 BDD 中,我们首先编写场景,然后让它们通过。

为什么要排除 setter 和 getter?

理论上,在 TDD 中,一个人编写测试,另一个人实现使测试通过的代码是完全可能的。

所以问问你自己:

为一个类编写测试的人是否应该提到 getter 和 setter。

因为 getter 和 setter 是类的公共接口,所以答案显然是 是的,否则就没有办法设置或查询对象的状态。然而,做到这一点的方法不一定是单独测试每个方法,请参阅我的 另一个答案了解更多。

显然,如果您先编写代码,那么答案可能就不那么明确了。

是的,但并不总是孤立的

请允许我详细说明:

什么是单元测试?

来自 有效地使用遗留代码1:

单元测试这个术语在软件开发中有很长的历史 大多数单元测试的概念是,它们是独立于个体的测试 软件的组件。什么是组件? 定义各不相同, 但是在单元测试中,我们通常关注系统中最原子的行为单元。在过程代码中,单元通常是函数。在面向对象的代码中,单位是类。

注意,在 OOP 中,您可以找到 getters 和 setter,该单元是 同学们,而不一定是 个别方法

什么是好的测试?

所有的要求和测试都遵循 白痴逻辑的形式:

{ P } C { Q }

地点:

  • {P}是先决条件(给予)
  • C是触发条件(什么时候)
  • {Q}是后置条件(那么)

接下来是一句格言:

测试行为,而不是实现

这意味着您不应该测试 C如何实现后置条件,您应该检查 {Q}是否是 C的结果。

当涉及到面向对象编程时,C是一个类。所以你不应该测试内部效果,只能测试外部效果。

为什么不单独测试 bean 的 getter 和 setter 呢

Getter 和 setter 可能涉及一些逻辑,但是只要这些逻辑没有外部效果——使它们成为 Bean 访问器2) ,测试就必须查看对象的内部,这样做不仅违反了封装,而且还违反了实现测试。

所以你不应该单独测试 bean 的 getter 和 setter,这是不好的:

Describe 'LineItem class'


Describe 'setVAT()'


it 'should store the VAT rate'


lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )

虽然如果 setVAT会抛出一个异常,一个相应的测试将是适当的,因为现在 有的是一个外部效应。

如何测试 getter 和 setter?

如果这种改变对外部没有影响,那么改变对象的内部状态实际上是没有意义的,即使这种影响后来才出现。

因此,对 setter 和 getter 的测试应该关注这些方法的外部效应,而不是内部效应。

例如:

Describe 'LineItem class'


Describe 'getGross()'


it 'should return the net time the VAT'


lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )

你可能会想:

等一下,我们正在测试 getGross()这里 没有 setVAT()

但是如果 setVAT()发生故障,那么测试仍然会失败。

羽毛,M。 ,2004。有效地使用遗留代码。 Prentice Hall Professional。

2 Martin,R.C. ,2009清洁代码: 敏捷软件工艺手册培生教育。

这实际上是我和我的团队之间最近的一个话题。代码覆盖率达到80% 。我的团队认为 getter 和 setter 是自动实现的,编译器在幕后生成一些基本代码。在这种情况下,由于生成的代码是非侵入性的,因此测试编译器为您创建的代码实际上没有意义。我们还讨论了异步方法,在这种情况下,编译器会在幕后生成一大堆代码。这是一个不同的情况,我们确实测试了一些东西。长话短说,向你的团队提出来,然后自己决定是否值得测试。

另外,如果您像我们一样使用代码覆盖率报告,那么您可以添加[ ExcludeFromCodeCoverage ]属性。我们的解决方案是将其用于仅使用 getter 和 setter 具有属性的模型,或者用于属性本身。这样,在运行代码覆盖率报告时,它不会影响总代码覆盖率% ,假设您正在使用它来计算代码覆盖率百分比。测试愉快!

在我看来,代码覆盖率是一个很好的方法,可以看到您是否遗漏了应该覆盖的任何功能。

当你手动检查覆盖率的时候,你会发现普通的 getter 和 setter 并不需要测试(尽管我总是这样做)。

如果您只检查项目上的代码覆盖率百分比,那么像80% 这样的测试覆盖率百分比是没有意义的。您可以测试所有没有逻辑的部分,而忘记一些关键的部分。在这种情况下,只有100% 意味着您已经测试了所有重要代码(以及所有非逻辑代码)。只要是99.9% 的人,你就知道他们忘记了什么。

顺便说一下: 代码覆盖率是检查您是否已经完全(单元)测试了一个类的最终检查。但是100% 的代码覆盖率并不一定意味着您实际上已经测试了类的所有功能。因此,单元测试应该始终按照类的逻辑实现。最后,你运行覆盖面,看看你是否忘记了什么。如果你做对了,第一次就能达到100% 。

还有一件事: 最近在荷兰一家大银行工作时,我注意到声纳显示100% 的代码覆盖率。然而,我知道少了点什么。检查每个文件的代码覆盖率百分比,它指示一个百分比较低的文件。整个代码基数百分比非常大,以至于一个文件不能使百分比显示为99.9% 。所以你最好小心这个。

我会说: 是的 Getter/setter 方法中的错误可能悄无声息地潜入并导致一些难看的 bug。

我编写了一个 lib 来简化这个测试和其他一些测试。 您在 JUnit 测试中必须编写的唯一内容是:

        assertTrue(executor.execute(*TheClassIWantToTest.class*, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));

- > https://github.com/Mixermachine/base-test

是的,特别是当要获取的项是从抽象类继承的类的对象时。IDE 可能会提醒您某个属性尚未初始化,也可能不会。

然后一些明显不相关的测试在使用 NullPointerException时崩溃了,您需要一段时间才能发现 gettable 属性实际上并不存在。

虽然这还不如在生产过程中发现问题那么糟糕。

确保所有抽象类都有构造函数可能是一个好主意。如果没有,getter 测试可能会提醒您那里有问题。

至于原语的 getter 和 setter,问题可能是: 我是在测试我的程序还是在测试 JVM 或 CLR?一般来说,不需要测试 JVM。