Java TDD:我必须定义我的代码不应该做的所有事情吗?
问题 我正在使用测试驱动开发,在让测试充分定义代码方面遇到了困难。我的问题的一个简单例子如下 我有Java TDD:我必须定义我的代码不应该做的所有事情吗?,java,unit-testing,junit,tdd,Java,Unit Testing,Junit,Tdd,问题 我正在使用测试驱动开发,在让测试充分定义代码方面遇到了困难。我的问题的一个简单例子如下 我有MyObject,我想从中调用属于OtherObject的methodA()或methodB(),具体取决于MyObject在自己的callMethod(int)中接收的参数 预期代码(和预期功能) 这基本上就是我希望代码要做的-但我想先进行测试: public class MyObject { private final OtherObject otherObject; pub
MyObject
,我想从中调用属于OtherObject
的methodA()
或methodB()
,具体取决于MyObject
在自己的callMethod(int)
中接收的参数
预期代码(和预期功能)
这基本上就是我希望代码要做的-但我想先进行测试:
public class MyObject {
private final OtherObject otherObject;
public MyObject(OtherObject otherObject) {
this.otherObject = otherObject;
}
public void callMethod(int i) {
switch (i) {
case 0:
otherObject.methodA();
break;
case 1:
otherObject.methodB();
break;
}
}
}
先编写it测试
为了实现这一点,我首先编写一个测试,检查调用callMethod(0)
时是否调用了methodA()
。我使用JUnit和Mockito
public class MyObjectTest {
private final OtherObject mockOtherObject = mock(OtherObject.class);
private final MyObject myObject = new MyObject(mockOtherObject);
@Test
public void callsMethodA_WhenArgumentIs0() {
myObject.callMethod(0);
verify(mockOtherObject).methodA();
}
}
通过实现MyObject
的方法,我创建了消除错误并通过测试所需的类/方法,如下所示:
public void callMethod(int i) {
otherObject.methodA();
}
public class MyObject {
private final OtherObjectFactory factory;
public MyObject(OtherObjectFactory factory) {
this.factory = factory;
}
public void callMethod(int i) {
factory.createOtherObject(i).doSomething();
}
}
public abstract class OtherObject{
public abstract void doSomething();
}
public class OtherObjectFactory {
public OtherObject createOtherObject(int i){
switch (i) {
case 0:
return new MethodAImpl();
case 1:
return new MethodBImpl();
}
}
}
接下来是另一个选项的测试-调用callMethod(1)
我得到的最终解决方案是:
public void callMethod(int i) {
otherObject.methodA();
otherObject.methodB();
}
问题
这是可行的,但显然不是我想要的。如何使用测试实现所需的代码?在这里,我已经测试了我想要的行为。我能想到的唯一解决办法是为我不想看到的行为编写更多的测试
在本例中,可以再编写两个测试来检查另一个方法是否未被调用,但在一般情况下,这样做肯定会更麻烦。当有更多的选项时,根据具体情况调用哪些方法以及调用多少不同的方法会更加复杂
假设在我的示例中有3个方法-我是否需要编写3个测试来检查调用了正确的方法-然后如果我检查3个案例中的每一个都没有调用其他2个方法,则需要再编写6个测试?(无论您是否尝试在每个测试中坚持使用一个断言,您仍然必须全部编写它们。)
看起来测试的数量与代码的选项数成阶乘关系
另一种选择是只编写
if
或switch
语句,但从技术上讲,它不会由测试驱动。我认为您需要对代码进行稍微放大的查看。不要考虑它应该调用什么方法,而是考虑这些方法的总体效果
- 调用
的输出和副作用应该是什么李>callMethod(0)
- 调用
的输出和副作用应该是什么李>callMethod(1)
methodA
或methodB
来回答问题,而是用从外部可以看到的东西来回答问题。callMethod
应该返回什么(如果有)?callMethod
的调用方还可以看到哪些行为
如果
methodA
执行了callMethod
的调用方可以观察到的特殊操作,则将其包括在测试中。如果在callMethod(0)
发生时观察这种行为很重要,那么测试它。如果在调用方法(1)
发生时不观察这种行为是很重要的,那么也要进行测试。关于您的具体示例,我认为您做得完全正确。测试应该指定被测试类的行为。如果您需要指定您的类在某些情况下不执行某些操作,那么就这样做吧。在另一个例子中,这不会打扰您。例如,在此方法中检查这两个条件可能不会引起任何异议:
public void save(){
if(isDirty)
persistence.write(this);
}
在一般情况下,你又是对的。增加方法的复杂性会使TDD更加困难。意外的结果是,这是TDD最大的好处之一。如果您的测试是隐藏编写的,那么您的代码也太复杂了。这将很难推理,也很难维持。如果你听你的测试,你会考虑用简化测试的方式来改变设计。
在您的示例中,我可能不去管它(它非常简单)。但是,如果<代码>案例< /代码>的数量增长,我会考虑这样的变化:
public void callMethod(int i) {
otherObject.methodA();
}
public class MyObject {
private final OtherObjectFactory factory;
public MyObject(OtherObjectFactory factory) {
this.factory = factory;
}
public void callMethod(int i) {
factory.createOtherObject(i).doSomething();
}
}
public abstract class OtherObject{
public abstract void doSomething();
}
public class OtherObjectFactory {
public OtherObject createOtherObject(int i){
switch (i) {
case 0:
return new MethodAImpl();
case 1:
return new MethodBImpl();
}
}
}
请注意,此更改为您试图解决的问题增加了一些开销;有两种情况下,我不会为它费心。但是随着案例的增长,这种扩展非常好:您为OtherObjectFactory
添加了一个新的测试,并为OtherObject
添加了一个新的实现。您永远不会更改MyObject,也不会更改它的测试;它只有一个简单的测试。这也不是使测试更简单的唯一方法,这只是我想到的第一件事
总的来说,如果您的测试很复杂,并不意味着测试无效。好的测试和好的设计是一枚硬币的两面。测试需要一次咬掉一小块问题才能有效,就像代码需要一次解决一小块问题才能保持可维护性和内聚性一样。两只手互相洗手。好问题。将TDD应用于信函(尤其是像您这样使用Devil’s Advocate技巧)确实揭示了一些有趣的问题 马克·西曼(MarkSeemann)有一个类似的问题,他证明了使用一种不同的、稍微严格一点的模拟解决了这个问题。我不知道Mockito是否能做到这一点,但在Moq这样的框架中,在您的示例中,使
mockOtherObject
成为严格的mock会导致我们想要的异常,因为会调用未准备好的方法methodB()
话虽如此,这仍然属于“测试你的代码不应该做的事情”,我不太喜欢验证事情不会发生——这会使你的测试变得非常僵化。我看到的唯一例外是,如果一个方法对您的系统来说非常关键/危险,可以使用防御手段来确保它不会被调用,但是