应该为吸气剂和吸气剂编写单元测试吗?


141

我们应该为吸气剂和塞脂剂编写测试,还是过度杀伤力?


我不这么认为。您不应该为getter / setter编写测试用例。
哈里·乔伊

1
我想你是说Java吗?对于Java来说,这是一个特别尖锐的问题,对于更现代的语言则更是如此。
skaffman 2011年

@skaffman哪些现代语言没有属性?当然,像Java这样的语言要求它们必须是完整的方法主体,但这在逻辑上与C#并没有什么不同。
克劳斯Jørgensen的

2
@克劳斯:他没有说财产,他说的是吸气剂和吸气剂。在Java中,您可以手动编写这些代码,而在其他语言中,则可以获得更好的支持。
skaffman 2011年

Answers:


180

我会说不。

@Will表示您应该争取100%的代码覆盖率,但是我认为这是一种危险的干扰。您可以编写具有100%覆盖率的单元测试,但绝对不能进行任何测试。

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

另一方面,如果您的getter和setter所做的不仅仅是获取和设置(即它们是适当的复杂方法),那么是的,应该对其进行测试。但是不要写一个单元测试用例只是为了测试一个或多个吸气剂,那是浪费时间。


7
争取100%的代码覆盖率是愚蠢的。您最终将覆盖未公开的代码和/或没有复杂性的代码。这样的事情最好留给自动生成,但是即使自动生成的测试也毫无意义。最好将重点转移到重要的测试上,例如集成测试。
ClausJørgensen

7
@Claus:我同意除了专注于集成测试之外。单元测试对于良好的设计和补充集成测试至关重要。
skaffman 2011年

5
我认为在整个测试套件运行时100%的代码覆盖率是一个很好的目标。属性获取器设置器和其他代码在较大的测试中运行。最后要介绍的内容可能是错误处理。而且,如果错误处理未包含在单元测试中,那么它将永远不会包含在内。您是否真的要交付包含从未运行过的代码的产品?
Anders Abel

我倾向于不同意这个建议。是的,getter和setter很简单,但是它们也很容易弄乱。几行简单的测试,您就知道它们正在起作用并继续起作用。
查尔斯

您可能正在测试您(或工具)创建的不必要的二传手,而这些二传手只是为了拥有“四舍五入”的POJO。您可能每个实例都使用Gson.fromJson来“膨胀” POJOS(不需要设置器)。在这种情况下,我的选择是删除未使用的setter。
艾伯托·高纳

36

罗伊·奥什罗夫(Roy Osherove)在他着名的《单元测试的艺术》中说:

属性(Java中的getter / setter)是很好的代码示例,通常不包含任何逻辑,也不需要测试。但是要当心:在属性中添加任何检查之后,您将要确保逻辑已经过测试。


1
考虑到花很少的时间测试那些,以及增加行为的可能性,我不明白为什么不这样做。我怀疑如果按下,他可能会回答“恩,我想为什么不这样做”。
雪橇

1
重要的早期书籍通常有不好的建议。我见过很多情况,人们会很快地在吸气器和装夹器中折腾,并且由于他们正在剪切和粘贴或者忘记了这一点而犯错。或this->或类似的东西。它们易于测试,因此应该进行测试。
查尔斯

35

TDD的确是肯定的


注意:尽管可能会带来错误的建议,但此答案仍会不断增加。要了解原因,请看下面的小妹妹


很有争议,但我认为,对此问题回答“否”的任何人都缺少TDD的基本概念。

对我来说,如果您遵循TDD ,答案是肯定的。如果不是,那么不合理的答案。

TDD中的DDD

TDD通常被认为具有主要优点。

  • 防御
    • 确保代码可能会更改,行为不会更改
    • 这允许进行如此重要的重构实践。
    • 您是否获得此TDD。
  • 设计
    • 在实施之前,您可以指定应执行的操作,其行为方式。
    • 这通常意味着更明智的实施决策
  • 文献资料
    • 测试套件应作为规范(要求)文档。
    • 为此目的使用测试意味着文档和实现始终处于一致状态-对一个进行更改意味着对另一个进行更改。与保留要求和在单独的Word文档上进行设计相比。

将责任与实施分开

作为程序员,将属性视为重要的事物而将属性视为获取器和设置器是非常费力的。

但是属性是实现细节,而setter和getter是使程序真正起作用的契约接口。

拼写一个对象应该:

允许其客户更改其状态

允许其客户查询其状态

然后实际存储此状态的方式(对于该状态,属性是最常见的,但不是唯一的方式)。

测试如

(The Painter class) should store the provided colour

对于TDD 的文档部分很重要。

当您编写测试时,最终的实现是微不足道的(属性)并且没有任何防御优势的事实对您来说应该是未知的。

缺乏往返工程...

系统开发领域中的关键问题之一是缺乏 往返工程1-系统的开发过程被分成多个相互分离的子过程,这些子过程的工件(文档,代码)通常不一致。

1 Brodie,MichaelL。“ John Mylopoulos:缝制概念模型的种子。” 概念建模:基础和应用程序。Springer Berlin Heidelberg,2009年。1-9。

...以及TDD如何解决

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

先设计,后实施

在TDD中,我们首先编写失败的验收测试,然后再编写使他们通过的代码。

在更高级别的BDD中,我们首先编写方案,然后使它们通过。

为什么要排除二传手和吸气剂?

从理论上讲,在TDD中,一个人完全可以编写测试,而另一个人可以实现通过测试的代码。

因此,问问自己:

编写课程测试的人是否应该提及吸气剂和吸气剂。

由于getter和setter是类的公共接口,因此答案显然是yes,否则将无法设置或查询对象的状态。但是,执行此操作的方法不一定是分别测试每种方法,有关更多信息,请参见我的其他答案

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


2
那只是硬币的一面。想一想您可以做的事情,而不只是为了测试而进行测试。正如肯特·贝克(Kent Beck)所说,您是通过工作代码而不是工作测试获得报酬的。
Georgii Oleinikov

@GeorgiiOleinikov您在下面阅读了我的其他答案吗?这非常符合您的观点。
伊扎基

21

tl; dr: 是的应该,并且与OpenPojo无关紧要。

  1. 您应该在获取器和设置器中进行一些验证,以便您进行测试。例如,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. 否则,如果您拥有最基本的设置器/获取器,则对每个对象进行单元测试最多可能会增加大约10分钟的时间,那么损失是多少?如果添加行为,则您已经有骨架测试,并且可以免费获得此回归测试。如果您使用Java,则没有借口,因为这里有OpenPojo。您可以启用一组现有规则,然后使用它们扫描您的整个项目,以确保在代码中一致地应用它们。

从他们的例子

final PojoValidator pojoValidator = new PojoValidator();

//create rules
pojoValidator.addRule( new NoPublicFieldsRule  () );
pojoValidator.addRule( new NoPrimitivesRule    () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );

//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester            () );
pojoValidator.addTester( new GetterTester            () );

//test all the classes
for(  PojoClass  pojoClass :  PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() )  )
    pojoValidator.runValidation( pojoClass );

11
我知道这现在已经过时了,但是:您是否应该在获取器和设置器中进行验证?我的印象是setMom应该按照它说的去做。如果正在验证,那么它不应该是validateAndSetMom吗?还是更好,验证代码是否应该比简单对象还存在?我在想什么?
ndtreviv

14
是的,您应该始终在验证输入。如果不是,那为什么不只使用公共变量呢?变成同一件事。使用setter和变量的全部好处在于,它可以确保对象永远不会无效,例如age为负数。如果您不在对象中执行此操作,那么您将在其他地方执行此操作(例如“服务” 上帝对象),那时候还不是真正的OOP。
2013年

1
好吧,这可能是我这一周的亮点。谢谢。
雪橇2013年

19

是的,但并非总是孤立

请允许我详细说明:

什么是单元测试?

有效使用旧代码1开始

术语“单元测试”在软件开发中具有悠久的历史。大多数单元测试概念的共同点是,它们是隔离软件各个组件的测试。什么是成分?定义各不相同,但是在单元测试中,我们通常关心系统中最原子的行为单元。在过程代码中,单位通常是功能。在面向对象的代码中,单位是类。

请注意,在OOP中,您可以找到getter和setter,单位是,而不一定是单个方法

什么是好的测试?

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

{P} C {Q}

哪里:

  • {P}是前提(给定
  • C是触发条件(当时
  • {Q}是后置条件(

然后是格言:

测试行为,而不是实现

这意味着您不应该测试如何C达到后置条件,而应该检查{Q}的结果C

说到OOP,C是一门课。因此,您不应测试内部效果,而只能测试外部效果。

为什么不单独测试bean的获取器和设置器

Getter和Setter可能涉及一些逻辑,但是很久以来,该逻辑没有外部影响-使它们成为bean访问器2)测试必须查看对象内部,不仅破坏封装,还测试实现。

因此,您不应该单独测试bean的获取器和设置器。这不好:

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抛出异常,则进行适当的测试是适当的,因为现在存在外部影响。

您应该如何测试吸气剂和吸气剂?

如果对象内部的状态对外部没有影响,则实际上没有任何改变的余地,即使这种影响稍后出现。

因此,对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()发生故障,则该测试将全部失败。

1 Feathers,M.,2004年。有效处理遗留代码。Prentice Hall专业人士。

2 Martin,RC,2009年。干净代码:敏捷软件工艺手册。培生教育。


13

尽管有合理的理由可以使用属性,但有一种常见的面向对象设计信念,即通过属性公开成员状态是不好的设计。罗伯特·马丁(Robert Martin)关于开放封闭原理的文章对此进行了扩展,指出“属性”鼓励耦合,因此限制了通过修改关闭类的能力-如果您修改该属性,则该类的所有使用者也将需要更改。他认为公开成员变量不一定是错误的设计,它可能只是糟糕的样式。但是,如果属性是只读的,则滥用和副作用的机会就更少。

我可以为单元测试提供的最佳方法(这似乎很奇怪)是使尽可能多的属性成为受保护的或内部的。这将防止耦合,同时阻止编写针对吸气剂和吸气剂的愚蠢测试。

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

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

  1. 您缺少间接使用属性的测试
  2. 属性未使用

如果编写用于getter和setter的测试,则会产生错误的覆盖感,并且无法确定功能行为是否真正使用了这些属性。


12

如果getter和/或setter 的圈复杂度为1(通常为1),则答案为否,您不应该这样做。

因此,除非您拥有要求100%代码覆盖率的SLA,否则请不要打扰,并专注于测试软件的重要方面。

PS请记住,即使在C#之类的属性似乎相同的语言中,也要区分getter和setter。设置器的复杂度可能会比吸气器高,从而验证单元测试。


4
尽管应该测试对吸气剂的防御性复制
雪橇

8

一个幽默而又明智的观点证言之道

“写下您今天能做的测试”

如果您是一位经验丰富的测试人员,并且这是一个小项目,那么测试吸气剂/吸气剂可能会显得过高。但是,如果您只是开始学习如何进行单元测试,或者这些获取器/设置器可能包含逻辑(例如@ArtB的setMom()示例),那么编写测试将是一个好主意。


1
您的链接实际上应该指向:证言的方式
JJS

2

实际上,这实际上是我和我团队之间的话题。我们拍摄了80%的代码。我的团队认为,getter和setter是自动实现的,并且编译器正在幕后生成一些基本代码。在这种情况下,由于生成的代码是非侵入性的,因此测试编译器为您创建的代码实际上没有任何意义。我们还讨论了异步方法,在这种情况下,编译器会在后台生成一堆代码。这是另一种情况,我们要进行测试。长话短说,将其与您的团队联系起来,并自己决定是否值得测试。

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


2

我认为代码覆盖率是查看是否错过了应覆盖的任何功能的好方法。

当您手动检查覆盖范围时,它的颜色很漂亮,因此可以说不需要对普通的吸气剂和吸气剂进行测试(尽管我总是这样做)。

当您仅检查项目上的代码覆盖率百分比时,像80%这样的测试覆盖率百分比就没有意义。您可以测试所有非逻辑部分,而忽略一些关键部分。在这种情况下,只有100%意味着您已经测试了所有重要代码(以及所有非逻辑代码)。一旦达到99.9%,您就会知道已经忘记了一些东西。

顺便说一句:代码覆盖率是检查您是否已经完全(单元)测试一个类的最终检查。但是100%的代码覆盖率并不一定意味着您已经实际测试了该类的所有功能。因此,单元测试应始终按照类的逻辑进行。最后,您运行覆盖范围以查看是否忘记了任何内容。当做得对时,您第一次击中100%。

还有一件事:最近在荷兰的一家大型银行工作时,我注意到Sonar表示100%的代码覆盖率。但是,我知道有些东西丢失了。如果检查每个文件的代码覆盖率百分比,则表明文件的百分比较低。整个代码库的百分比很大,以至于一个文件没有使该百分比显示为99.9%。所以您可能要注意这一点...


1

对JUnit代码本身实现的覆盖率进行了一些分析

一类未发现的代码是“测试起来太简单”。这包括简单的getter和setter,JUnit的开发人员不进行测试。

另一方面,JUnit没有任何方法(不建议使用)长于3行的任何测试都没有涵盖的方法。


1

我会说:是的getter / setter方法中的错误可能会悄悄地潜入并引起一些难看的错误。

我编写了一个lib程序,使此程序和其他一些测试更容易。您必须在JUnit测试中编写的唯一内容是:

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

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


0

是的,特别是如果要获取的项是抽象类的子类的对象。您的IDE可能会也可能不会提醒您某些属性尚未初始化。

然后一些显然不相关的测试以崩溃,NullPointerException您需要花费一些时间才能弄清楚gettable属性实际上根本就不存在。

尽管那仍然不会像在生产中发现问题那样糟糕。

确保所有抽象类都具有构造函数可能是一个好主意。如果不是这样,则对吸气剂的测试可能会提醒您那里存在问题。

至于基元的获取器和设置器,问题可能是:我正在测试程序还是在测试JVM或CLR?一般来说,不需要测试JVM。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.