在this thread中,Brian(唯一的回答者)说“你的代码应该以与测试无关的方式编写”
单个评论说“你的代码绝对不应该在全球范围内分支”我正在测试标志“。”。
但是没有给出理由,我会真的喜欢听到关于此事的一些理性的想法。这将是非常容易的(特别是考虑到许多测试具有对app类的包私有访问这一事实)以进入给定的app类并设置一个布尔值来表示“这是一个测试,而不是一个运行”。 / p>
我发现自己跳过篮球(注入模拟的私人田地等)以实现的各种事情可能变得更容易实现。
同样显而易见的是,如果你把它拿得太远就可能是灾难性的......但作为软件测试军械库中许多人的一个工具,为什么这个概念会遇到这样的耻辱?
回答Mick Mnemonic:
如果您实际在方法中间创建一个新的类实例并将其分配给私有字段,那么这可能有用的一个简单示例:私有字段模拟在这种情况下将无济于事,因为您是取代私人领域。但实际上创建一个真实的对象可能会非常昂贵:您可能希望在测试时将其替换为轻量级版本。
我昨天遇到过这种情况,实际上......我的解决方案是创建一个名为createXXX()的新的包私有方法...所以我可以嘲笑它。但这又违背了格言“你不能创造出适合你测试的方法”!
答案 0 :(得分:3)
想想大众汽车的大丑闻。在测试中表现出与在生产负载下不同的系统并未真正测试。那就是:它实际上是两个系统,生产系统和测试系统 - 其中唯一被测试的是测试系统。生产系统不同,未经过测试。您在两个系统之间引入的行为的每个差异都是一个测试漏洞。
答案 1 :(得分:2)
许多测试都具有对应用类的包私有访问权限
我建议反对这一点,在生产代码中打破封装的想法感觉就像尾巴摇尾巴给我。它表明这些课程太大和/或缺乏凝聚力。 TDD,依赖注入/控制反转,嘲弄和编写单一责任类应该不再需要放松可见性。
单个评论说“你的代码绝对不应该在全球范围内分支”我正在测试标志“。”。
生产代码是生产代码,无需了解您的测试。在那里应该没有关于测试的逻辑,它的分离很差。同样,依赖注入/控制反转将允许您在运行时交换测试特定逻辑,这将不包含在生产工件中。
答案 2 :(得分:2)
我将把这个答案分成两部分。首先,我将分享我对Brian的答案的看法,然后我将分享一些有关如何有效测试的提示。
Brian似乎暗示了两个关键想法。我将逐一解决每一个问题。
您的代码应该以与测试无关的方式编写。
生产代码不依赖于测试。应该是相反的。
这有多种原因:
注意:任何体面的编译器都会删除测试代码。虽然我不认为这是设计/测试系统设计不佳的借口。
您测试的环境应尽可能接近现实世界。
像听起来就像Brian在他的回答中暗示这个想法一样。与上一个想法不同,这个想法并没有得到普遍的认同,所以要把它当作一粒盐。
通过测试抽象,您可以提高对被测单元的尊重程度。你同意你不与其内部人员相处并窥探其内部状态。
为什么我不应该在测试期间监视对象的状态?
通过侦察物体内部,你会造成这些问题:
您的测试将您绑定到某个单元的特定实施。
例如......
想要更改您的班级以使用不同的排序算法?太糟糕了,您的测试将失败,因为您已声明必须1>}函数
您将打破封装。
通过测试对象的内部状态,您可能会放松该对象的一些隐私。这意味着您的生产代码中的更多代码也可以提高对象的可见性。
通过放松对象的封装,您会诱使其他生产代码也依赖于它。这不仅可以将测试绑定到特定的实现,还可以绑定整个系统本身。你不希望这种情况发生。
然后我怎么知道班级是否有效?
测试被调用方法的前置条件和后置条件/结果。如果您需要更复杂的测试,请查看我在模拟和依赖注入上写的最后一节。
我认为,只要您的生产代码与您的测试无关,您的主要方法中quicksort
一定不好。
例如:
if (TEST_MODE)
但是,如果您的其他类知道他们在测试模式下运行,则会出现问题。如果您在所有生产代码中都有public class Startup {
private static final boolean TEST_MODE = false;
public static void main(String[] args) {
if (TEST_MODE) {
TestSuite testSuite = new TestSuite();
testSuite.execute();
} else {
Main main = new Main();
main.execute();
}
}
}
,那么您就可以解决我上面提到的问题。
显然在Java中你会使用像JUnit或TestNG这样的东西而不是这个,但我只想分享我对if (TEST_MODE)
想法的看法。
这是一个非常大的话题,所以我将保留这部分答案。
而不是监视内部状态,使用模拟和依赖注入。
使用模拟,你可以断言已经调用了你注入的模拟方法。更好的是,依赖注入会颠覆你的课程。依赖于你注射的任何东西的实施。这意味着您可以更换不同的实现,而无需担心。
这完全消除了在课堂内徘徊的需要。
如果有一本书我强烈建议阅读,那么 Jeff Langr 将是使用测试驱动开发的现代C ++编程。它可能是我用过的最好的TDD资源。
尽管标题中有C ++,但其主要焦点仍然是TDD。本书的介绍讨论了这些示例如何适用于所有(类似)语言。鲍勃叔叔甚至在前言中说明了这一点:
您是否需要成为C ++程序员才能理解它?当然你不是。 C ++代码非常干净,编写得非常好,概念非常清晰,任何Java,C#,C甚至Ruby程序员都不会遇到任何麻烦。
答案 3 :(得分:0)
1)Carl Manaster带来了一个优秀而简短的答案。如果您的实现根据测试的具有不同的行为,则您的测试没有价值,因为它不能反映应用程序在生产中的实际行为,因此它不会验证要求。TDD:为什么让应用代码知道它正在测试中为什么会出错? 不跑?
2)测试驱动开发与让app代码知道它正在测试的事实无关。无论您使用何种开发方法,都可能会引入此类错误。
凭借我的TDD经验,我认为TDD阻止让应用程序代码知道它正在测试中,因为当您在第一个意图中编写单元测试并且适当地执行它时,您可以保证拥有可自然测试的应用程序代码验证应用程序要求,并且不了解已测试的代码。
我想相反,当您在编写应用程序代码后创建测试代码时,更可能发生这种错误,因为您可能不想重构应用程序代码以使代码可测试,因此添加一些技巧实现绕过重构任务。
3)测试驱动开发是可以工作的代码,但是当你使用它时,你不能忘记你的应用程序类和测试类的设计方面。
如果你真的有这个可能有帮助的一个简单的例子 在方法中间创建新的类实例并进行分配 它属于一个私人领域:在这种情况下,私人领域的模拟将无济于事 因为你正在取代私人领域。但实际创建一个 真实对象可能非常昂贵:您可能希望用a替换它 测试时的轻量级版本。
我昨天遇到过这种情况,事实上......和我的解决方案 是创建一个名为createXXX()的新的包私有方法...所以我 可以嘲笑它。但这反过来又违背了“你要这么说 不是为了适合你的测试而创建方法“!
在某些情况下,使用package-private修饰符是可以接受的,但只有在设计代码的所有自然方式都不允许具有可接受的解决方案时才应该使用它。
“你不能创造适合你的测试的方法” 可能会产生误导。
事实上,我会说:“你不应该创建适合你的测试的方法,并以不合需要的方式打开应用程序的API”
在您的示例中,当您想要修改您希望在测试期间模拟或替换依赖项的代码的依赖项时,如果您练习TDD,则不应直接修改实现,而应通过测试代码开始修改。登记/>
如果您测试代码似乎被阻止,因为您错过了构造函数,方法,对象等...为您的测试类设置依赖关系,您将被迫添加到您的测试类中。
它是TDD方式。
上面,我提到不要超过需要打开API。
我将提供两个示例,提供一种设置依赖关系但不以相同方式打开API的方法。
这种做法是可取的,因为客户端无法在生产中改变MyClass的行为:
@Service
public class MyClass{
...
MyDependency myDependency;
...
@Autowired
public MyClass(MyDependency myDependency){
this.myDependency = myDependency;
}
...
}
这种做法不太理想,因为MyClass API增长而应用程序代码不需要它。除了这个新方法,客户端还可以使用myDependency字段的setter来改变生产中MyClass的行为:
@Service
public class MyClass{
...
MyDependency myDependency;
...
@Autowired
public void setMyDependency(MyDependency myDependency){
this.myDependency = myDependency;
}
...
}
请注意:如果构造函数中包含4个或5个以上的参数,则使用它可能会很麻烦。
如果它发生,使用setter仍然可能不是最好的解决方案,因为问题的根源可能是该类有太多的责任。所以如果是这样的话就应该重构。
答案 4 :(得分:0)
我非常仔细地阅读了所有这些答案,它们都很有帮助。但也许我应该重新归类为自己:我似乎正在成为一个低中级TDD从业者,而不是一个新手。我已经吸收了很多这些观点和经验法则,无论是通过阅读还是有时令人费解,在过去6个月左右的时候偶尔会有苦涩但总是有启发性的经验。
卡尔·曼纳斯特与大众汽车丑闻的比喻很诱人,但稍微不适用,或许:我并不是说应用程序代码应该"检测"测试正在发生并改变其行为。我 建议的是,有一两个棘手的,令人讨厌的低级别问题,您可能希望以不会以任何方式干扰演员的方式使用此工具铁规则"哲学" TDD。
两个例子:
我的代码中有一些情况会抛出异常,并且测试我想要检查它们的位置。好的:我去了doThrow( ... )
和@Test( expected = ... )
,一切正常。但是在生产运行期间,我希望使用堆栈跟踪打印出错误消息。在测试运行期间,我只想要错误消息。我不希望logback-test.xml
完全取消错误级别的日志记录。但显然没有办法配置记录器以防止打印出堆栈跟踪。
所以我可以做的是在应用程序代码中使用这样的方法,仅用于测试:
boolean suppressStacktrace(){ return false; };
...然后我将其用作给定LOGGER.error( ...
情况的测试,然后在我想在测试期间激发该异常时模拟该方法返回true
。
其次,控制台输入的特定情况:BufferedReader.readLine()
。将另一个InputStream
替换为System.in
并使用List
替换Strings
每readLine
一次private
,这将是一个正确的痛苦。我所做的是在app类中有一个Deque<String> inputLinesDeque;
字段:
package-private
...以及一个List<String>
方法,可以使用pop
输入行设置此值,然后可以Deque
直到Deque
为空。在应用运行期间,此null
为if
,因此br.readline()
分支到logback
。
这些只是两个例子。毫无疑问,在其他情况下,超纯粹主义的方法价格过高,并且可以说没有真正的好处。
但是,我很欣赏davidxxx对TDD 10诫命之一的高级定义:&#34;您不能创建适合您的测试的方法,并且不会以不合需要的方式打开应用程序的API。 。非常有帮助:值得深思。
<强> 后 强>
自从一个月前写这篇文章以来,我已经意识到,扩展和修改logback
课程是不可能的......我认为制作你的课程并不困难自己的logback-test.xml
类确实会接受logback
中的配置标志,以及#34;抑制堆栈跟踪&#34;。当然,当你制作一个应用程序的可执行jar时,这个定制的<html>
类不会被导出...但是再一次,对我来说,这就属于&#34;跳过箍和&#34; #34 ;.如何&#34;纯粹&#34;应用程序代码真的需要吗?