我现在正在学习TDD。据我了解,私有方法是不可测试的,不应担心,因为公共API将提供足够的信息来验证对象的完整性。

我已经了解OOP了一段时间了。据我了解,私有方法使对象更易于封装,从而更能抵抗更改和错误。因此,默认情况下应使用它们,并且只有那些对客户端重要的方法才应公开。听他们的活动。这将是非常封装的,但完全不可测试。

此外,为了测试而添加方法也被认为是不好的做法。 ?适当的余额是多少?我现在倾向于公开大多数或所有方法...

评论

软件行业的错误实践和现实是不同的动物。在商业世界中,理想情况往往是扭曲的现实。做有意义的事情,并在整个应用程序中坚持下去。我宁愿采取一种不好的做法,也不愿在整个应用程序中分散当月的口味。

“私有方法不可测”?哪种语言?在某些语言中,这很不方便。在其他语言中,这非常简单。另外,您是在说封装的设计原理必须始终通过许多私有方法来实现吗?这似乎有点极端。有些语言还没有私有方法,但似乎仍然具有很好的封装设计。

“我的理解是,私有方法使对象更易于封装,从而更能抵抗更改和错误。因此,默认情况下应使用它们,并且只有那些对客户端重要的方法才应公开。”在我看来,这似乎与TDD试图实现的目标相反。 TDD是一种开发方法论,可引导您创建简单,可行和开放的设计。完全扭转了“从私人”和“仅公开...”的眼光。忘了有一种包含TDD的私有方法。稍后,根据需要进行操作;作为重构的一部分。

受保护的测试专用方法的可能重复

所以@gnat,您认为应该将其关闭,作为我对这个问题的回答中出现的问题的副本? * 8')

#1 楼

我宁愿对接口进行测试,也不愿对实现进行测试。

我的理解是私有方法不可测试

这取决于您的开发环境,请参见下文。

[私有方法]不必担心,因为公共API将提供足够的信息来验证对象的完整性。

是的,TDD专注于测试接口。
私有方法是一个实现细节,可以在任何重构周期内更改。应该可以在不改变界面或黑匣子行为的情况下进行重构。实际上,这是TDD好处的一部分,您可以轻松地产生信心,即班级内部的更改不会影响该班级的用户。

我有可能制作一个仅具有私有方法并通过侦听其他事件与其他对象进行交互的对象。

即使该类没有公共方法,它的事件处理程序也是它的公共接口,并且它与您可以测试的公共接口相对。 >由于事件是接口,因此它是您需要生成以测试该对象的事件。
研究使用模拟对象作为测试系统的粘合剂。应该可以创建一个简单的模拟对象,该对象生成一个事件并获取状态的变化(可能由另一个接收者模拟对象引起)。为了测试。

绝对,您应该非常警惕公开内部状态。

这是否意味着TDD与封装不一致?适当的平衡是什么?

绝对不是。
TDD不应更改类的实现,而应简化类(通过从较早的角度应用YAGNI)。 >使用TDD的最佳做法与不使用TDD的最佳做法相同,您只是找出原因,因为您在开发接口时就在使用该接口。

我倾向于使用大部分或全部现在公开方法...

这宁可将婴儿与洗澡水一起扔出去。
您不必公开所有方法,以便可以采用TDD方式开发。请参阅下面的注释,以查看您的私有方法是否真的不可测试。
更详细地介绍了测试私有方法的方法
如果您绝对必须对类的某些私有行为进行单元测试,具体取决于语言/环境,您可能有三个选择:

将测试放入要测试的类中。
将测试放入另一个类/源文件中,并将要测试的私有方法公开为公共方法。
使用一个测试环境,该环境允许您将测试代码与生产代码分开,但仍然允许测试代码访问生产代码的私有方法。

显然,第三个选择是最好。
1)将测试放在要测试的类中(不理想)。
将测试用例存储在与要测试的生产代码相同的类/源文件中是最简单的选择。但是,如果没有很多预处理器指令或注释,您最终将导致测试代码不必要地膨胀生产代码,并且取决于代码的结构方式,最终可能会意外地向代码用户公开内部实现。 br /> 2)将要测试的私有方法公开为公共方法或受保护的方法(真的不是一个好主意)。
建议这样做是一种很差的做法,破坏封装并将内部实现公开给代码用户。
3)使用更好的测试环境(最佳选择,如果有的话)
在Eclipse世界中,可以使用片段来实现3.。在C#世界中,我们可能使用部分类。其他语言/环境通常具有类似的功能,只需要找到它即可。
盲目的假设1.或2.是唯一的选择,很可能会导致生产软件充斥着测试代码或讨厌的类接口,从而冲洗掉它们在公共场合脏的亚麻布。 * 8')

总而言之-最好不要对私有实现进行测试。


评论


我不确定我会同意您建议的三个选项中的任何一个。我的选择是仅测试您之前所说的公共接口,但要确保这样做可以执行私有方法。这样做的部分优势是找到无效代码,如果您强迫测试代码破坏该语言的常规用法,则不太可能发生。

–魔术师
2014年3月26日18:23

您的方法应该做一件事,并且测试不应以任何方式考虑实现。私有方法是实现细节。如果仅测试公共方法意味着您的测试是集成测试,则说明您存在设计问题。

–魔术师
2014年3月31日在22:35

将方法设置为默认/受保护并在具有相同程序包的测试项目中创建测试该怎么办?

–RichardCypher
2015年2月27日在16:26

@RichardCypher这实际上与2)相同,因为您正在更改理想的方法规范,以适应测试环境中的不足,因此,实践仍然很差。

– Mark Booth
15年3月3日在20:07

我发现,将私有方法放在业务级别而不是抽象级别的接口/类中,可以实现很好的隔离和测试能力。它不会污染“公共”接口,因为这些接口是在高级别声明的。 (可选)您可以通过使用friend / internal将其锁定得更多,并且仅向一组明确的库公开,例如您的测试工具。

–马修·怀特(Matthew Whited)
20-10-27在12:17

#2 楼

当然,您可以有私有方法,当然也可以对其进行测试。在这种情况下,没有办法让私有程序运行:为什么要尝试测试它,只需删除该死的东西!

在您的示例中:


好吧,对于我来说,可以创建一个仅具有私有方法并通过侦听其他事件与其他对象进行交互的对象。这将是非常封装的,但却完全不可测试。


为什么那不可测试?如果方法是为了响应事件而调用的,则只需让测试为对象提供适当的事件即可。

这不是没有私有方法,而是要打破封装。您可以使用私有方法,但是应该通过公共API对其进行测试。如果公共API基于事件,则使用事件。

对于私有助手方法的更常见情况,可以通过调用它们的公共方法对其进行测试。特别是,由于只允许您编写代码以使失败的测试通过,并且您的测试正在测试公共API,因此您编写的所有新代码通常都将公开。私有方法仅在从现有公共方法中拔出时,才作为提取方法重构的结果出现。但是在那种情况下,测试公用方法的原始测试仍然涵盖了专用方法,因为公用方法调用了专用方法。已经测试了公共方法,因此也已经测试了。

评论


通过公共方法进行的测试在99%的时间内效果很好。挑战在于,当您的单个公共方法后面有数百或数千行复杂代码并且所有中间状态都特定于实现时,这种情况的发生率为1%。一旦变得足够复杂,尝试使用公共方法处理所有极端情况就变得很痛苦。或者,通过破坏封装并将更多方法公开为私有方法来测试边缘情况,或者通过使用kludge让测试调用私有方法来直接进行测试,这除了使丑陋之外还导致了脆弱的测试用例。

–丹在火光中摆弄
2012年2月14日在19:57

大型,复杂的私有方法是代码的味道。如此复杂的实现无法以有用的方式分解为组成部分(带有公共接口),是可测试性的问题,它揭示了潜在的设计和体系结构问题。在私有代码庞大的情况下,有1%的情况通常会受益于返工以分解和公开。

– S.Lott
2012年2月14日在20:06

@Dan Neely这样的代码无论如何都无法测试-编写单元测试的一部分指出了这一点。消除状态,分解类,应用所有典型的重构,然后编写单元测试。另外,使用TDD时,您是如何做到这一点的?这是TDD的优点之一,可测试代码的编写变得自动。

– Bill K
2012年2月14日在20:34

至少应该相当频繁地直接测试内部类中的内部方法或公共方法。幸运的是,.net支持InternalsVisibleToAttribute,但没有它,测试这些方法将是PITA。

– CodesInChaos
2012年2月15日在16:35

缺乏测试能力的原因在于,要测试或嵌套一个深层嵌套的调用要多少代码。尽管第二次私人通话可能(并且将会)给建立和维护带来极大的痛苦,但尝试测试第四个排列。小型平面类更易于测试和维护。

–马修·怀特(Matthew Whited)
20-10-27在12:20

#3 楼

在代码中创建新类时,您可以这样做来满足某些要求。要求说代码必须做什么,而不是怎么做。这使我们很容易理解为什么大多数测试都在公共方法级别进行。

通过测试,我们验证代码是否符合预期的工作,并在预期的情况下抛出适当的异常,等等。真正关心开发人员如何实现代码。虽然我们不关心实现,即代码如何执行其工作,但避免测试私有方法还是有道理的。

对于测试没有任何公共方法并与之交互的类外部世界只能通过事件进行测试,您也可以通过测试通过发送事件并侦听响应来进行测试。例如,如果某个类每次接收到一个事件都必须保存一个日志文件,则单元测试将发送该事件并验证是否已写入日志文件。

最后但并非最不重要的,在某些情况下,它非常适合测试私有方法。这就是为什么在.NET中,您不仅可以测试公共类,还可以测试私有类,即使解决方案不像公共方法那样简单。

评论


+1 TDD的一项重要功能是,它迫使您测试是否满足要求,而不是测试方法是否按照他们认为的去做。因此,“我可以测试私有方法”这个问题有点与TDD的精神背道而驰-问题可能是“我可以测试其实现包括私有方法的需求”。这个问题的答案显然是肯定的。

–达伍德·伊本·卡里姆(Dawood ibn Kareem)
2012年2月14日在21:04

#4 楼


我的理解是私有方法不可测试


我不同意该声明,或者我会说您不直接测试私有方法。公共方法可以调用不同的私有方法。也许作者想拥有“小型”方法,并将一些代码提取到一个巧妙命名的私有方法中。

无论如何编写公共方法,您的测试代码都应涵盖所有路径。如果在测试后发现测试中从未涉及一种私有方法中的一个分支语句(if / switch),那么您就有问题了。要么您错过了一个案例,要么实现是正确的,要么实现是错误的,并且该分支实际上不应该存在。这就是为什么我经常使用Cobertura和NCover来确保我的公共方法测试还涵盖私有方法。随意使用私有方法编写好的OO对象,并且在这种情况下不要让TDD / Testing陷入困境。

#5 楼

只要您使用依赖注入来提供与CUT进行交互的实例,您的示例就仍然可以完美测试。然后,您可以使用模拟程序,生成感兴趣的事件,然后观察CUT是否对其依赖项采取正确的操作。

另一方面,如果您的语言支持事件良好您可能会采取略有不同的方法。我不喜欢对象自己订阅事件,而是让创建对象的工厂将事件连接到对象的公共方法。它更易于测试,并且可以从外部看到CUT需要测试的事件类型。

评论


这是一个好主意-“ ...拥有创建对象的工厂,可以将事件与对象的公共方法联系起来。它更易于测试,并且可以从外部看到需要测试CUT的哪些事件。 ”

–小狗
2012-2-14在16:44



#6 楼

您无需放弃使用私有方法。使用它们是完全合理的,但是从测试的角度来看,在不破坏封装或向类中添加特定于测试的代码的情况下,很难直接对其进行测试。诀窍是尽量减少您知道会使蠕动的东西,因为您感觉自己已经弄脏了代码。可行的余额。


尽量减少使用的私有方法和属性的数量。无论如何,您需要课堂上完成的大多数工作都需要公开公开,因此请考虑一下是否确实需要将该聪明的方法设为私有。
最小化私有方法中的代码量-无论如何,您确实应该这样做-并通过其他方法的行为间接测试。您永远都不会期望获得100%的测试覆盖率,也许您将需要通过调试器手动检查一些值。使用私有方法引发异常可以很容易地间接测试。私有属性可能需要手动或通过另一种方法进行测试。
如果间接或手动检查不适合您,请添加一个受保护的事件,并通过一个接口进行访问以公开一些私有的东西。这有效地“弯曲了”封装规则,但避免了实际交付执行测试的代码的需要。缺点是,这可能会导致一些额外的内部代码,以确保在需要时将触发该事件。
如果您认为公共方法不够“安全”,请查看是否有方法可以在您的方法中实施某种验证过程以限制其使用方式。可能是,当您通过考虑一种更好的方法来实现您的方法的方式进行思考时,或者您会看到另一个类开始成形。
如果您有很多私有方法为您的公共方法做“工作”,则可能有一个新类正在等待提取。您可以将其作为一个单独的类直接进行测试,但可以在使用它的类中作为一个复合体来私有实现。使您的类变小,方法变小,并使用大量的组合。听起来需要做更多的工作,但最后您将获得更多可单独测试的项目,测试将变得更加简单,您将拥有更多使用简单模拟代替真实,大型和复杂对象的选项,希望可以-因数和松散耦合的代码,更重要的是,您将为自己提供更多选择。尽量减少内容的使用量最终会节省您的时间,因为减少了每个类需要单独检查的内容,并且自然地减少了当类变大且包含很多内容时有时会发生的代码错误内部相互依赖的代码行为。

#7 楼


好吧,我有可能创建一个仅具有私有方法的对象,并通过侦听其他对象的事件与其他对象进行交互。
这是非常封装的,但完全不可测试。 />

该对象如何应对这些事件?据推测,它必须在其他对象上调用方法。您可以通过检查是否调用了这些方法来进行测试。让它调用一个模拟对象,然后您可以轻松地断言它可以满足您的期望。我们不在乎对象内部发生了什么。所以不,您不应该再有其他公共方法。

#8 楼

我也为同样的问题而苦恼。确实,解决该问题的方法是:您如何期望程序的其余部分与该类交互?相应地测试您的课程。这将迫使您根据程序的其余部分如何与之交互来设计类,并且实际上将鼓励对类进行封装和良好的设计。

#9 楼

代替私人使用默认修饰符。然后,您可以单独测试这些方法,而不仅仅是与公共方法结合使用。这要求您的测试与主代码具有相同的包结构。

评论


...假设这是Java。

–达伍德·伊本·卡里姆(Dawood ibn Kareem)
2012年2月15日在8:18

或.net内部。

– CodesInChaos
2013年12月2日在18:19

#10 楼

一些私有方法通常不是问题。您只需通过公共API测试它们,就像将代码内联到您的公共方法中一样。过多的私人方法可能表明凝聚力不佳。您的班级应该承担一种凝聚力,并且人们经常将方法私有化,以在没有真正存在的情况下表现出凝聚力。

例如,您可能具有一个事件处理程序,该事件处理程序会响应这些事件进行大量数据库调用。由于实例化事件处理程序以进行数据库调用显然是不好的做法,因此诱惑是使所有与数据库相关的调用成为私有方法,而实际上应该将它们拉到单独的类中。

#11 楼


这是否意味着TDD与封装不一致?
适当的余额是多少?我现在倾向于公开大多数或所有方法。


TDD与封装没有冲突。以最简单的getter方法或属性为例,具体取决于您选择的语言。假设我有一个Customer对象,并且希望它具有一个ID字段。我要编写的第一个测试是说“ customer_id_initializes_to_zero”的内容。我定义吸气剂引发未实现的异常并观察测试失败。然后,我要做的最简单的事情就是让getter返回零。

从那里开始进行其他测试,大概是那些涉及客户ID的实际功能领域。在某个时候,我可能必须创建一个私有字段,客户类使用该私有字段来跟踪getter应该返回的内容。我到底该如何追踪?这是一个简单的支持int吗?我要跟踪一个字符串然后将其转换为int吗?我是否跟踪20个整数并取平均值?外部环境不在乎-您的TDD测试也不在乎。那是一个封装的细节。

我认为在启动TDD时,这并不总是立即显而易见的-您没有测试内部使用的方法-您正在测试的类的关注程度较小。因此,您并不是要测试DoSomethingToFoo()实例化Bar的方法,在其上调用一个方法,在其属性中添加两个的方法,等等。更改(或未更改)。那就是测试的一般模式:“当我对被测类进行X训练时,我随后可以观察到Y”。到达Y的方式与测试无关,这就是封装的原因,这就是TDD与封装没有矛盾的原因。

#12 楼

避免使用?否。
避免从头开始?是的。

我注意到您并没有询问使用TDD进行抽象类是否可以;如果您了解TDD期间抽象类是如何出现的,则相同的原理也适用于私有方法。

您不能像直接测试私有方法那样直接在抽象类中测试方法,但是这就是为什么您不从抽象类和私有方法开始的原因;您从具体的类和公共API开始,然后在进行过程中重构常用功能。

#13 楼

您的课程有要求。粗略地说,您的类应该具有与需求相对应的公共方法,而没有其他方法。

您的班级需要代码来实现要求。根据要求,可能需要很多代码。出于各种原因,您的方法不应太大。

如果您坚持不使用私有方法,那么所有代码​​都必须放入公共方法中,这显然是垃圾。因此,您需要添加一个拥有良好代码类的私有方法,而添加的私有方法的数量恰好与之相同。可测试性不是全部。实际上,如果您违反了良好的编码原则,并且最终得到的代码总是有太多错误而无法通过测试,那么可测试性就什么都不是。

方法是私有的,以防止每段旧代码调用它们。这并不意味着您不想测试它们。例如,在MacOS / iOS上,您只需将方法标记为@testable即可在构建单元测试或其他测试时将其称为公共方法,而在构建应用程序时则可以将其称为公共方法。