java 如何在Junit5中测试接口的不同实现而不复制代码

5ssjco0h  于 2023-02-20  发布在  Java
关注(0)|答案(3)|浏览(127)

请问如何为具有不同实现的接口编写junit 5测试?
例如,我有一个接口Solution,有不同的实现,如SolutionISolutionII,我可以只编写一个测试类来测试这两个吗?
有一个post显示了一个例子,但是如果有多个测试方法需要调用,我必须为每个测试方法传递参数。
请问有没有像Junit4那样的优雅方式
在Junit4中,我有一个非常优雅的代码示例,如下所示

@RunWith(Parameterized.class)
public class SolutionTest {
  private Solution solution;

  public SolutionTest(Solution solution) {
    this.solution = solution;
  }

  @Parameterized.Parameters
  public static Collection<Object[]> getParameters() {
    return Arrays.asList(new Object[][]{
        {new SolutionI()},
        {new SolutionII()}
    });
  }
  // normal test methods
  @Test
  public void testMethod1() {

  }
}

junit5声称ExtendWith()是类似的,我尝试了下面的代码

@ExtendWith(SolutionTest.SolutionProvider.class)
public class SolutionTest {
  private Solution solution;

  public SolutionTest(Solution solution) {
    System.out.println("Call constructor");
    this.solution = solution;
  }

  @Test
  public void testOnlineCase1() {
    assertEquals(19, solution.testMethod(10));
  }

  @Test
  public void testOnlineCase2() {
    assertEquals(118, solution.testMethod(100));
  }

  static class SolutionProvider implements ParameterResolver {
    private final Solution[] solutions = {
        new SolutionI(),
        new SolutionII()
    };
    private static int i = 0;

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
      return parameterContext.getParameter().getType() == Solution.class;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
      System.out.println(i);
      return solutions[i++];
    }
  }
}

不幸的是,testMethod1使用了SolutionItestMethod2使用了SolutionII,这是有道理的,我不知道这是否有助于激发一点灵感。
先谢谢你的帮助

ulydmbyx

ulydmbyx1#

Jupiter提供完全符合您的目的的测试接口-测试接口合同
例如,让我们有一个字符串诊断契约的接口,以及两个遵循该契约但利用不同实现思想的实现:

public interface StringDiagnose {
    /**
     * Contract: a string is blank iff it consists of whitespace chars only
     * */
    boolean isTheStringBlank(String string);
}

public class DefaultDiagnose implements StringDiagnose {

    @Override
    public boolean isTheStringBlank(String string) {
        return string.trim().length() == 0;
    }
}

public class StreamBasedDiagnose implements StringDiagnose {

    @Override
    public boolean isTheStringBlank(String string) {
        return string.chars().allMatch(Character::isWhitespace);
    }
}

根据推荐的方法,您将创建 * 测试接口 *,用于验证default方法中的诊断 * 契约 *,并将依赖于实现的部分暴露给钩子:

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;

public interface StringDiagnoseTest<T extends StringDiagnose> {

    T createDiagnose();

    @Test
    default void blankCheckFollowsContract(){
        assertTrue(createDiagnose().isTheStringBlank("\t\n "));
        assertFalse(createDiagnose().isTheStringBlank("\t\n !  \r\n"));
    }
}

然后针对每个特定解决方案实施此“测试接口”:

class DefaultDiagnoseTest implements StringDiagnoseTest<DefaultDiagnose> {

    @Override
    public DefaultDiagnose createDiagnose() {
        return new DefaultDiagnose();
    }
}

class StreamBasedDiagnoseTest implements StringDiagnoseTest<StreamBasedDiagnose> {

    @Override
    public StreamBasedDiagnose createDiagnose() {
        return new StreamBasedDiagnose();
    }
}

使用更多的钩子而不是-default接口方法来测试同名解决方案的各个方面(如性能),并在接口实现中定义新的测试,以实现完全不同的特性。

jfgube3f

jfgube3f2#

很抱歉有一段时间没有回复这个帖子了。和楼主的回答相比,我发现了我目前正在采用的一些其他方法:

@ParameterizedTest
  @MethodSource("solutionStream")
  void testCase(Solution solution) {
   // add your test
  }

  static Stream<Solution> solutionStream() {
    return Stream.of(
        new SolutionI(),
        new SolutionII()
    );
  }

构造函数需要参数(非类型安全)

@ParameterizedTest
  @MethodSource("solutionStream")
  void testOnlineCase(Class<Solution> solutionClass) throws NoSuchMethodException, IllegalAccessException,
      InvocationTargetException, InstantiationException {
    Solution solution = solutionClass.getConstructor(Integer.TYPE).newInstance(2);
  }

  static Stream<Class> solutionStream() {
    return Stream.of(
        SolutionI.class
    );
  }
xzv2uavs

xzv2uavs3#

我尝试了 * lotor * 方法,它只在 * 测试接口 * 中声明的 * 实现类 * 时有效。
木星:5.9.2(2023年1月10日)https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
代码示例:

public interface StringDiagnoseTest<T extends StringDiagnose> {
    
    T createDiagnose();
    
    @Test
    default void blankCheckFollowsContract() {
        assertTrue(createDiagnose().isTheStringBlank("\t\n "));
        assertFalse(createDiagnose().isTheStringBlank("\t\n !  \r\n"));
    }
    
    class DefaultDiagnoseTest implements StringDiagnoseTest<DefaultDiagnose> {
    
        @Override
        public DefaultDiagnose createDiagnose() {
            return new DefaultDiagnose();
        }
    }

    class StreamBasedDiagnoseTest implements StringDiagnoseTest<StreamBasedDiagnose> {
    
        @Override
        public StreamBasedDiagnose createDiagnose() {
            return new StreamBasedDiagnose();
        }
    }
}

相关问题