我们应该为吸气剂和塞脂剂编写测试,还是过度杀伤力?
我们应该为吸气剂和塞脂剂编写测试,还是过度杀伤力?
Answers:
我会说不。
@Will表示您应该争取100%的代码覆盖率,但是我认为这是一种危险的干扰。您可以编写具有100%覆盖率的单元测试,但绝对不能进行任何测试。
单元测试可以用一种富有表现力和有意义的方式来测试代码的行为,而吸气剂/设置剂只是达到目的的一种手段。如果您使用getter / setter进行测试以实现其测试“真实”功能的目标,那么这已经足够了。
另一方面,如果您的getter和setter所做的不仅仅是获取和设置(即它们是适当的复杂方法),那么是的,应该对其进行测试。但是不要写一个单元测试用例只是为了测试一个或多个吸气剂,那是浪费时间。
注意:尽管可能会带来错误的建议,但此答案仍会不断增加。要了解原因,请看下面的小妹妹。
很有争议,但我认为,对此问题回答“否”的任何人都缺少TDD的基本概念。
对我来说,如果您遵循TDD ,答案是肯定的。如果不是,那么不合理的答案。
TDD通常被认为具有主要优点。
作为程序员,将属性视为重要的事物而将属性视为获取器和设置器是非常费力的。
但是属性是实现细节,而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中,我们首先编写失败的验收测试,然后再编写使他们通过的代码。
在更高级别的BDD中,我们首先编写方案,然后使它们通过。
为什么要排除二传手和吸气剂?
从理论上讲,在TDD中,一个人完全可以编写测试,而另一个人可以实现通过测试的代码。
因此,问问自己:
编写课程测试的人是否应该提及吸气剂和吸气剂。
由于getter和setter是类的公共接口,因此答案显然是yes,否则将无法设置或查询对象的状态。但是,执行此操作的方法不一定是分别测试每种方法,有关更多信息,请参见我的其他答案。
显然,如果您首先编写代码,答案可能不会那么明确。
tl; dr: 是的,应该,并且与OpenPojo无关紧要。
您应该在获取器和设置器中进行一些验证,以便您进行测试。例如,setMom(Person p)
不应允许将比自己年轻的人设置为母亲。
即使您现在不做任何事情,将来也很有可能,这对回归分析将是一个好选择。如果您想让准妈妈为null
您服务,则应进行测试,以备日后有人更改时进行测试,这将加强您的假设。
一个常见的错误是void setFoo( Object foo ){ foo = foo; }
应该在哪里void setFoo( Object foo ){ this.foo = foo; }
。(在第一种情况下,foo
正被写入到是参数 不所述foo
的上字段对象)。
如果要返回数组或集合,则应在返回之前测试getter是否将对传递给setter的数据进行防御性复制。
否则,如果您拥有最基本的设置器/获取器,则对每个对象进行单元测试最多可能会增加大约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 );
请允许我详细说明:
从有效使用旧代码1开始:
术语“单元测试”在软件开发中具有悠久的历史。大多数单元测试概念的共同点是,它们是隔离软件各个组件的测试。什么是成分?定义各不相同,但是在单元测试中,我们通常关心系统中最原子的行为单元。在过程代码中,单位通常是功能。在面向对象的代码中,单位是类。
请注意,在OOP中,您可以找到getter和setter,单位是类,而不一定是单个方法。
所有要求和测试都遵循Hoare逻辑的形式:
{P} C {Q}
哪里:
{P}
是前提(给定)C
是触发条件(当时){Q}
是后置条件(则)然后是格言:
测试行为,而不是实现
这意味着您不应该测试如何C
达到后置条件,而应该检查{Q}
的结果C
。
说到OOP,C
是一门课。因此,您不应测试内部效果,而只能测试外部效果。
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年。干净代码:敏捷软件工艺手册。培生教育。
尽管有合理的理由可以使用属性,但有一种常见的面向对象设计信念,即通过属性公开成员状态是不好的设计。罗伯特·马丁(Robert Martin)关于开放封闭原理的文章对此进行了扩展,指出“属性”鼓励耦合,因此限制了通过修改关闭类的能力-如果您修改该属性,则该类的所有使用者也将需要更改。他认为公开成员变量不一定是错误的设计,它可能只是糟糕的样式。但是,如果属性是只读的,则滥用和副作用的机会就更少。
我可以为单元测试提供的最佳方法(这似乎很奇怪)是使尽可能多的属性成为受保护的或内部的。这将防止耦合,同时阻止编写针对吸气剂和吸气剂的愚蠢测试。
有明显的原因应该使用读/写属性,例如绑定到输入字段的ViewModel属性等。
实际上,单元测试应该通过公共方法来驱动功能。如果您正在测试的代码碰巧使用了这些属性,那么您将免费获得代码覆盖率。如果事实证明这些属性从未被代码覆盖范围突出显示,则很有可能:
如果编写用于getter和setter的测试,则会产生错误的覆盖感,并且无法确定功能行为是否真正使用了这些属性。
实际上,这实际上是我和我团队之间的话题。我们拍摄了80%的代码。我的团队认为,getter和setter是自动实现的,并且编译器正在幕后生成一些基本代码。在这种情况下,由于生成的代码是非侵入性的,因此测试编译器为您创建的代码实际上没有任何意义。我们还讨论了异步方法,在这种情况下,编译器会在后台生成一堆代码。这是另一种情况,我们要进行测试。长话短说,将其与您的团队联系起来,并自己决定是否值得测试。
另外,如果您像我们一样使用代码覆盖率报告,您可以做的一件事就是添加[ExcludeFromCodeCoverage]属性。我们的解决方案是将其用于仅具有使用getter和setter的属性或属性本身的模型。这样,假设运行计算覆盖率报告的代码覆盖率百分比时,它不会影响总覆盖率%。测试愉快!
我认为代码覆盖率是查看是否错过了应覆盖的任何功能的好方法。
当您手动检查覆盖范围时,它的颜色很漂亮,因此可以说不需要对普通的吸气剂和吸气剂进行测试(尽管我总是这样做)。
当您仅检查项目上的代码覆盖率百分比时,像80%这样的测试覆盖率百分比就没有意义。您可以测试所有非逻辑部分,而忽略一些关键部分。在这种情况下,只有100%意味着您已经测试了所有重要代码(以及所有非逻辑代码)。一旦达到99.9%,您就会知道已经忘记了一些东西。
顺便说一句:代码覆盖率是检查您是否已经完全(单元)测试一个类的最终检查。但是100%的代码覆盖率并不一定意味着您已经实际测试了该类的所有功能。因此,单元测试应始终按照类的逻辑进行。最后,您运行覆盖范围以查看是否忘记了任何内容。当做得对时,您第一次击中100%。
还有一件事:最近在荷兰的一家大型银行工作时,我注意到Sonar表示100%的代码覆盖率。但是,我知道有些东西丢失了。如果检查每个文件的代码覆盖率百分比,则表明文件的百分比较低。整个代码库的百分比很大,以至于一个文件没有使该百分比显示为99.9%。所以您可能要注意这一点...
我对JUnit代码本身实现的覆盖率进行了一些分析。
一类未发现的代码是“测试起来太简单”。这包括简单的getter和setter,JUnit的开发人员不进行测试。
另一方面,JUnit没有任何方法(不建议使用)长于3行的任何测试都没有涵盖的方法。
我会说:是的getter / setter方法中的错误可能会悄悄地潜入并引起一些难看的错误。
我编写了一个lib程序,使此程序和其他一些测试更容易。您必须在JUnit测试中编写的唯一内容是:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));