我该如何对线程代码进行单元测试?

时间:2008-08-15 11:44:33

标签: multithreading unit-testing

到目前为止,我已经避免了测试多线程代码的噩梦,因为它看起来像是一个雷区太多了。我想问一下人们如何测试依赖线程成功执行的代码,或者人们如何测试那些只在两个线程以给定方式交互时出现的问题?

对于今天的程序员来说,这似乎是一个非常关键的问题,将我们的知识集中在这个人身上会很有用。

27 个答案:

答案 0 :(得分:227)

看,没有简单的方法可以做到这一点。我正在开发一个本质上是多线程的项目。事件来自操作系统,我必须同时处理它们。

处理复杂的多线程应用程序代码的最简单方法是:如果它太复杂而无法测试,那么你做错了。如果您有一个具有多个线程的单个实例,并且您无法测试这些线程相互重叠的情况,那么您的设计需要重做。它既简单又复杂。

有许多方法可以为多线程编程,避免线程同时运行在实例中。最简单的方法是使所有对象都不可变。当然,这通常是不可能的。因此,您必须在设计中识别线程与同一实例相互作用并减少这些位置数量的位置。通过这样做,您可以隔离实际发生多线程的几个类,从而降低测试系统的整体复杂性。

但是你必须意识到,即使这样做,你仍然无法测试两个线程相互衔接的每种情况。要做到这一点,你必须在同一个测试中同时运行两个线程,然后在任何给定时刻准确控制它们正在执行的行。你能做的最好的就是模拟这种情况。但这可能需要您专门为测试编写代码,而这最多只是迈向真正解决方案的一半。

测试线程问题代码的最佳方法可能是通过代码的静态分析。如果您的线程代码不遵循一组有限的线程安全模式,那么您可能会遇到问题。我相信VS中的Code Analysis确实包含一些线程知识,但可能并不多。

看看,目前的情况(并且可能会持续一段时间),测试多线程应用程序的最佳方法是尽可能降低线程代码的复杂性。最小化线程交互的区域,尽可能测试,并使用代码分析来识别危险区域。

答案 1 :(得分:87)

这个问题发布时已经有一段时间了,但仍然没有回答......

kleolb02的答案很好。我会尝试详细介绍。

有一种方法,我为C#代码练习。对于单元测试,您应该能够编写可重现的测试,这是多线程代码中的最大挑战。因此,我的回答旨在将异步代码强制转换为测试工具,该工作同步

这是Gerard Meszardos的书“xUnit Test Patterns”中的一个想法,被称为“Humble Object”(第695页):你必须将核心逻辑代码和任何闻起来像异步代码的东西分开。这将导致核心逻辑的一个类,它同步

这使您能够以同步方式测试核心逻辑代码。您可以完全控制对核心逻辑执行的调用的时间,从而可以进行可重现的测试。这是分离核心逻辑和异步逻辑的好处。

这个核心逻辑需要由另一个类包围,该类负责异步接收对核心逻辑的调用,并且将这些调用委托到核心逻辑。生产代码只能通过该类访问核心逻辑。因为这个类只应该委托调用,所以它是一个非常“愚蠢”的类,没有太多的逻辑。因此,您可以至少为这个非同步工作班保持单元测试。

高于此值(测试类之间的交互)是组件测试。同样在这种情况下,如果你坚持使用“Humble Object”模式,你应该能够对时间进行绝对控制。

答案 2 :(得分:58)

确实很难!在我的(C ++)单元测试中,我根据所使用的并发模式将其分解为几个类别:

  1. 单个测试用于在单个线程中运行且不是线程感知的类 - 很容易,像往常一样进行测试。

  2. 公开同步公共API的Monitor objects(在调用者的控制线程中执行同步方法的单元测试) - 实例化多个运行API的模拟线程。构建行使被动对象内部条件的场景。包括一个长时间运行的测试,它基本上可以在很长一段时间内从多个线程中击败它。我知道这是不科学的,但它确实建立了信心。

  3. Active objects的单元测试(封装自己的线程或控制线程的单元测试) - 与上面的#2类似,具体取决于类设计。公共API可能是阻塞的或非阻塞的,呼叫者可能获得期货,数据可能到达队列或需要出列。这里有许多可能的组合;白盒子走了。仍然需要多个模拟线程来调用被测对象。

  4. 暂且不说:

    在我所做的内部开发人员培训中,我将Pillars of Concurrency和这两种模式作为思考和分解并发问题的主要框架。显然有更先进的概念,但我发现这套基础知识有助于让工程师摆脱困境。如上所述,它还会导致代码更加可单元测试。

答案 3 :(得分:43)

近几年来,我为几个项目编写线程处理代码时多次遇到过这个问题。我提供了一个迟到的答案,因为大多数其他答案在提供替代方案时,实际上并没有回答有关测试的问题。我的答案是针对除了多线程代码之外没有其他选择的情况;我确实完成了代码设计问题,但也讨论了单元测试。

编写可测试的多线程代码

要做的第一件事是将生产线程处理代码与执行实际数据处理的所有代码分开。这样,数据处理可以作为单线程代码进行测试,多线程代码唯一能做的就是协调线程。

要记住的第二件事是多线程代码中的错误是概率性的;最不经常表现出来的错误是潜入生产的错误,即使在生产中也难以复制,从而导致最大的问题。出于这个原因,快速编写代码然后调试它直到它工作的标准编码方法对于多线程代码来说是一个坏主意;它将导致代码中的容易错误被修复,危险的错误仍然存​​在。

相反,在编写多线程代码时,您必须以一种态度编写代码,以避免首先编写错误。如果你已经正确删除了数据处理代码,那么线程处理代码应该足够小 - 最好是几行,最差几十行 - 你有机会编写它而不会编写bug,当然也没有写出很多bug ,如果你理解穿线,花点时间,小心。

编写多线程代码的单元测试

一旦尽可能仔细地编写多线程代码,仍然值得为该代码编写测试。测试的主要目的不是测试高度时序相关的竞争条件错误 - 它不可能重复测试这种竞争条件 - 而是测试你的防止这种错误的锁定策略允许多线程按预期进行互动。

要正确测试正确的锁定行为,测试必须启动多个线程。为了使测试可重复,我们希望线程之间的交互以可预测的顺序发生。我们不希望外部同步测试中的线程,因为这将掩盖在生产中可能发生的线程未在外部同步的错误。这使得线程同步使用了时序延迟,这是我在必须编写多线程代码测试时成功使用的技术。

如果延迟太短,则测试变得脆弱,因为较小的时序差异 - 例如在可能运行测试的不同机器之间 - 可能导致时间关闭并且测试失败。我通常做的是从导致测试失败的延迟开始,增加延迟以便测试在我的开发机器上可靠地通过,然后将延迟加倍,以便测试很有可能传递到其他机器上。这确实意味着测试需要花费大量的时间,但根据我的经验,仔细的测试设计可以将时间限制在不超过十几秒。由于您的应用程序中不应该有很多需要线程协调代码的地方,因此您的测试套件应该可以接受。

最后,跟踪测试中捕获的错误数量。如果您的测试具有80%的代码覆盖率,则可能会捕获大约80%的错误。如果你的测试设计得很好但没有发现错误,那么你就不会有额外的错误,这些错误只会出现在生产中。如果测试捕获了一个或两个错误,您可能仍然很幸运。除此之外,您可能需要仔细检查甚至完全重写您的线程处理代码,因为代码仍然可能包含在代码生产之前很难找到的隐藏错误,并且非常很难解决。

答案 4 :(得分:21)

我在测试多线程代码时也遇到了严重问题。然后我在Gerard Meszaros的“xUnit Test Patterns”中找到了一个非常酷的解决方案。他描述的模式称为 Humble object

基本上它描述了如何将逻辑提取到一个与环境分离的独立,易于测试的组件中。在测试了这个逻辑之后,您可以测试复杂的行为(多线程,异步执行等......)

答案 5 :(得分:17)

有一些工具非常好。以下是一些Java的摘要。

一些优秀的静态分析工具包括FindBugs(提供一些有用的提示),JLintJava Pathfinder(JPF& JPF2)和Bogor

MultithreadedTC是一个非常好的动态分析工具(集成到JUnit中),您必须在其中设置自己的测试用例。

来自IBM Research的

ConTest很有意思。它通过插入各种线程修改行为(例如,睡眠和放大)来检测代码,以尝试随机发现错误。

SPIN是一个非常酷的工具,用于建模Java(和其他)组件,但是你需要有一些有用的框架。它很难按原样使用,但如果你知道如何使用它会非常强大。相当多的工具在引擎盖下使用SPIN。

MultithreadedTC可能是最主流的,但上面列出的一些静态分析工具绝对值得一看。

答案 6 :(得分:13)

另一种(有点)测试线程代码的方法,以及非常复杂的系统通常是Fuzz Testing。 它并不好,它找不到所有东西,但它可能很有用而且很容易做到。

引用:

  

模糊测试或模糊测试是一种软件测试技术,可为程序的输入提供随机数据(“模糊”)。如果程序失败(例如,通过崩溃或内置代码断言失败),则可以注意到缺陷。模糊测试的巨大优势在于测试设计非常简单,并且没有对系统行为的偏见。

     

...

     

模糊测试通常用于采用黑盒测试的大型软件开发项目中。这些项目通常有预算来开发测试工具,而模糊测试是提供高成本效益的技术之一。

     

...

     

然而,模糊测试不能代替详尽的测试或正式方法:它只能提供系统行为的随机样本,并且在许多情况下通过模糊测试可能只能证明一个软件处理异常而不会崩溃而不是表现正确。因此,模糊测试只能被视为一种错误查找工具,而不是质量保证。

答案 7 :(得分:12)

Awaitility对于帮助您编写确定性单元测试也很有用。它允许您等到系统中的某个状态更新。例如:

await().untilCall( to(myService).myMethod(), greaterThan(3) );

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

它还支持Scala和Groovy。

await until { something() > 4 } // Scala example

答案 8 :(得分:11)

我做了很多这样的事情,是的很糟糕。

一些提示:

  • GroboUtils用于运行多个测试线程
  • alphaWorks ConTest指示类使迭代之间的交错变化
  • 创建一个throwable字段并在tearDown中进行检查(参见清单1)。如果你在另一个线程中捕获到一个错误的异常,只需将它分配给throwable。
  • 我在清单2中创建了utils类,发现它非常有价值,特别是waitForVerify和waitForCondition,这将大大提高测试的性能。
  • 在测试中充分利用AtomicBoolean。它是线程安全的,你经常需要一个最终的引用类型来存储来自回调类等的值。请参阅清单3中的示例。
  • 确保始终为测试提供超时(例如@Test(timeout=60*1000)),因为并发测试有时会在它们被破坏时永久挂起

清单1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

清单2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

清单3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}

答案 9 :(得分:11)

如前所述,测试MT代码的正确性是一个非常难的问题。最后,它归结为确保代码中没有错误同步的数据争用。这个问题是线程执行(交错)的可能性非常多,你没有太多的控制权(尽管请阅读this文章)。在简单的情况下,可能通过推理实际证明正确性,但通常情况并非如此。特别是如果你想避免/最小化同步,而不是去寻找最明显/最简单的同步选项。

我遵循的一种方法是编写高度并发的测试代码,以便可能发生可能未检测到的数据争用。然后我运行那些测试一段时间:)我曾经偶然发现一个谈话,其中一些计算机科学家展示了一种工具,这种方式(从规范中随机设计测试,然后同时运行它们,同时检查定义的不变量)被打破了。)

顺便说一下,我认为这里没有提到测试MT代码的这方面:识别可以随机检查的代码的不变量。不幸的是,找到那些不变量也是一个很难的问题。此外,他们可能无法在执行期间保持所有时间,因此您必须找到/强制执行可以预期它们为真的执行点。将代码执行带到这样的状态也是一个难题(并且本身可能会引发并发问题。哎呀,它太难了!

一些有趣的链接:

答案 10 :(得分:6)

对于Java,请查看JCIP的第12章。编写确定性,多线程单元测试以至少测试并发代码的正确性和不变量有一些具体的例子。

使用单元测试“证明”线程安全性更加严格。我的信念是,通过各种平台/配置的自动化集成测试可以更好地实现这一目标。

答案 11 :(得分:6)

Pete Goodliffe有一系列unit testing of threaded代码。

这很难。我采取了更简单的方法,并尝试保持从实际测试中抽象出来的线程代码。皮特确实提到我这样做的方式是错的,但我要么正确分离,要么就是幸运。

答案 12 :(得分:5)

我喜欢编写两个或多个测试方法来在并行线程上执行,并且每个测试方法都会调用被测对象。我一直在使用Sleep()调用来协调来自不同线程的调用顺序,但这并不可靠。它也慢很多,因为你必须睡得足够长,以便时间通常有效。

我从编写FindBugs的同一组中找到了Multithreaded TC Java library。它允许您在不使用Sleep()的情况下指定事件的顺序,并且它是可靠的。我还没有尝试过。

这种方法的最大限制是它只允许您测试您怀疑会导致麻烦的场景。正如其他人所说的那样,你真的需要将你的多线程代码分离成少量的简单类,以便彻底测试它们。

一旦你仔细测试了你希望引起麻烦的场景,一个不科学的测试会在课堂上抛出一堆同时发出的请求,这是寻找意外麻烦的好方法。

更新:我在多线程TC Java库中玩了一下,效果很好。我还将其部分功能移植到我称之为TickingTest的.NET版本中。

答案 13 :(得分:5)

我处理线程组件的单元测试与处理任何单元测试的方式相同,即控制和隔离框架的反转。我在.Net-arena开发并开箱即用,线程(以及其他事情)非常难(我说几乎不可能)完全孤立。

因此我写了一些看起来像这样(简化)的包装器:

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

从那里我可以轻松地将IThreadingManager注入到我的组件中,并使用我选择的隔离框架使线程在测试期间表现得像我期望的那样。

到目前为止,这对我来说非常有用,我对线程池使用相同的方法,在System.Environment,Sleep等中使用相同的方法等。

答案 14 :(得分:5)

查看我的相关答案

Designing a Test class for a custom Barrier

它偏向于Java,但对选项有一个合理的总结。

总之虽然(IMO)它不是使用一些确保正确性的花哨框架,而是如何设计多线程代码。分散关注点(并发性和功能性)对提高信心起着重要作用。 Growing Object Orientated Software Guided By Tests比我更好地解释了一些选项。

静态分析和形式化方法(参见Concurrency: State Models and Java Programs)是一种选择,但我发现它们在商业开发中的用途有限。

不要忘记任何加载/浸泡样式测试很少能保证突出问题。

祝你好运!

答案 15 :(得分:4)

我刚刚发现(用于Java)一个名为Threadsafe的工具。它是一个静态分析工具,很像findbugs,但专门用于发现多线程问题。它不是测试的替代品,但我可以将其作为编写可靠的多线程Java的一部分推荐。

它甚至可以捕获一些非常微妙的潜在问题,例如类包含,通过并发类访问不安全对象以及在使用双重检查锁定范例时发现丢失的volatile修饰符。

如果您编写多线程Java give it a shot.

答案 16 :(得分:3)

以下文章提出了两种解决方案。包装信号量(CountDownLatch)并添加内部线程外部化数据等功能。实现此目的的另一种方法是使用线程池(请参阅兴趣点)。

Sprinkler - Advanced synchronization object

答案 17 :(得分:1)

我遇到了测试线程代码的不幸任务,它们绝对是我写过的最艰难的测试。

在编写测试时,我使用了委托和事件的组合。基本上所有关于使用PropertyNotifyChanged或某种WaitCallback轮询使用ConditionalWaiter个事件。

我不确定这是否是最好的方法,但它对我有用。

答案 18 :(得分:1)

我上周大部分时间都在大学图书馆学习调试并发代码。核心问题是并发代码是非确定性的。通常情况下,学术调试已经落入三大阵营之一:

  1. 事件跟踪/重放。这需要事件监视器,然后查看已发送的事件。在UT框架中,这将涉及手动发送事件作为测试的一部分,然后进行事后审查。
  2. 脚本化。这是您使用一组触发器与正在运行的代码进行交互的地方。 “在x&​​gt; foo,baz()”。这可以解释为一个UT框架,在这个框架中你有一个运行时系统在特定条件下触发给定的测试。
  3. 互动。这显然不适用于自动测试情况。 ;)
  4. 现在,正如上面的评论员所注意到的,您可以将并发系统设计为更具确定性的状态。但是,如果你没有正确地做到这一点,你只需要再次设计一个顺序系统。

    我的建议是专注于有一个非常严格的设计协议,关于什么是线程和什么没有线程。如果限制界面以使元素之间的依赖性最小,则更容易。

    祝你好运,并继续解决这个问题。

答案 19 :(得分:0)

并发是内存模型,硬件,缓存和我们的代码之间的复杂相互作用。在Java的情况下,至少jcstress主要解决了部分此类测试。该库的创建者是许多JVM,GC和Java并发功能的作者。

但是,即使这个库也需要Java内存模型规范的丰富知识,以便我们确切地知道我们正在测试什么。但是我认为这项工作的重点是微基准测试。不是巨大的业务应用程序。

答案 20 :(得分:0)

这可能会有所帮助。对于这个有限的实验,纯 java 和 junit 就足够了。

https://stackoverflow.com/a/68552832/1518331

答案 21 :(得分:0)

假设“多线程”代码下的含义是

  • 有状态且易变
  • 并由多个线程访问/修改 同时

换句话说,我们正在谈论测试自定义有状态线程安全类/方法/单元-这在当今应该是非常罕见的野兽。

因为这种野兽很少见,所以我们首先要确保有所有有效的借口来写它。

步骤1。考虑在相同的同步上下文中修改状态。

今天,很容易编写可组合的并发和异步代码,其中IO或其他慢速操作被卸载到后台,但是共享状态在一个同步上下文中被更新和查询。例如异步/等待任务和.NET中的Rx等-它们都可以通过设计进行测试,可以使用“真实”任务和调度程序来进行确定性的测试(但是这超出了问题的范围)。

这听起来很受限制,但是这种方法出奇地好。可以用这种样式编写整个应用程序,而无需使任何状态的线程安全(我愿意)。

第2步。如果绝对不可能在单个同步上下文上操作共享状态。

确保不要重新发明轮子/绝对没有标准的替代品可以适合这项工作。代码很可能具有很强的凝聚力,并且包含在一个单元中,例如很有可能这是某些标准的线程安全数据结构(例如哈希映射或集合等)的特例。

注意:如果代码很大/跨多个类并且需要多线程状态操作,那么很有可能设计不好,请重新考虑步骤1

第3步。如果达到此步骤,则我们需要测试我们自己的自定义有状态线程安全类/方法/单元

我会说实话:我从来不需要为此类代码编写适当的测试。在大多数情况下,我无法执行步骤1,有时可以执行步骤2。上一次不得不编写自定义线程安全代码的时间是很多年前,因此在我通过单元测试之前/也许不必编写它无论如何还是以当前的知识。

如果我真的必须测试这样的代码(最终是实际答案),那么我将尝试以下几件事

  1. 不确定性压力测试。例如同时运行100个线程,并检查最终结果是否一致。 对于多用户场景的更高级别/集成测试而言,这更为典型,但也可以在单元级别使用。

  2. 公开一些测试“钩子”,其中测试可以注入一些代码,以帮助确定性方案,其中一个线程必须先执行另一个线程。 尽管丑陋,但我想不出更好的方法。

  3. 延迟驱动的测试,以使线程按特定顺序运行并执行操作。严格说来,此类测试也是不确定性的(系统冻结/ GC停止收集可能会扭曲原本精心策划的延迟),这也很丑陋,但可以避免钩子。

答案 22 :(得分:0)

对于J2E代码,我使用了SilkPerformer,LoadRunner和JMeter来进行线程的并发测试。他们都做同样的事情。基本上,它们为您提供了一个相对简单的界面,用于管理其所需的代理服务器版本,以便分析TCP / IP数据流,并模拟多个用户同时向您的应用服务器发出请求。通过代理服务器,您可以在处理请求后,通过显示发送到服务器的整个页面和URL以及服务器的响应来分析所做的请求。

您可以在不安全的http模式下找到一些错误,您可以在其中至少分析正在发送的表单数据,并系统地更改每个用户的错误。但真正的测试是在https(安全套接字层)中运行。然后,您还必须应对系统地更改会话和cookie数据,这可能会更复杂。

在测试并发性时,我发现的最好的错误是,当我发现开发人员依赖Java垃圾收集来关闭登录时建立的连接请求,以及登录时的LDAP服务器。这导致了用户在接触到其他用户的会话时会产生非常混乱的结果,在尝试分析服务器被瘫痪时发生的事情时,几乎每隔几秒钟就无法完成一次交易。

最后,你或某人可能不得不扣掉并分析代码,就像我刚才提到的那样。当我们展开上述问题时,跨部门的公开讨论是最有用的。但这些工具是测试多线程代码的最佳解决方案。 JMeter是开源的。 SilkPerformer和LoadRunner是专有的。如果你真的想知道你的应用程序是否是线程安全的,大男孩就是这样做的。我已经为非常大的公司专业做过这个,所以我不猜。我是根据个人经历说的。

提醒一句:理解这些工具确实需要一些时间。这不是简单地安装软件和启动GUI的问题,除非您已经接触过多线程编程。我已经尝试确定要理解的3个关键领域类别(表单,会话和cookie数据),希望至少从了解这些主题开始将有助于您专注于快速结果,而不是必须通读整个文档。

答案 23 :(得分:0)

有一篇关于该主题的文章,示例代码中使用Rust作为语言:

https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a

总而言之,诀窍是使用通道和condvars这样的工具编写并发逻辑,以使其对涉及多个执行线程的不确定性具有鲁棒性。

然后,如果这是构造“组件”的方式,则测试它们的最简单方法是使用通道向它们发送消息,然后阻止其他通道断言该组件发送了某些预期的消息。

链接到的文章完全使用单元测试编写。

答案 24 :(得分:-1)

如果您正在测试简单的新线程(可运行).run() 您可以模拟Thread以顺序运行runnable

例如,如果测试对象的代码调用这样的新线程

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

然后模拟新的Threads并按顺序运行runnable参数可以帮助

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}

答案 25 :(得分:-2)

(如果可能)不使用线程,使用actor / active对象。易于测试。

答案 26 :(得分:-4)

您可以使用EasyMock.make Thread Safe来使测试实例线程安全