如何在1个测试类中使用相同的方法测试多个对象?

时间:2019-05-30 23:28:06

标签: java unit-testing testing

我有2个不同的对象:C c和B b。 B和C实现接口A,以便他们可以使用接口A中存在的称为color()的方法。我已经对类B进行了单元测试,以测试B已实现的color()方法。因此,我现在想做的是在C类中使用与B类相同的单元测试来测试color()方法。因此,我想在为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搜索,但是没有搜索结果关于这种情况,在1个测试中需要注入2个对象。那么,我应该如何重构代码,以便注入类B和C,让它们使用与我已经为B创建的单元测试相同的单元测试?

1 个答案:

答案 0 :(得分:0)

BC使用相同的测试意味着实际上您正在通过接口A进行测试。然后,这些将主要是黑盒测试,因为您的测试只能依赖于界面的元素,而不能依赖于BC中特定实现的元素。有关此内容的更多信息。

但是,首先,提供一些示例代码来说明如何实现目标:该方法使用@MethodSource进行的Junit 5参数化测试。在示例中,@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的测试。由于BC将具有不同的实现,因此您可能还需要针对BC进行一些特定的测试。请记住,测试的目的是发现错误-不同的实现通常具有不同的潜在错误(不同的控制流,不同的溢出可能性等)。

更新:上面的解决方案回答了如何使用参数化测试来做到这一点,该测试将被测类作为参数。一种替代方法是为接口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(); }
}

在Junit 5下运行B_Test时,将执行基类中的所有测试方法(如果在B_Test中定义了其他测试方法)。好处是,每次创建新的派生类时,您都无需修改A_Test2。但是,如果每个派生类的期望值都不相同(第一个解决方案中的类名就是这种情况),则需要进行一些调整,例如更多类似于getClassUnderTest的回调方法。