【问题标题】:How to test multiple objects with same method in 1 test class?如何在 1 个测试类中使用相同的方法测试多个对象?
【发布时间】:2019-05-30 23:28:05
【问题描述】:

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

【问题讨论】:

    标签: java unit-testing testing


    【解决方案1】:

    BC 使用相同的测试意味着,您实际上是通过接口A 进行测试。这些将主要是黑盒测试,因为您的测试只能依赖于接口的元素,而不是BC 中特定实现的元素。更多信息请参见下文。

    然而,首先,一些示例代码如何实现您的目标:该方法使用使用 @MethodSource 的 Junit 5 参数化测试。在示例中,@MethodSource 提供了作为参数 a) 测试的描述(在测试方法逻辑中未使用,因此在此处称为 dummy),b) 实现接口 @ 的类之一的实例987654329@,并且,作为一个工作示例,您可以试验一下,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 的回调方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      • 2021-03-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多