如何为线程安全编写自动化测试

时间:2008-12-27 11:07:43

标签: c++ multithreading unit-testing testing

我有一个非线程安全的类:

class Foo { 
    /* Abstract base class, code which is not thread safe */ 
};

此外,如果你有foo1和foo2对象,你不能调用foo1-> someFunc()直到foo2-> anotherFunc()返回(这可能发生在两个线程上)。这种情况是无法更改的(Foo子类实际上是python脚本的包装器)。

为了防止不必要的来电,我创建了以下内容 -

class FooWrapper {
public:
    FooWrapper(Foo* foo, FooWrappersMutex* mutex);
    /* Wrapped functions from Foo */
};

在内部,FooWrapper使用共享互斥锁包装对Foo函数的调用。

我想测试FooWrapper的线程安全性。我最大的问题是线程是由操作系统管理的,这意味着我对执行的控制较少。我想测试的是以下场景:

  • 线程1调用fooWrapper1-> someFunc()并在函数内部阻塞
  • 线程2调用fooWrapper2-> anotherFunc()并立即返回(因为someFunc()仍在执行)
  • 线程1完成执行

最简单的方法是自动测试这样的场景吗?

我在Win32上使用QT,虽然我更喜欢QT至少是跨平台的解决方案。

6 个答案:

答案 0 :(得分:9)

您可能希望通过Microsoft Research查看CHESS: A Systematic Testing Tool for Concurrent Software。它是多线程程序(.NET和本机代码)的测试框架。

如果我理解正确,它会用自己的操作系统替换操作系统的线程库,以便它可以控制线程切换。然后它分析程序,找出线程执行流可以交错的每种可能方式,并为每次可能的交错重新运行测试套件。

答案 1 :(得分:5)

为什么不创建一个伪造的Foo来由你的包装器调用,其中函数记录它们实际启动/完成的时间,而不仅仅是检查特定线程是否已完成。那么你的屈服线程只需等待足够长的时间就可以区分记录时间之间的差异。在您的测试中,您可以声明another_func的开始时间在some_func的开始时间之后,并且它的完成时间在some_func完成时间之前。由于您的假类仅记录时间,因此这应足以保证包装器类正常工作。

编辑:当然,您知道Foo对象的作用可能是anti-pattern,即Sequential Coupling。根据它的作用,你可以通过简单地让第二种方法在第一种方法尚未被调用时不做任何事来处理它。使用顺序耦合链接中的示例,这类似于在按下加速踏板时汽车不执行任何操作,如果汽车尚未启动。如果不采取任何措施,您可以等待稍后再试,在当前线程中启动“启动序列”,或将其作为错误处理。所有这些东西都可以由你的包装器强制执行,并且可能更容易测试。

如果需要对另一个方法进行干预调用,您可能还需要注意确保相同的方法不会按顺序调用两次。

答案 2 :(得分:3)

Intel Threadchecker

如果我没记错的话,该工具会检查您的代码是否存在理论上可能的数据争用。 关键是你不需要运行你的代码来检查它是否正确。

答案 3 :(得分:2)

当你开始多线程时,你的代码根据定义变成了非确定性的,因此在一般情况下,测试线程安全是不可能的。

但是对于你非常具体的问题,如果你在Foo中插入长时间的延迟以使每个Foo方法花费一段时间,那么你可以按照你的要求做。也就是说,在第二个线程进入调用之前返回的第一个线程的概率基本上为零。

但是你真正想要完成的是什么?这个测试应该测试什么?如果您尝试验证FooWrappersMutex类是否正常工作,则不会这样做。

答案 4 :(得分:0)

到目前为止,我已经编写了以下代码。有时候它会工作,有时测试会失败,因为Sleep不足以运行所有线程。

//! Give some time to the other threads
static void YieldThread()
{
#ifdef _WIN32
    Sleep(10);
#endif //_WIN32
}

class FooWithMutex: public Foo {
public:
    QMutex m_mutex;
    virtual void someFunc()
    {
        QMutexLocker(&m_mutex);
    }
    virtual void anotherFunc()
    {
        QMutexLocker(&m_mutex);
    }
};

class ThreadThatCallsFooFunc1: public QThread {
public:
    ThreadThatCallsFooFunc1( FooWrapper& fooWrapper )
        : m_fooWrapper(fooWrapper) {}

    virtual void run()
    {
        m_fooWrapper.someFunc();
    }
private:
    FooWrapper& m_fooWrapper;
};

class ThreadThatCallsFooFunc2: public QThread {
public:
    ThreadThatCallsFooFunc2( FooWrapper& fooWrapper )
        : m_fooWrapper(fooWrapper) {}

    virtual void run()
    {
        m_fooWrapper.anotherFunc();
    }
private:
    FooWrapper& m_fooWrapper;
};

TEST(ScriptPluginWrapperTest, CallsFromMultipleThreads)
{
    // FooWithMutex inherits the abstract Foo and adds
    // mutex lock/unlock on each function.
    FooWithMutex fooWithMutex;

    FooWrapper fooWrapper( &fooWithMutex );
    ThreadThatCallsFooFunc1 thread1(fooWrapper);
    ThreadThatCallsFooFunc2 thread2(fooWrapper);

    fooWithMutex.m_mutex.lock();
    thread1.start(); // Should block

    YieldThread();
    ASSERT_FALSE( thread1.isFinished() );

    thread2.start(); // Should finish immediately
    YieldThread();
    ASSERT_TRUE( thread2.isFinished() );

    fooWithMutex.m_mutex.unlock();

    YieldThread();
    EXPECT_TRUE( thread1.isFinished() );
}

答案 5 :(得分:0)

相关问题