如何编写接口的junit测试?

时间:2011-07-17 14:15:18

标签: java unit-testing testing interface junit

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

e.g。你有这个接口和实现类:

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);
    }
}

临:

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

缺点:

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

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

6 个答案:

答案 0 :(得分:71)

与@dlev给出的高得多的答案相反,编写像你建议的测试有时非常有用/需要。通过其接口表示的类的公共API是最重要的测试。话虽这么说,我不会使用你提到的方法,而是使用Parameterized测试,其中参数是要测试的实现:

@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()}
        );
    }
}

答案 1 :(得分:18)

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

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

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

  2. 使用test suites

答案 2 :(得分: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
}

答案 3 :(得分: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。这个项目使用简单的测试结构,我相信,它可以很容易地被复制以满足任何特定的需求。

嗯,现在就是这样;我将使用我可能找到的其他有用信息更新此帖子。

答案 4 :(得分:5)

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

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

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

答案 5 :(得分:1)

使用java 8我这样做

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

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

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

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

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

   @Disabled
   @Override
   @Test
   public void testMyMethod_True() {
       MyInterfaceTest.super.testMyMethod_True();
   };
}