java - 对具有重试逻辑的方法进行单元测试

标签 java testng jmockit

我有一个带有重试逻辑的方法。重试逻辑是使用自定义注释实现的。我想要一个单元测试来验证在抛出某个异常时是否再次调用该方法。

public class FileTest {

    String first;
    String second;
    Instant third;
    int fourth;

    @Tested
    StoreFiles storeFiles;

    @Injectable
    FileSystemFactory fileSystemFactory;

    @Mocked
    OracleConnection conn;

    @Before public void initMocks() {
       MockitoAnnotations.initMocks(StoreFiles.class);
    }

    @BeforeClass()
    public void init() {
        first= "test";
        second= "testRoot";
        third= Instant.now();
        fourth= 1;
    }

    @Test
    public void testRetry(@Mocked FileSystemFactory fileSystemFactory,
                          @Mocked StructDescriptor dbDataRecDesc,
                          @Mocked ArrayDescriptor dbDataTabDesc) throws CustomException {

        StoreFiles files = mock(StoreFiles.class);

        files.storeFiles(conn, first, second, third, fourth);

        Mockito.verify(files, times(2)).storeFiles(conn, first, second, third, fourth);
    }
}

当前使用jmockit、testng 和mockito。我只需要确保在抛出 CustomException 时再次调用 storeFile 方法。如果我使用 storeFiles 对象而不是 files 对象,则会抛出我想要的异常。如果我像此处编写的那样运行测试,则会收到一条错误,指出 storeFiles 方法仅被调用一次,指的是我在测试方法中显式调用它的位置。两者都会导致我正在测试的方法无法重试。

最佳答案

这很难测试,因为 StoreFiles::storeFile 方法正在做两件事:存储文件和执行重试逻辑。您可以更改您的设计以使其更具可测试性。这是我的建议:

首先,从 StoreFiles::storeFile 中删除重试逻辑,并将其提取到具有该职责的另一个类:

public class Retry {

    public void exec(Runnable r) {
        try {
            r.run();
        } catch (CustomException e) {
            r.run();
        }
    }

}

现在,您可以像这样编写重试逻辑测试:

public class RetryTest {

    //fields and init method

    @Test
    public void test() {
        Retry retry = new Retry();
        Mockito
            .doThrow(new CustomException()) //first StoreFiles::storeFile call, throws exeption
            .doNothing() //second StoreFiles::storeFile call, does nothing
            .when(storeFiles).storeFile(conn, first, second, third, fourth);

        retry.exec(() -> storeFiles.storeFile(conn, first, second, third, fourth));

        //verify StoreFiles::storeFile is called twice
        Mockito.verify(storeFiles, Mockito.times(2)).storeFile(conn, first, second, third, fourth);
    }

}

(我假设 CustomException 是一个非检查异常)

另一个解决方案是实现装饰器模式并构建一个RetryStoreFiles:

public class RetryStoreFiles implements StoreFiles {

    private final StoreFiles decorated;

    public RetryStoreFiles(StoreFiles decorated) {
        this.decorated = decorated;
    }

    @Override
    public void storeFile(conn: Conn, first: First, second: Second, third: Third, fourth: Fourth) {
        try {
            decorated.storeFile(conn, first, second, third, fourth);
        } catch (CustomException e) {
            decorated.storeFile(conn, first, second, third, fourth);
        }
    }

}

我更喜欢第二种解决方案。我认为它比第一个更语义化和面向对象。

关于java - 对具有重试逻辑的方法进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57430196/

相关文章:

java - 我需要什么集合来存储对象及其数量?

java - 为什么 SparkLauncher 立即返回并且没有产生任何工作?

java - 构建 java 应用程序后,某些 GUI 组件无法正常工作,但在 IDE 测试期间一切正常。为什么?

java - 编译 ant build.xml 时出现错误 "Package doesn' t 存在

java - JMockit 错误 - 附加 API 的 native 库在此 JRE 中不可用

java - Java内存泄漏是否有可能使用比堆+ permgen更多的内存?

selenium - 使用 TestNG 框架使用 Selenium WebDriver 跳过测试用例

java - Allure 框架: using @Step and @Attachment annotations with TestNG and Maven

java - 使用 JMockit 模拟和验证 SLF4J

jmockit - 如何使用 jmockit 模拟嵌套类的构造函数