单元测试Refcounted Critical Section Class

时间:2010-03-11 05:18:21

标签: c++ unit-testing winapi multithreading critical-section

我正在查看一个简单的类,我必须管理关键部分和锁,我想用测试用例来解决这个问题。这是否有意义,以及如何去做?这很困难,因为验证类工作的唯一方法是设置非常复杂的线程场景,即使这样,也没有一种好方法可以测试Win32中关键部分的泄漏。有没有更直接的方法来确保它正常工作?

以下是代码:

CriticalSection.hpp:

#pragma once
#include <windows.h>
#include <boost/shared_ptr.hpp>

namespace WindowsAPI { namespace Threading {

    class CriticalSectionImpl;
    class CriticalLock;
    class CriticalAttemptedLock;

    class CriticalSection
    {
        friend class CriticalLock;
        friend class CriticalAttemptedLock;
        boost::shared_ptr<CriticalSectionImpl> impl;
        void Enter();
        bool TryEnter();
        void Leave();
    public:
        CriticalSection();
    };

    class CriticalLock
    {
        CriticalSection &ref;
    public:
        CriticalLock(CriticalSection& sectionToLock) : ref(sectionToLock) { ref.Enter(); };
        ~CriticalLock() { ref.Leave(); };
    };

    class CriticalAttemptedLock
    {
        CriticalSection &ref;
        bool valid;
    public:
        CriticalAttemptedLock(CriticalSection& sectionToLock) : ref(sectionToLock), valid(ref.TryEnter()) {};
        bool LockHeld() { return valid; };
        ~CriticalAttemptedLock() { if (valid) ref.Leave(); };
    };

}}

CriticalSection.cpp:

#include "CriticalSection.hpp"

namespace WindowsAPI { namespace Threading {

class CriticalSectionImpl
{
    friend class CriticalSection;
    CRITICAL_SECTION sectionStructure;
    CriticalSectionImpl() { InitializeCriticalSection(&sectionStructure); };
    void Enter() { EnterCriticalSection(&sectionStructure); };
    bool TryEnter() { if (TryEnterCriticalSection(&sectionStructure)) return true; else return false; };
    void Leave() { LeaveCriticalSection(&sectionStructure); };
public:
    ~CriticalSectionImpl() { DeleteCriticalSection(&sectionStructure); };
};

void CriticalSection::Enter() { impl->Enter(); };
bool CriticalSection::TryEnter() { return impl->TryEnter(); };
void CriticalSection::Leave() { impl->Leave(); };
CriticalSection::CriticalSection() : impl(new CriticalSectionImpl) {} ;

}}

1 个答案:

答案 0 :(得分:4)

以下是三个选项,我个人赞成最后一个......

  • 您可以创建一个可以传递给构造函数的“关键部分工厂”界面。这将包含您需要使用的API级别函数的函数。然后,您可以模拟此接口并在测试时将模拟传递给代码,您可以确保调用正确的API函数。通常,您也会有一个没有接受此接口的构造函数,而是使用直接调用API的工厂的静态实例初始化自身。对象的正常创建不会受到影响(因为它们使用默认实现)但您可以在测试时进行检测。这是标准的依赖注入路由,使您能够parameterise from above。所有这一切的缺点是你有一个间接层,你需要在每个实例中存储一个指向工厂的指针(所以你可能在空间和时间上都输掉了)。
  • 或者你可以尝试从下面嘲笑API ...很久以前我研究过使用API​​挂钩测试这种低级API的用法;我的想法是,如果我挂钩实际的Win32 API调用,我可以开发一个'模拟API层',它将以与更常规的模拟对象相同的方式使用,但是依赖于“来自下面的参数化”而不是从上面进行参数化。虽然这很有效,但我在项目中走了很长一段路,确保你只是在嘲笑被测试的代码是非常复杂的。这种方法的好处是我可以在我的测试中使受控条件下的API调用失败;这让我可以测试非常难以操作的故障路径。
  • 第三种方法是接受某些代码在合理的资源下无法测试,并且依赖注入并不总是合适的。尽可能简化代码,注意它,为可以进行的位编写测试并继续进行。这就是我在这种情况下倾向于做的事情。

然而....

我怀疑你的设计选择。首先,课堂上发生了太多事情(恕我直言)。引用计数和锁定是正交的。我将它们分开,以便我有一个简单的类进行关键部分管理然后构建它我发现我真的需要引用计数...其次是引用计数和锁定函数的设计;而不是返回一个在其dtor中释放锁的对象,为什么不简单地在堆栈上创建一个对象来创建一个范围锁。这将消除大部分复杂性。事实上,你最终可能会得到一个简单的关键部分类:

CCriticalSection::CCriticalSection()
{
   ::InitializeCriticalSection(&m_crit);
}

CCriticalSection::~CCriticalSection()
{
   ::DeleteCriticalSection(&m_crit);
}

#if(_WIN32_WINNT >= 0x0400)
bool CCriticalSection::TryEnter()
{
   return ToBool(::TryEnterCriticalSection(&m_crit));
}
#endif

void CCriticalSection::Enter()
{
   ::EnterCriticalSection(&m_crit);
}

void CCriticalSection::Leave()
{
   ::LeaveCriticalSection(&m_crit);
}

这符合我对这种代码的想法,这种代码很容易引人注目而不是引入复杂的测试...

然后你可以有一个范围锁定类,如:

CCriticalSection::Owner::Owner(
   ICriticalSection &crit)
   : m_crit(crit)
{
   m_crit.Enter();
}

CCriticalSection::Owner::~Owner()
{
   m_crit.Leave();
}

你会像这样使用它

void MyClass::DoThing()
{
   ICriticalSection::Owner lock(m_criticalSection);

   // We're locked whilst 'lock' is in scope...
}

当然我的代码没有使用TryEnter()或做任何复杂的事情,但没有什么可以阻止你的简单RAII类做更多事情;虽然,恕我直言,我认为实际上很少需要TryEnter()