【问题标题】:Library for Asserting Multi Thread Code in Java在 Java 中断言多线程代码的库
【发布时间】:2013-08-14 19:16:13
【问题描述】:

作为一名 TDD 从业者,我想测试我编写的所有代码。

在过去的几年里,我编写了许多多线程代码,其中一部分测试非常麻烦。

当我必须断言可能在 run() 循环期间发生的事情时,我最终会得到一些类似这样的断言:

assertEventually(timeout, assertion)

我知道Mockito 对此有解决方案,但仅适用于 verify 调用。我也知道 JUnit 有一个 timeout 属性,它有助于避免挂起(或永久)测试。但我想要的是能够让我断言随着时间的推移可能会成为现实的东西。

所以我的问题是,有人知道提供此功能的库吗?

到目前为止,这是我的解决方案:

private void assertEventually(int timeoutInMilliseconds, Runnable assertion){
    long begin = System.currentTimeMillis();
    long now = begin;
    Throwable lastException = null;
    do{
        try{
            assertion.run();
            return;
        }catch(RuntimeException e){
            lastException = e;
        }catch(AssertionError e){
            lastException = e;
        }
        now = System.currentTimeMillis(); 
    }while((now - begin) < timeoutInMilliseconds);
    throw new RuntimeException(lastException);
}

使用结果如下:

assertEvetuallyTrue(1000, new Runnable() {
        public void run() {
            assertThat(Thread.activeCount()).isEqualTo(before);
        }
    });

【问题讨论】:

  • 您在代码示例中断言什么?方法名称暗示某事物的值为 true 的断言,但代码仅执行 Runnable 并确保它不会出错。
  • 我假设您将使用某种断言来测试您的条件。所以他们会抛出有意义的异常来帮助调试你的代码。我举个例子。
  • 我原以为 assertEventually 会在超时时抛出异常。在您的示例中,它看起来像是试图抛出 null。此外,抛出AssertError 可能是有意义的。
  • 确实也必须预料到 AssertionError。但我仍在一个可用的库中寻找类似的东西。
  • 是的,我知道没有图书馆这样做。我用 junit 超时做了一个非常相似的事情,然后在我的竞争条件测试周围循环 while (true)

标签: java multithreading unit-testing


【解决方案1】:

您可能想查看两个库:

  • ConcurrentUnit - 允许您从任何线程执行断言,等待固定时间
  • Awaitility - 允许您等待固定的时间或直到满足某些条件

【讨论】:

    【解决方案2】:

    从您使用的语言 - 断言某事可能是真的 - 听起来您正在走上一条会导致脆弱测试的道路。测试应该有二元结果——它们要么通过,要么失败,并且没有灰色区域。

    看看这个具体的例子,考虑到多线程的使用,我会建议这样的事情:

    • 重构您的设计,以便您的代码中的所有实际逻辑都可以轻松地独立于您的多线程环境作为同步执行的代码运行,这将使围绕此编写单元测试变得相当简单。
    • 在所有这些代码之外实现您的线程管理 - 这将更难以使用单元测试进行测试,因此您应该将其与您的其余代码完全隔离,这样它就不会使其他代码更难测试。
    • 开始构建一个高级系统测试套件,它作为一个整体执行系统(或系统的大型组件),通过应用程序的外部边界驱动您的测试。这些将覆盖线程处理代码,以及测试系统中各种组件的集成。此外,您不必编写处理线程的特定测试逻辑 - 当您对其进行测试时,它们应该只在系统内部运行。

    以这种方式拆分测试的另一个好处是,您可以为单元测试和系统测试创建单独的测试套件,这应该有助于保持您的单元测试套件快速和精简(这样您就可以在测试期间更轻松、更频繁地运行它)发展)。任何涉及超时的测试(即本例中的系统测试)将需要更长的时间来执行,因此更适合仅偶尔运行 - 这种方法更容易做到。

    【讨论】:

    • 其实我已经在做这个了。我以直接的方式对特定行为进行单元测试。但最终我必须测试事物是如何协同工作的,这种断言在那种情况下是有意义的。
    • 我的立场是,采用这种方法进行测试会导致 (a) 更难测试和 (b) 导致更脆弱的测试。如果您从更高级别(即应用程序公开的 API)驱动测试,您可以以不必处理这些问题的方式构建测试。但是,我希望有人能证明我错了,因为如果有人能够展示一种能够给出非常明确和可靠的通过/失败断言的好方法,这可能是一种有用的技术。
    • 我同意你的看法。出于与您所说的相同的原因,这种测试不应用于对您的类进行单元测试。然而,总有一天,所有这些东西都必须组装在一起,你需要对它们进行测试。这时候这种断言就派上用场了。
    【解决方案3】:

    如果你来自 Scala,你可能已经习惯了 eventually trait。

    这对测试非常有用,因为您可以重试一段时间,直到结果最终返回或不返回。

    这里有一个 halfvim 制作的 Java 包装器:https://github.com/halfvim/eventually

    【讨论】:

      【解决方案4】:

      时效性测试是一种性能测试,除非您有严格的实时要求,否则会通过许多样本的统计数据来断言。

      单元测试只关注单个样本,它们应该是确定性的。我认为您应该通过某种机制通知您的 JUnit 线程,而不是这种检查和循环构造,只要您想要断言的事件发生。回调、Futures、Latches、Exchanger 等可以用来构建这样的机制。

      构建您的多线程代码是一个设计挑战,这样它就可以在没有检查循环或状态自旋等待的情况下进行测试。

      但如果您真的必须这样做,请使用System.nanoTime() 而不是System.currentTimeMillis(),因为它更新得更频繁,尽管这两个时钟的实际实现和准确性取决于您的 JVM 版本、操作系统和硬件。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多