Testing multiple interface implementations with sa

2019-04-22 17:48发布

I want to run the same JUnit tests for different interface implementations. I found a nice solution with the @Parameter option:

public class InterfaceTest{

  MyInterface interface;

  public InterfaceTest(MyInterface interface) {
    this.interface = interface;
  }

  @Parameters
  public static Collection<Object[]> getParameters()
  {
    return Arrays.asList(new Object[][] {
      { new GoodInterfaceImpl() },
      { new AnotherInterfaceImpl() }
    });
  }
}

This test would be run twice, first with the GoodInterfaceImpl then with the AnotherInterfaceImpl class. But the problem is I need for most of the testcases a new object. A simplified example:

@Test
public void isEmptyTest(){
   assertTrue(interface.isEmpty());
}

@Test
public void insertTest(){
   interface.insert(new Object());
   assertFalse(interface.isEmpty());
}

If the isEmptyTest is run after the insertTest it fails.

Is there an option to run automatically each testcase with a new instance of an implementation?

BTW: Implementing a clear() or reset()-method for the interface is not really an options since I would not need it in productive code.

标签: junit4
4条回答
叼着烟拽天下
2楼-- · 2019-04-22 18:21

Just in case somebody reaches here(like I did), looking for testing multiple implementations of the same interface in .net you could see one of the approaches that I was using in one of the projects here

Below is what we are following in short

The same test project dll is run twice using vstest.console, by setting an environment variable. Inside the test, (either in the assembly initialize or test initialize) register the appropriate implementations into a IoC container, based on the environment variable value.

查看更多
Evening l夕情丶
3楼-- · 2019-04-22 18:26

Create a factory interface and implementations, possibly only in your test hierarchy if you don't need such a thing in production, and make getParameters() return a list of factories.

Then you can invoke the factory in a @Before annotated method to get a new instance of your actual class under test for each test method run.

查看更多
干净又极端
4楼-- · 2019-04-22 18:28

Here is another approach with the Template Method pattern:

The interface-oriented tests go into the base class:

public abstract class MyInterfaceTest {

    private MyInterface myInterface;

    protected abstract MyInterface makeContractSubject();

    @Before
    public void setUp() {
        myInterface = makeContractSubject();
    }

    @Test
    public void isEmptyTest(){
        assertTrue(myInterface.isEmpty());
    }

    @Test
    public void insertTest(){
        myInterface.insert(new Object());
        assertFalse(myInterface.isEmpty());
    }
}

For each concrete class, define a concrete test class:

public class GoodInterfaceImplTest extends MyInterfaceTest {

    @Override
    protected MyInterface makeContractSubject() {
        // initialize new GoodInterfaceImpl
        // insert proper stubs
        return ...;
    }

    @Test
    public void additionalImplementationSpecificStuff() {
        ...
    }
}

A slight advantage over @Parameter is that you get the name of the concrete test class reported when a test fails, so you know right away which implementation failed.

Btw, in order for this approach to work at all, the interface must be designed in a way which allows testing by the interface methods only. This implies state-based testing -- you cannot verify mocks in the base test class. If you need to verify mocks in implementation-specific tests, these tests must go into the concrete test classes.

查看更多
Evening l夕情丶
5楼-- · 2019-04-22 18:35

In Junit 5 you could do:

@ParameterizedTest
@MethodSource("myInterfaceProvider")
void test(MyInterface myInterface) {}

static Stream<MyInterface> myInterfaceProvider() {
   return Stream.of(new ImplA(), new ImplB());
}

interface MyInterface {}

static class ImplA implements MyInterface {}

static class ImplB implements MyInterface {}
查看更多
登录 后发表回答