如何为接口编写 junit 测试?

2022-08-31 12:01:28

为接口编写 junit 测试以便它们可以用于具体的实现类的最佳方法是什么?

例如,你有这个接口和实现类:

public interface MyInterface {
    /** Return the given value. */
    public boolean myMethod(boolean retVal);
}

public class MyClass1 implements MyInterface {
    public boolean myMethod(boolean retVal) {
        return retVal;
    }
}

public class MyClass2 implements MyInterface {
    public boolean myMethod(boolean retVal) {
        return retVal;
    }
}

您将如何针对接口编写测试,以便将其用于类?

可能性1:

public abstract class MyInterfaceTest {
    public abstract MyInterface createInstance();

    @Test
    public final void testMyMethod_True() {
        MyInterface instance = createInstance();
        assertTrue(instance.myMethod(true));
    }

    @Test
    public final void testMyMethod_False() {
        MyInterface instance = createInstance();
        assertFalse(instance.myMethod(false));
    }
}

public class MyClass1Test extends MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass1();
    }
}

public class MyClass2Test extends MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass2();
    }
}

优点:

  • 只需实现一种方法

缺点:

  • 对于所有测试,被测类的依赖关系和模拟对象必须相同

可能性2:

public abstract class MyInterfaceTest
    public void testMyMethod_True(MyInterface instance) {
        assertTrue(instance.myMethod(true));
    }

    public void testMyMethod_False(MyInterface instance) {
        assertFalse(instance.myMethod(false));
    }
}

public class MyClass1Test extends MyInterfaceTest {
    @Test
    public void testMyMethod_True() {
        MyClass1 instance = new MyClass1();
        super.testMyMethod_True(instance);
    }

    @Test
    public void testMyMethod_False() {
        MyClass1 instance = new MyClass1();
        super.testMyMethod_False(instance);
    }
}

public class MyClass2Test extends MyInterfaceTest {
    @Test
    public void testMyMethod_True() {
        MyClass1 instance = new MyClass2();
        super.testMyMethod_True(instance);
    }

    @Test
    public void testMyMethod_False() {
        MyClass1 instance = new MyClass2();
        super.testMyMethod_False(instance);
    }
}

优点:

  • 每个测试的精细粒度,包括依赖项和模拟对象

缺点:

  • 每个实现测试类都需要编写其他测试方法

您更喜欢哪种可能性,或者您使用哪种其他方式?


答案 1

与@dlev给出的经过大量投票的答案相反,有时像你建议的那样写一个测试是非常有用/需要的。类的公共 API(通过其接口表示)是要测试的最重要的东西。话虽如此,我不会使用您提到的任何一种方法,而是使用参数化测试,其中参数是要测试的实现:

@RunWith(Parameterized.class)
public class InterfaceTesting {
    public MyInterface myInterface;

    public InterfaceTesting(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    @Test
    public final void testMyMethod_True() {
        assertTrue(myInterface.myMethod(true));
    }

    @Test
    public final void testMyMethod_False() {
        assertFalse(myInterface.myMethod(false));
    }

    @Parameterized.Parameters
    public static Collection<Object[]> instancesToTest() {
        return Arrays.asList(
                    new Object[]{new MyClass1()},
                    new Object[]{new MyClass2()}
        );
    }
}

答案 2

我强烈反对@dlev。通常,编写使用接口的测试是一种非常好的做法。接口定义客户端和实现之间的协定。通常,您的所有实现都必须通过完全相同的测试。显然,每个实现都可以有自己的测试。

所以,我知道2解决方案。

  1. 使用使用接口的各种测试实现抽象测试用例。声明返回具体实例的抽象受保护方法。现在,根据需要多次继承此抽象类,以便对接口的每个实现进行,并相应地实现上述工厂方法。您也可以在此处添加更具体的测试。

  2. 使用测试套件


推荐