Как протестировать различные реализации интерфейса в Junit5 без дублирования кода - PullRequest
1 голос
/ 31 марта 2019

Могу ли я спросить, как написать тест junit 5 для интерфейса с различными реализациями?

Например, у меня есть интерфейс Solution с различными реализациями, такими как SolutionI, SolutionII. Могу ли я написать только один тестовый класс для проверки обоих?

Существует сообщение , показывающее пример, но если необходимо вызвать несколько методов тестирования, я должен передать параметр для каждого метода тестирования.

Могу ли я спросить, есть ли элегантный способ, подобный тому, что используется в 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 использует SolutionI, а testMethod2 использует SolutionII, что имеет смысл, я не знаю, поможет ли это немного вдохновить.

Заранее спасибо за помощь

Ответы [ 2 ]

1 голос
/ 17 апреля 2019

Юпитер предоставляет Тест интерфейсов именно для вашей цели - для тестирования контракт интерфейса .

Например, давайте создадим интерфейс для контракта по диагностике строк и две реализации, следующие за контрактом, но использующие различные идеи реализации:

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

Используйте больше хуков и не-1023 * методов интерфейса для тестирования аспектов решений с таким же именем (например, производительности) и определения новых тестов в реализациях интерфейса для совершенно отличительной реализацииОСОБЕННОСТИ.

1 голос
/ 31 марта 2019

Например, у меня есть интерфейс Solution с различными реализациями, такими как SolutionI, SolutionII, могу ли я написать только один тестовый класс для тестирования обоих?

Короткий ответ: ты не должен этого делать .В соответствии с рекомендациями, для UT каждая реализация будет иметь свой собственный класс тестов, поэтому при изменении одной реализации будут затронуты только соответствующие тесты.Пожалуйста, найдите ниже некоторые дополнительные мысли:

  • Если у вас есть две реализации одного и того же интерфейса, я думаю, логика отличается, иначе зачем беспокоиться о наличии двух реализаций во-первых?Таким образом, у вас должно быть два набора тестов:

  • Если у вас есть общая логика между двумя реализациями, то вы должны поместить ее в абстрактный класс, который будет расширен вашими реализациями.

  • Не следует использовать ParameterizedTest для отклонения от лучшего шаблона;

  • В противном случае, чтобы избежать репликации кода для тестов, в соответствии с вашим вариантом использования,В JUnit5 вы действительно можете использовать расширения , как объяснено здесь в документации.

...