簡體   English   中英

如何在不重復代碼的情況下在 Junit5 中測試接口的不同實現

[英]How to test different implementations for an interface in Junit5 without duplicating the code

請問如何為具有不同實現的接口編寫junit 5測試?

例如,我有一個接口Solution ,具有不同的實現,如SolutionISolutionII ,我可以只編寫一個測試類來測試兩者嗎?

有一個帖子展示了一個例子,但是如果有多個測試方法需要調用,我必須為每個測試方法傳遞參數。

請問有沒有類似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() {

  }
}

Junit 5 聲稱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 ,這是有道理的,我不知道這是否有助於啟發一點。

我在這里先向您的幫助表示感謝

木星提供的測試接口正是你的目的-測試界面合同

例如,讓我們有一個字符串診斷合約的接口和兩個遵循合約但利用不同實現思路的實現:

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接口方法來測試同名解決方案的方面(如性能),並在接口實現中定義新的測試,以獲得完全不同的實現特性。

很抱歉有一段時間沒有回復這個帖子。 與 lotor 的回答相比,我發現了我目前采用的其他一些方法:


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

例如,我有一個接口解決方案,具有不同的實現,如解決方案I、解決方案II,我可以只編寫一個測試類來測試兩者嗎?

簡短的回答是你不應該那樣做 對於 UT,最佳實踐是每個實現都有自己的測試類,因此如果一個實現發生變化,那么只有相關的測試會受到影響。 請在下面找到一些額外的想法:

  • 如果你有同一個接口的兩個實現,我猜邏輯是不同的,否則為什么要首先有兩個實現呢? 所以你應該有兩組測試;

  • 如果您在兩個實現之間有共同的邏輯,那么您應該將它放在一個抽象類中,該類將被您的實現擴展。

  • ParameterizedTest 不應被濫用以偏離最佳模式;

  • 在其他以避免測試的代碼復制,根據你的使用情況,在JUnit5你的確可以使用擴展作為解釋這里的文件中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM