如何参数化泛型方法以免重复测试结构?

Posted

技术标签:

【中文标题】如何参数化泛型方法以免重复测试结构?【英文标题】:How can I parameterize a generic method so as not to repeat the structure of the tests? 【发布时间】:2021-05-24 21:23:15 【问题描述】:

我有一个 Spring Boot 项目,我在其中添加了以下 Test 的一些类。

    @Test
    void documentException() 
        assertThrows(DocumentException.class, () -> 
            try 
                throw new DocumentException();
             catch (Exception ex) 
                assertEquals("Error converting document format", ex.getMessage());
                assertEquals(417 /* EXPECTATION_FAILED */, ex.getHttpStatus());
                assertEquals(ErrorCodes.DOCUMENT_ERROR, ex.getCode());
                throw ex;
            
        );
    

@Test
    void maxUserException() 
        assertThrows(MaxUserException.class, () -> 
            try 
                Integer maxUsers = 5;
                throw new MaxUserException(maxUsers);
             catch (Exception ex) 
                Integer maxUsers = 5;
                assertEquals("Excedido el número máximo de "+ maxUsers +" dispositivos", ex.getMessage());
                assertEquals(403 /* FORBIDDEN */, ex.getHttpStatus());
                assertEquals(ErrorCodes.MAX_USERS_DEVICES, ex.getCode());
                throw ex;
            
        );
    
    
    @Test
    void docFormatException() 
        assertThrows(DocFormatException.class, () -> 
            try 
                throw new DocFormatException();
             catch (Exception ex) 
                assertEquals("Document format", ex.getMessage());
                assertEquals(415 /* UNSUPPORTED_MEDIA_TYPE */, ex.getHttpStatus());
                assertEquals(ErrorCodes.DOCUMENT_ERROR, ex.getCode());
                throw ex;
            
        );
    

对于每个班级,我使用相同的method

有没有办法用@Test 创建一个类并为每个类调用它?这样就不必为每个类创建这么大的结构。

我想要的是制作一个通用方法并将其应用于每个类。这样就不必为所有类重复相同的结构。

【问题讨论】:

拥有一个包含相同方法并在所有测试中重用的通用工具怎么样?这些看起来像 API 级 UT,并且您可能有多个 API 抛出相同的文档转换异常。 我不确定您在寻找什么,据我了解,您正在尝试创建一个需要从不同类调用的方法?如果我的理解是正确的尝试重构代码,如果你有多余的代码重构它使用通用方法并在其他地方使用,这将帮助你编写有效的测试用例。 创建一个基础测试类并将常用函数放入其中。然后在所有测试类中扩展该基类。根据您的要求使用@Before@BeforeEach 你到底在用这个测试测试什么? 我能够测试的是每个类的小例外,在做了很多测试之后,我正在寻找一种方法来制作一个独特的参数化方法,以免每次都重复整个结构,但我不知道该怎么做。我只用一个测试(问题中的那个)创建了一个单独的类,但我不知道如何为每个类调用它,也不知道如何继续参数化它。 【参考方案1】:

好吧,直接回答您的问题并使用您的实际示例 - 如果您想避免代码重复,那么您可以使用一些实用方法。

假设您的异常是您的自定义类的子类型或实现具有方法getHttpStatusgetCode 的相同接口。假设父类或接口的名称为CustomException

创建一个新类,例如 TestUtils 并定义可以重用的方法是您的测试,类似这样(idk 应该返回什么类型 getCode(),所以我假设它是 String

public class TestUtils 

    public static void testException(CustomException exception, String message, 
        String errorCode, int httpStatus) 
    
        assertThrows(exception.getClass(), () -> 
            try 
                throw exception;
             catch (CustomException ex) 
                assertEquals(message, ex.getMessage());
                assertEquals(httpStatus, ex.getHttpStatus());
                assertEquals(errorCode, ex.getCode());
                throw ex;
            
        );
    

那么你的测试方法可以简化:

    @Test
    void documentException() 
       TestUtils.testException(new DocumentException(), 
           "Error converting document format",
           417,
           ErrorCodes.DOCUMENT_ERROR);           
    

maxUserException() 有点棘手,但仍然有效:

@Test
void maxUserException() 
    int maxUsers = 5;
    MaxUserException exception = new MaxUserException(maxUsers);
    String message = "Excedido el número máximo de "+ maxUsers +" dispositivos";
    TestUtils.testException(exception, message, ErrorCodes.MAX_USERS_DEVICES, 403);

希望对您有所帮助。然而值得一提的是——如果它是你真正的测试代码并且它是这样写的,那么这些方法会尝试测试你的自定义异常是用正确的默认参数创建的(仅此而已),在这种情况下,测试方法可以被简化为它们现在太复杂了。

【讨论】:

我在ExceptionTest.java 的一个类中的所有测试,但我没有你说的主类,它们都是独立的类。如果没有主课,我怎么能做到这一点?我没有像CustomException 这样的类。 我认为您的异常确实有共同的父级,因为它们具有相同的方法,例如 ex.getHttpStatus()ex.getCode()。如果他们不这样做,那么为您的自定义异常创建一个接口或父类是值得的。如果您的 catch 子句 catch (Exception ex) 中的 ex 变量是 java.lang.Exception 类型,您的示例将无法编译,因为 java.lang.Exception 没有像 ex.getHttpStatus()ex.getCode() 这样的方法。【参考方案2】:

您的示例测试方法似乎有点复杂,但值得一提的是JUnit5 中有处理参数化测试的注释。 您应该查看这些文件:

@ParameterizedTest、@ValueSource、@CsvSource 的 Java 文档。

这是一个如何参数化测试的示例:

@ParameterizedTest
@CsvSource(value = 
                      // input and expected output separated by :
                     "test:test",
                     "tEst:test",
                     "Java:java"
                   , delimiter = ':')
void toLowerCase_ShouldGenerateTheExpectedLowercaseValue(String input, String expected) 
    String actualValue = input.toLowerCase();
    assertEquals(expected, actualValue);

看看这个Guide to JUnit 5 Parameterized Tests。

【讨论】:

以上是关于如何参数化泛型方法以免重复测试结构?的主要内容,如果未能解决你的问题,请参考以下文章

Java:泛型参数上带有.class的泛型方法[重复]

Java--泛型

Java进阶之泛型

如何通过使用类型名称来实例化泛型类?

实例化泛型类时如何避免指定冗余类型

如何在基类中实例化泛型类型?