Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java TDD:我必须定义我的代码不应该做的所有事情吗?_Java_Unit Testing_Junit_Tdd - Fatal编程技术网

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()

话虽如此,这仍然属于“测试你的代码不应该做的事情”,我不太喜欢验证事情不会发生——这会使你的测试变得非常僵化。我看到的唯一例外是,如果一个方法对您的系统来说非常关键/危险,可以使用防御手段来确保它不会被调用,但是