我们是应该为我们的吸气剂和制定者编写测试还是过度杀伤?
答案 0 :(得分:156)
我会说不。
@Will说你应该瞄准100%的代码覆盖率,但在我看来这是一个危险的分心。你可以编写100%覆盖率的单元测试,但绝对没有测试。
单元测试用于以富有表现力和有意义的方式测试代码的行为,而getter / setter只是达到目的的手段。如果你测试使用getters / setter来实现测试“真实”功能的目标,那就足够了。
另一方面,如果你的getter和setter做的不仅仅是获取和设置(即它们是正确的复杂方法),那么是的,它们应该被测试。但是不要写一个单元测试用例只是来测试一个getter或setter,这是浪费时间。
答案 1 :(得分:34)
注意:这个答案不断得到提升,尽管可能是一个糟糕的建议。要了解原因,请查看下面的its little sister。
有争议的好吧,但我认为任何对这个问题回答'不'的人都缺少TDD的基本概念。
对我来说,如果您关注TDD,答案就是响亮的是。如果你不是,那么不是一个合理的答案。
作为程序员,将属性视为重要的东西,将getter和setter视为某种开销是非常诱人的。
但属性是一个实现细节,而setter和getter是实际使程序工作的契约接口。
拼写对象应该更为重要:
允许其客户更改其状态
和
允许其客户查询其状态
然后如何实际存储此状态(属性是最常见的,但不是唯一的方式)。
诸如
之类的测试(The Painter class) should store the provided colour
对于TDD的文档部分非常重要。
当你编写测试时,你应该不知道最终实现是微不足道的(属性)并且没有防御的好处。
系统开发领域的一个关键问题是缺乏往返工程 1 - 系统的开发过程分散为脱节的子流程其中的文件(文档,代码)经常不一致。
1 Brodie,Michael L.“John Mylopoulos:缝合概念造型的种子。”概念建模:基础和应用。 Springer Berlin Heidelberg,2009。1-9。
TDD的文档部分可确保系统规范及其代码始终保持一致。
在TDD中,我们首先编写失败的验收测试,然后编写允许它们通过的代码。
在更高级别的BDD中,我们首先编写场景,然后让它们通过。
为什么要排除setter和getter?
理论上,在TDD中完全可以让一个人编写测试,而另一个人则可以实现使其通过的代码。
所以问问自己:
为一个班级编写测试的人是否应该提到吸气剂和制定者。
由于getter和setter是类的公共接口,答案显然是是,否则将无法设置或查询对象的状态。 但,执行此操作的方法不一定是单独测试每个方法,有关详情,请参阅我的other answer。
显然,如果你先编写代码,答案可能就不那么明确了。
答案 2 :(得分:30)
属性(Java中的getter / setter)是通常不包含任何逻辑的代码的良好示例,并且不需要测试。但请注意:一旦在属性中添加任何检查,您将需要确保正在测试逻辑。
答案 3 :(得分:20)
tl; dr: 是你应该,而OpenPojo则是微不足道。
您应该在getter和setter中进行一些验证,以便进行测试。例如,setMom(Person p)
不应该允许任何比自己更年轻的人作为他们的母亲。
即使您现在没有做任何这种情况,将来也有可能,那么这对于回归分析会有好处。如果你想允许母亲设置为null
你应该对某人进行测试,如果有人稍后改变,这将加强你的假设。
一个常见的错误是void setFoo( Object foo ){ foo = foo; }
,它应该是void setFoo( Object foo ){ this.foo = foo; }
。 (在第一种情况下,正在写入的foo
是参数 而不是 对象上的foo
字段)。
如果您要返回一个数组或集合,那么您应该测试getter是否会在返回之前执行传递给setter的数据的defensive copies。
否则,如果您拥有最基本的setter / getter,那么对它们进行单元测试最多可能会增加每个对象大约10分钟,那么损失是多少?如果添加行为,您已经进行了骨架测试,并且您可以免费获得此回归测试。如果您使用的是Java,那么就没有任何借口,因为OpenPojo。您可以启用一组现有规则,然后使用它们扫描整个项目,以确保它们在您的代码中一致地应用。
来自examples:
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 );
答案 4 :(得分:15)
请允许我详细说明:
从有效地使用遗留代码 1 :
术语单元测试在软件开发方面有着悠久的历史。普遍于 大多数单元测试的概念是他们认为它们是孤立于个体的测试 软件组件。什么是组件?定义各不相同, 但在单元测试中,我们通常关注系统的大多数原子行为单元。在程序代码中,单元通常是功能。在面向对象的代码中,单位是类。
请注意,使用OOP,您可以找到getter和setter,单位是类,不一定是个别方法。
所有要求和测试均采用Hoare logic:
的形式{P} C {Q}
其中:
{P}
是前提条件(给)C
是触发条件(时){Q}
是后置条件(然后)然后是格言:
测试行为,而非实施
这意味着您不应该测试C
如何达到后置条件,您应该检查{Q}
是C
的结果。
说到OOP,C
是一个类。所以你不应该测试内部效应,只测试外部效应。
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
会抛出异常,相应的测试也是合适的,因为现在有外部效果。
如果这种变化对外界没有影响,那么改变对象的内部状态几乎没有任何意义,即使这种影响会在以后发生。
所以对二传手和吸气剂的测试应关注这些方法的外部效应,而不是内部效应。
例如:
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 Professional。
2 Martin,R.C.,2009。清洁代码:敏捷软件工艺手册。皮尔逊教育。
答案 5 :(得分:12)
虽然有合理的属性原因,但有一个共同的面向对象设计的信念,即通过属性暴露成员状态是糟糕的设计。 Robert Martin's article on the Open Closed Principle通过声明属性鼓励耦合并因此限制从修改中关闭类的能力来扩展这一点 - 如果修改属性,则类的所有使用者也需要更改。他认为暴露成员变量不一定是糟糕的设计,它可能只是糟糕的风格。但是,如果属性是只读的,那么滥用和副作用的可能性就会降低。
我可以为单元测试提供的最佳方法(这可能看起来很奇怪)是尽可能多地保护或内部属性。这将阻止耦合,同时阻止为吸气剂和制定者编写愚蠢的测试。
有明显的理由应该使用读/写属性,例如绑定到输入字段的ViewModel属性等。
更实际的是,单元测试应该通过公共方法驱动功能。如果您正在测试的代码恰好使用这些属性,则可以免费获得代码覆盖率。如果事实证明这些属性永远不会被代码覆盖突出显示,那么很有可能:
如果您为getter和setter编写测试,则会产生错误的覆盖感,并且无法确定属性是否实际被功能行为使用。
答案 6 :(得分:11)
如果getter和/或setter的cyclomatic complexity是1(他们通常是),那么答案是否定的,你不应该。
因此,除非您的SLA需要100%的代码覆盖率,否则请不要费心,并专注于测试软件的重要方面。
P.S。记住区分getter和setter,即使在像C#这样的语言中,属性可能看起来是一样的。 setter复杂度可能高于getter,因此验证单元测试。
答案 7 :(得分:6)
幽默但明智的做法:The Way of Testivus
“今天就写下你的测试”
如果您是一位经验丰富的测试员并且这是一个小项目,那么测试getter / setter可能会有点过分。但是,如果您刚刚开始学习如何进行单元测试,或者这些getter / setter可能包含逻辑(例如@ ArtB的setMom()
示例),那么编写测试将是一个好主意。
答案 8 :(得分:1)
这实际上是我和我的团队之间最近的话题。我们拍摄了80%的代码覆盖率。我的团队认为getter和setter是自动实现的,编译器在后台生成一些基本代码。在这种情况下,如果生成的代码是非侵入式的,那么测试编译器为您创建的代码并没有多大意义。我们也讨论过异步方法,在这种情况下,编译器会在幕后生成一大堆代码。这是一个不同的案例和我们要测试的东西。简短的回答,与你的团队一起提出,并自己决定是否值得测试。
此外,如果您使用像我们这样的代码覆盖率报告,您可以做的一件事是添加[ExcludeFromCodeCoverage]属性。我们的解决方案是将此用于仅具有使用getter和setter的属性的模型,或者用于属性本身。这样,在运行代码覆盖率报告时,它不会影响总代码覆盖率%,假设您正在使用它来计算代码覆盖率百分比。快乐的测试!
答案 9 :(得分:1)
在我看来,代码覆盖率是一种很好的方式,可以查看您是否错过了应该涵盖的任何功能。
当你手动检查覆盖范围时,它的颜色很漂亮,那么可以说普通的吸气剂和定位器不需要进行测试(尽管我总是这样做)。
当您只检查项目的代码覆盖百分比时,那么80%的测试覆盖百分比毫无意义。您可以测试所有非逻辑部分并忘记一些关键部分。在这种情况下,只有100%意味着您已经测试了所有重要代码(以及所有非逻辑代码)。只要99.9%,你就知道忘记了什么。
顺便说一句:代码覆盖率是最终检查,看你是否完全(单位)测试了一个类。但100%的代码覆盖率并不一定意味着您已经实际测试了该类的所有功能。因此,应始终按照类的逻辑实现单元测试。最后,你运行覆盖范围,看看你是否忘了什么。当你做得对,你第一次达到100%。
还有一件事:最近在荷兰的一家大银行工作时,我注意到Sonar表示100%的代码覆盖率。但是,我知道有些东西丢失了。检查每个文件的代码覆盖率百分比,它指示一个较低百分比的文件。整个代码库百分比很大,一个文件没有使百分比显示为99.9%。所以你可能想要注意这个......
答案 10 :(得分:1)
我会说:是的 getter / setter方法中的错误可能会悄悄地潜入并导致一些难看的错误。
我编写了一个lib来使此测试和其他一些测试更容易。 您必须在JUnit测试中编写的唯一内容是:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
答案 11 :(得分:0)
我做了一点analysis of the coverage achieved in the JUnit code itself。
一类未覆盖的代码“太简单,无法测试”。这包括简单的getter和setter,JUnit的开发人员不测试。
另一方面,JUnit没有任何(非弃用)方法超过任何测试未涵盖的3行。
答案 12 :(得分:0)
是的,尤其是如果要获取的项是从抽象类继承来的类的对象时。您的IDE可能会也可能不会提醒您某些属性尚未初始化。
然后,一些显然不相关的测试因NullPointerException
而崩溃,您需要花费一段时间才能弄清楚gettable属性实际上并不存在。
尽管如此,它仍然不会像在生产中发现问题那样糟糕。
确保所有抽象类都具有构造函数可能是一个好主意。如果没有,吸气剂的测试可能会提醒您那里的问题。
对于基元的获取器和设置器,问题可能是:我正在测试程序还是在测试JVM或CLR?一般来说,不需要对JVM进行测试。