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

Hec*_*421 130 unit-testing

我们应该为我们的吸气剂和制定者编写测试还是过度杀伤?

ska*_*man 167

我会说不.

@Will说你应该瞄准100%的代码覆盖率,但在我看来这是一个危险的分心.您可以编写具有100%覆盖率的单元测试,但绝对没有测试.

单元测试用于以一种富有表现力和有意义的方式测试代码的行为,而getter/setter只是达到目的的手段.如果你测试使用getters/setters来实现测试"真实"功能的目标,那就足够了.

另一方面,如果你的getter和setter做的不仅仅是获取和设置(即它们是正确的复杂方法),那么是的,它们应该进行测试.但是,不要仅仅为了测试吸气剂或定位器来编写单元测试用例,这是浪费时间.

  • 瞄准100%代码覆盖是愚蠢的.您最终会覆盖未公开的代码和/或没有复杂性的代码.这样的事情最好留给自动生成,但即使是自动生成的测试也毫无意义.最好将重点转移到重要测试,如集成测试. (7认同)
  • @Claus:我同意除了关注集成测试之外的一点点.单元测试对于良好的设计和补充集成测试至关重要. (7认同)
  • 我认为在整个测试套件运行时100%的代码覆盖率是一个很好的目标.属性getter setter和其他代码作为较大测试的一部分运行.最后要讨论的部分可能是错误处理.如果单元测试没有涵盖错误处理,那么它将永远不会被覆盖.您真的想要发布包含从未运行过的代码的产品吗? (5认同)

Izh*_*aki 35

TDD响亮的YES


注意:这个答案不断得到提升,尽管可能是一个糟糕的建议.要了解原因,请看下面的小妹妹.


有争议的,但我认为任何对这个问题回答'不'的人都缺少TDD的基本概念.

对我来说,如果你遵循TDD ,答案是肯定的.如果你不是,那么不是一个看似合理的答案.

TDD中的DDD

TDD经常被引用为具有主要益处.

  • 防御
    • 确保代码可能会改变不会改变其行为.
    • 这允许重构的重要做法.
    • 你获得了这个TDD.
  • 设计
    • 您可以指定应该执行的操作,在实现之前应该如何操作.
    • 这通常意味着更明智的实施决策.
  • 文档
    • 测试套件应作为规范(要求)文档.
    • 为此目的使用测试意味着文档和实现始终处于一致状态 - 更改为一个意味着对其他更改.与在单独的word文档上保持要求和设计相比较.

将责任与执行分开

作为程序员,将属性视为重要的东西,将getter和setter视为某种开销是非常诱人的.

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

拼写对象应该更重要:

允许其客户更改其状态

允许其客户端查询其状态

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

一个测试,如

(The Painter class) should store the provided colour
Run Code Online (Sandbox Code Playgroud)

对TDD 的文档部分很重要.

当你编写测试时,你应该不知道最终实现是微不足道的(属性)并且没有防御好处这一事实.

缺乏往返工程......

系统开发领域的一个关键问题是缺乏 往返工程1 - 系统的开发过程被分割成脱节的子流程,其中的工件(文档,代码)经常不一致.

1 Brodie,Michael L."John Mylopoulos:缝合概念造型的种子." 概念建模:基础和应用.Springer Berlin Heidelberg,2009.1-9.

......以及TDD如何解决它

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

首先设计,稍后实施

在TDD中,我们首先编写失败的验收测试,然后编写允许它们通过的代码.

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

为什么要排除setter和getter?

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

所以问问自己:

为一个班级编写测试的人是否应该提到吸气剂和二传手.

由于getter和setter是类的公共接口,因此答案显然是肯定的,或者无法设置或查询对象的状态.但是,这样做的方法不一定是单独测试每个方法,请参阅我的其他答案.

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

  • 这只是硬币的一面。想想你可以做的事情,而不是仅仅为了测试而测试。正如 Kent Beck 所说,你为工作代码而不是工作测试获得报酬。 (2认同)

azh*_*kov 33

Roy Osherove在他的着名着作"单位测试的艺术"中说:

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

  • 考虑到测试这些所需的时间和行为被添加的可能性,我不明白为什么不这样做。我怀疑如果按下他可能会回答“好吧,我想为什么不”。 (2认同)
  • 重要的早期书籍往往有不好的建议。我见过很多这样的例子,人们快速地投入 getter 和 setter 并犯错误,因为他们正在剪切和粘贴或忘记这一点。或 this-> 或类似的东西。它们很容易测试,当然应该进行测试。 (2认同)

Sle*_*led 22

tl; dr: 是的,应该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 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 );
Run Code Online (Sandbox Code Playgroud)

  • 是的,您应该始终验证您的输入.如果你不是,为什么不只是使用一个公共变量?它变成了同样的事情.使用setter与变量的全部好处是,它允许您确保您的对象永远无效,例如age是负数.如果你不在对象中执行此操作,那么你将在其他地方执行此操作(例如"服务"[上帝对象](http://en.wikipedia.org/wiki/God_object))并且那不是真正的OOP点. (13认同)
  • 我知道这现在已经过时了,但是:你应该在你的吸气剂和制定者身上做验证吗?我的印象是setMom应该按照它说的做.如果它正在验证,那么它不应该是validateAndSetMom吗?或者更好的是,验证代码不应该存在于ELSEWHERE而不是简单对象吗?我错过了什么,这里? (9认同)

Izh*_*aki 17

是的,但并不总是孤立无援

请允许我详细说明:

什么是单元测试?

与遗留代码1 有效合作:

单元测试一词在软件开发方面有着悠久的历史.大多数单元测试概念的共同之处在于,它们是独立于软件各个组件的测试.什么是组件?定义各不相同,但在单元测试中,我们通常关注系统的大多数原子行为单元.在程序代码中,单元通常是功能.在面向对象的代码中,单元是类.

请注意,使用OOP,您可以找到getter和setter,单位是,不一定是单独的方法.

什么是好考试?

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

{P} C {Q}

哪里:

  • {P}是先决条件(给定)
  • C是触发条件(何时)
  • {Q}是后置条件(然后)

格言是:

测试行为,而不是实现

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

说到OOP,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 )
Run Code Online (Sandbox Code Playgroud)

虽然if setVAT会抛出异常,但相应的测试是合适的,因为现在一个外部效应.

你应该如何测试getter和setter?

如果这种变化对外界没有影响,即使后来出现这样的影响,也几乎没有必要改变对象的内部状态.

所以对二传手和吸气剂的测试应该关注这些方法的外部效应,而不是内部效应.

例如:

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 )
Run Code Online (Sandbox Code Playgroud)

你可能会想到自己:

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

但是,如果setVAT()测试失败,那么同样失败.

1 Feathers,M.,2004.有效地使用遗留代码.Prentice Hall专业.

2 Martin,RC,2009.清洁代码:敏捷软件工艺手册.皮尔逊教育.


bry*_*ook 13

虽然对于属性有合理的理由,但是有一个共同的面向对象设计的信念,即通过属性暴露成员状态是糟糕的设计.罗伯特马丁关于开放封闭原则的文章通过声明属性鼓励耦合并因此限制关闭类的修改能力来扩展这一点 - 如果修改属性,类的所有消费者也需要改变.他认为暴露成员变量不一定是糟糕的设计,它可能只是糟糕的风格.但是,如果属性是只读的,那么滥用和副作用的可能性就会降低.

我可以为单元测试提供的最佳方法(这可能看起来很奇怪)是尽可能多地保护或内部属性.这将阻止耦合,同时阻止为吸气剂和制定者编写愚蠢的测试.

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

更实际的是,单元测试应该通过公共方法驱动功能.如果您正在测试的代码恰好使用这些属性,则可以免费获得代码覆盖率.如果事实证明这些属性永远不会被代码覆盖突出显示,那么很可能:

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

如果您为getter和setter编写测试,则会产生错误的覆盖感,并且无法确定属性是否实际被功能行为使用.


Cla*_*sen 12

如果吸气剂和/或设定器的圈复杂度为1(它们通常是),那么答案是否定的,你不应该.

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

PS记住区分getter和setter,即使在像C#这样的语言中,属性可能看起来是一样的.setter复杂度可以高于getter,从而验证单元测试.

  • 虽然应该测试吸气剂的防御性复制 (4认同)

muy*_*moo 8

一个幽默但明智的作品:Testivus之路

"今天就写出你的测试"

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