Programing

인터페이스에 대한 junit 테스트를 작성하는 방법은 무엇입니까?

lottogame 2020. 11. 5. 07:37
반응형

인터페이스에 대한 junit 테스트를 작성하는 방법은 무엇입니까?


구체적인 구현 클래스에 사용할 수 있도록 인터페이스에 대한 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);
    }
}

찬성:

  • 종속성 및 모의 객체를 포함한 각 테스트에 대한 세분화

범죄자:

  • 각 구현 테스트 클래스는 추가 테스트 메서드를 작성해야합니다.

어떤 가능성을 선호하거나 다른 방법을 사용 하시겠습니까?


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

@dlev에 강력히 동의하지 않습니다. 인터페이스를 사용하는 테스트를 작성하는 것은 매우 좋은 방법입니다. 인터페이스는 클라이언트와 구현 간의 계약을 정의합니다. 매우 자주 모든 구현이 정확히 동일한 테스트를 통과해야합니다. 분명히 각 구현에는 자체 테스트가있을 수 있습니다.

그래서 저는 두 가지 해결책을 알고 있습니다.

  1. Implement abstract test case with various tests that use interface. Declare abstract protected method that returns concrete instance. Now inherit this abstract class as many times as you need for each implementation of your interface and implement the mentioned factory method accordingly. You can add more specific tests here as well.

  2. Use test suites.


I disagree with dlev as well, there's nothing wrong with writing your tests against interfaces instead of concrete implementations.

You probably want to use parameterized tests. Here is what it would look like with TestNG, it's a little more contrived with JUnit (since you can't pass parameters directly to test functions):

@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
}

Late addition to the subject, sharing newer solution insights

I'm also looking for a proper and efficient way of testing (based on JUnit) correctness of multiple implementations of some interfaces and abstract classes. Unfortunately, neither JUnit's @Parameterized tests nor TestNG's equivalent concept correctly fits my requirements, since I don't know a priori the list of implementations of these interface/abstract classes that might exists. That is, new implementations might be developped, and testers might not have access to all existing implementations; it is therefore not efficient to have test classes specify the list of implementation classes.

At this point, I have found the following project which seems to offer a complete and efficient solution to simplify this type of tests: https://github.com/Claudenw/junit-contracts . It basically allows the definition of "Contract Tests", through the annotation @Contract(InterfaceClass.class) on contract test classes. Then an implementer would create an implementation specific test class, with annotations @RunWith(ContractSuite.class) and @ContractImpl(value = ImplementationClass.class); the engine shall automatically apply any contract test that applies to ImplementationClass, by looking for all Contract Test defined for any interface or abstract class from which ImplementationClass derives. I have not yet tested this solution, but this sounds promising.

I have also found the following library: http://www.jqno.nl/equalsverifier/ . This one satisfies a similar though much more specific need, which is asserting a class conformity specifically to Object.equals and Object.hashcode contracts.

Similarly, https://bitbucket.org/chas678/testhelpers/src demonstrate a strategy to validate some Java fondamental contracts, including Object.equals, Object.hashcode, Comparable.compare, Serializable. This project use simple test structures, which, I believe, can be easily reproduced to suite any specific needs.

Well, that's it for now; I'll keep this post updated with other usefull informations I may find.


I would generally avoid writing unit tests against an interface, for the simple reason that an interface, however much you would like it to, does not define functionality. It encumbers its implementors with syntactic requirements, but that's it.

Unit tests, conversely, are intended to ensure that the functionality you expect is present in a given code path.

That being said, there are situations where this type of test could make sense. Assuming you wanted these tests to ensure that classes you wrote (that share a given interface) do, in fact, share the same functionality, then I would prefer your first option. It makes it easiest on the implementing subclasses to inject themselves into the testing process. Also, I don't think your "con" is really true. There's no reason you can't have the classes actually under test provide their own mocks (though I think that if you really need different mocks, then that suggests your interface tests aren't uniform anyway.)


with java 8 i do this

public interface MyInterfaceTest {
   public 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();
   };
}

참고URL : https://stackoverflow.com/questions/6724401/how-to-write-junit-tests-for-interfaces

반응형