Используя те же тесты для B
и C
, вы фактически тестируете через интерфейс A
. Тогда это будут в основном тесты черного ящика, потому что ваши тесты могут зависеть только от элементов интерфейса, но не от элементов конкретных реализаций в B
и C
. Подробнее об этом ниже.
Однако сначала приведем пример кода, как вы могли бы достичь своей цели: подход использует параметризованные тесты Junit 5 с использованием @MethodSource
. В этом примере @MethodSource
предоставляет в качестве аргументов a) описание теста (который не используется в логике метода тестирования и поэтому там называется dummy
), b) экземпляр одного из классов, которые реализуют интерфейс A
, и, ради рабочего примера, с которым вы можете поэкспериментировать, c) ожидаемое имя класса объекта.
Код для интерфейса A:
package so56386880;
public interface A {
public void color();
}
Код для классов B и C (я использовал тот же код, здесь показываю B):
package so56386880;
public class B implements A {
public void color() { }
}
Тестовый код
package so56386880;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
class A_Test {
@DisplayName("Test one common property of color method")
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("provideImplementors")
void color_whenCalled_shallXxx(String dummy, A someA, String expectedClassName) {
// you would do some test for someA.color(), but just for example:
assertEquals(expectedClassName, someA.getClass().getName());
}
static Stream<Arguments> provideImplementors() {
return Stream.of(
Arguments.of("expecting class B's name", new B(), "so56386880.B"),
Arguments.of("expecting class C's name", new C(), "so56386880.C"));
}
}
Как сказано выше, это может выполнять только те тесты, которые ограничены использованием интерфейса A
. Поскольку B
и C
будут иметь разные реализации, вполне вероятно, что вам также понадобятся некоторые специальные тесты для B
и C
. Помните, что целью тестирования является поиск ошибок - и разные реализации обычно имеют разные потенциальные ошибки (разные потоки управления, разные возможности переполнения и т. Д.).
Обновление: Решение, приведенное выше, отвечает, как это сделать с параметризованными тестами, которые предоставляют тестируемый класс в качестве аргумента. Альтернативным подходом может быть реализация тестового класса для интерфейса as below
:
package so56386880;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
abstract class A_Test2 {
public abstract A getClassUnderTest();
@Test
void color_whenCalled_shallXxx() {
A classUnderTest = this.getClassUnderTest();
classUnderTest.color(); // exercise
assertEquals(expected..., actual...);
}
}
И затем выведите его, как показано ниже:
package so56386880;
class B_Test extends A_Test2 {
public A getClassUnderTest() { return new B(); }
}
При запуске B_Test
в Junit 5 выполняются все методы тестирования в базовом классе (плюс дополнительные, если они определены в B_Test
). Преимущество состоит в том, что вам не нужно изменять A_Test2
всякий раз, когда создается новый производный класс. Однако если у вас есть ожидаемые значения, которые различаются для каждого производного класса (как это было в случае с именем класса в первом решении), это требует некоторой адаптации, например, дополнительных методов обратного вызова, подобных getClassUnderTest
.