Как проверить несколько объектов одним и тем же методом в одном тестовом классе? - PullRequest
3 голосов
/ 31 мая 2019

У меня есть 2 разных объекта: C c и B b.B и C реализуют интерфейс A, так что они могут использовать метод с именем color (), который присутствует в интерфейсе A. Я уже провел модульное тестирование для класса B, который тестирует метод color (), который реализовал B.Теперь я хочу протестировать метод color () в классе C с помощью одного и того же модульного теста класса B. Таким образом, я хочу протестировать оба из них в одном и том же тестовом классе, который я создал для класса B.

Чтобы достичь этого, один из моих друзей сказал, что мне придется использовать параллельную иерархию классов этих классов.Но я действительно не знаю, как мне реализовать это в моем тесте.

Это то, что у меня есть с точки зрения кода:

private static Sprites sprites;
private static B b;

@BeforeAll
static void setUp() {
    sprites = mock(Sprites.class);
    b = new B(sprites);
}

@Test
void testing_color_method() {

    M m = mock(M.class);

    b.color(m);

    verify(sprites).getSpritesOf(m);

}

//... some more tests

Я использую JUnit 5, и я знаю, что яЯ мог бы использовать @ParameterizedTest для внедрения объектов B и C в тест, чтобы позволить им использовать одни и те же модульные тесты. Я также провел поиск в Google по этому поводу, но ни в одном из результатов поиска не было такого рода случаев, когда нужно 2 объекта.вводится в 1 тест.Итак, как мне провести рефакторинг кода, чтобы я мог внедрить классы B и C, чтобы позволить им использовать те же модульные тесты, которые я уже написал для B?

1 Ответ

0 голосов
/ 03 июня 2019

Используя те же тесты для 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...