如何有效地测试这段代码?

How to test this code effectively?

我很抱歉抛出这个随机主题,但我没有想出一个更好的名字,

class ReportSenderRunnable implements Runnable {

    private final LPLogCompressor compressor;

    public ReportSenderRunnable(final LPLogCompressor compressor) {
      this.compressor = compressor;
    }

    @Override
    public void run() {
      executeTasks();
    }

    private void executeTasks() {
      try {
//        compressor.compress();
        reportStatus = ReportStatus.COMPRESSING;
        System.out.println("compressing for 10 seconds");
        Thread.sleep(10000);
      } catch (final IllegalStateException e) {
        logCompressionError(e.getMessage());
      } /*catch (final IOException e) {
        logCompressionError(e.getMessage());
      }*/ catch (InterruptedException e) {
        logCompressionError(e.getMessage());
      }

      try {
        reportStatus = ReportStatus.SENDING;
        System.out.println("sending for 10 seconds");
        Thread.sleep(10000);
      } catch (final InterruptedException e) {
        reportStatus = ReportStatus.EXCEPTION_IN_SENDING;
      }

      try {
        reportStatus = ReportStatus.SUBMITTING_REPORT;
        System.out.println("submitting report for 10 seconds");
        Thread.sleep(10000);
      } catch (final InterruptedException e) {
        reportStatus = ReportStatus.EXCEPTION_IN_SUBMITTING_REPORT;
      }
      System.out.println("Report Sender completed");
      reportStatus = ReportStatus.DONE;
    }

    private void logCompressionError(final String cause) {
      logError(ReportStatus.COMPRESSING, cause);
      reportStatus = ReportStatus.EXCEPTION_IN_COMPRESSION;
    }

    private void logError(final ReportStatus status, final String cause) {
      LOGGER.error("{} - {}", status, cause);
    }
  }

理想情况下,

System.out.println("sending for 10 seconds");
Thread.sleep(10000);

将被实际任务取代,但现在假设是这种情况,它们运行的​​方式是

  private void submitJob() {
    final ExecutorService executorService = Executors.newSingleThreadExecutor();
    try {
      final LPLogCompressor lpLogCompressor = getLpLogCompressor();
      executorService.execute(getReportSenderRunnable(lpLogCompressor));
    } catch (final IOException e) {
      reportStatus = ReportStatus.EXCEPTION_IN_COMPRESSION;
      LOGGER.debug("Error in starting compression: {}", e.getMessage());
    }
    System.out.println("started Report Sender Job");
  }

我的问题是如何有效地测试这段代码?我写的是

 @Test
  public void testJobAllStages() throws InterruptedException, IOException {
    final ReportSender reportSender = spy(new ReportSender());
    doReturn(compressor).when(reportSender).getLpLogCompressor();
    when(compressor.compress()).thenReturn("nothing");
    reportSender.sendAndReturnStatus();
    Thread.sleep(10);
    assertEquals(ReportStatus.COMPRESSING, reportSender.getCurrentStatus());
    Thread.sleep(10000);
    assertEquals(ReportStatus.SENDING, reportSender.getCurrentStatus());
    Thread.sleep(10000);
    assertEquals(ReportStatus.SUBMITTING_REPORT, reportSender.getCurrentStatus());
  }

以上代码运行良好。 对我来说这很糟糕,原因如下

  1. 在理想情况下,并非所有任务都需要相同的时间
  2. 使用 Thread.sleep 进行测试会花费太多时间并且还会增加不确定性。

问题

  1. 如何有效地测试它?

您可以使用接受 Callable 的方法(例如 TimedAssertion.waitForCallable)添加 class,然后使用 ExecutorService 每秒执行一次 Callable,直到 returns真的。如果在特定时间段内不 return 为真,则失败。

然后您可以像这样从您的测试中调用 class:

boolean result;
result = new TimedAssertion().waitForCallable(() -> 
    reportSender.getCurrentStatus() == ReportStatus.COMPRESSING);
assertTrue(result);
result = new TimedAssertion().waitForCallable(() -> 
    reportSender.getCurrentStatus() == ReportStatus.SENDING);
assertTrue(result);

...等这样,您可以轻松地等待代码中的特定状态为真,而无需等待太久——并且您可以在需要此类断言的任何地方重用这个新的 class。

根据 @Boris the Spider 评论,我使用了模拟,下面是我的测试结果

  @Mock
  private ReportSenderRunnable reportSenderRunnable;

  @Mock
  private LPLogCompressor compressor;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
  }

  @Test(timeout = 1000)
  public void testJobNoException() throws InterruptedException, IOException {
    final ReportSender reportSender = spy(new ReportSender());
    doReturn(compressor).when(reportSender).getLpLogCompressor();
    when(compressor.compress()).thenReturn("nothing");
    reportSender.sendAndReturnStatus();
    Thread.sleep(10);
    assertEquals("Job must be completed successfully", ReportStatus.DONE,
                 reportSender.getCurrentStatus());
  }

  @Test(timeout = 1000)
  public void testJobWithIllegalStateException() throws Exception {
    final ReportSender reportSender = spy(new ReportSender());
    doReturn(compressor).when(reportSender).getLpLogCompressor();
    doThrow(IllegalStateException.class).when(compressor).compress();
    reportSender.sendAndReturnStatus();
    Thread.sleep(10);
    assertEquals("Job must failed during compression", ReportStatus.EXCEPTION_IN_COMPRESSION,
                 reportSender.getCurrentStatus());
  }