如何编写接口的junit测试?

Xen*_*pus 69 java testing junit unit-testing interface

为接口编写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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您如何针对界面编写测试,以便将其用于课程?

可能性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();
    }
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 只需要实现一种方法

缺点:

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

可能性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);
    }
}
Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点:

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

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

Rya*_*art 73

与@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()}
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果每个测试方法都需要一个新的fixture实例,那么让参数方法返回一个工厂,每个测试调用它来获取它的夹具.它不会影响这种方法的可行性. (12认同)
  • 这种方法似乎存在问题.MyClass1和MyClass2的相同实例用于执行所有测试方法.理想情况下,每个testMethod都应该使用MyClass1/MyClass2的新实例执行,这个缺点使得这种方法无法使用. (4认同)
  • 假设我编写了一个接口,提供了一些实现和一个像这样编写的测试.如果用户创建新实现并想要测试它,则必须修改测试的源代码.这使我认为返回测试实例的抽象方法更有用,特别是如果您希望客户创建自己的实现. (4认同)
  • @ArcanisCz:与预告片评论者一样,*如何获得测试实例并不重要.重要的是,某种参数化测试可能是正确的方法. (2认同)

Ale*_*exR 18

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

所以,我知道2个解决方案.

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

  2. 使用测试套件.


Ced*_*ust 14

我也不同意dlev,对接口而不是具体实现编写测试没有任何问题.

您可能想要使用参数化测试.以下是使用TestNG的样子,使用JUnit更加节省(因为你不能直接将参数传递给测试函数):

@DataProvider
public Object[][] dp() {
  return new Object[][] {
    new Object[] { new MyImpl1() },
    new Object[] { new MyImpl2() },
  }
}

@Test(dataProvider = "dp")
public void f(MyInterface itf) {
  // will be called, with a different implementation each time
}
Run Code Online (Sandbox Code Playgroud)


jwa*_*ins 10

该主题的后期添加,分享更新的解决方案见解

我也在寻找一种适当而有效的测试方法(基于JUnit)一些接口和抽象类的多个实现的正确性.不幸的是,JUnit的@Parameterized测试,也不TestNG的等价概念正确符合我的要求,因为我不知道先验的存在可能这些接口/抽象类的实现的列表.也就是说,可能会开发新的实现,测试人员可能无法访问所有现有的实现; 因此,让测试类指定实现类列表是没有效率的.

在这一点上,我发现以下项目似乎提供了一个完整而有效的解决方案来简化这种类型的测试:https://github.com/Claudenw/junit-contracts.它基本上允许通过@Contract(InterfaceClass.class)合同测试类的注释来定义"合同测试" .然后,实现者将创建一个特定于实现的测试类,带有注释@RunWith(ContractSuite.class)@ContractImpl(value = ImplementationClass.class); 引擎应自动应用任何适用于ImplementationClass的合同测试,方法是查找为ImplementationClass派生的任何接口或抽象类定义的所有合同测试.我还没有测试过这个解决方案,但这听起来很有希望.

我还找到了以下库:http://www.jqno.nl/equalsverifier/.这个满足了类似但更具体的需求,它特别声明了Object.equals和Object.hashcode契约的类一致性.

同样,https: //bitbucket.org/chas678/testhelpers/src演示了验证一些Java fondamental合同的策略,包括Object.equals,Object.hashcode,Comparable.compare,Serializable.这个项目使用简单的测试结构,我相信,它可以很容易地被复制以满足任何特定需求.

那就是现在呢; 我将使用我可能找到的其他有用信息更新此帖子.


dle*_*lev 5

我通常会避免针对接口编写单元测试,原因很简单,接口,无论你想要多少,都没有定义功能.它使用语法要求阻碍了它的实现者,但就是这样.

相反,单元测试旨在确保您期望的功能存在于给定的代码路径中.

话虽这么说,但有些情况下这种类型的测试才有意义.假设您希望这些测试确保您编写的类(共享给定接口)实际上共享相同的功能,那么我更希望您的第一个选项.它使实现子类最容易将自己注入到测试过程中.另外,我不认为你的"骗局"是真的.没有理由你不能让真正受测试的类提供他们自己的模拟(虽然我认为如果你真的需要不同的模拟,那么这表明你的接口测试无论如何都不统一.)

  • 接口不定义功能,但它定义了其实现必须遵守的API.这是测试应该关注的内容,那么为什么不写一个测试来表达呢?特别是当您通过多个接口实现充分利用多态性时,这种测试非常有价值. (24认同)
  • @Ryan - 一个接口契约很少"只是一个约定".合同传达了公司对接口接收者的期望.编译器可能允许违反合同,但在运行时经常会导致意外行为.单位测试中该合同的单一定义优于多重. (5认同)
  • 我将通过接口测试仅作为合同一部分的功能,例如:当item第一次添加**时,**size**应该变为1,无论使用哪种实现,这个不变量应该成立.但是,实现可能需要进行其他测试,这些测试仅针对该实现测试特定功能. (5认同)
  • 我同意dlev - 我没有看到测试界面的重点.编译器会告诉您具体实现是否无法实现接口.我认为没有任何价值.单元测试适用于具体类. (2认同)