0

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

这是代码:

关键部分.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(); };
    };

}}

关键部分.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) {} ;

}}
4

1 回答 1

4

这里有三个选项,我个人更喜欢最后一个......

  • 您可以创建一个可以传递给构造函数的“临界区工厂”接口。这将具有包装您需要使用的 API 级别函数的函数。然后,您可以模拟这个接口,并在测试时将模拟传递给代码,您可以确保调用了正确的 API 函数。通常,您还会有一个不采用此接口的构造函数,而是使用直接调用 API 的工厂的静态实例初始化自身。对象的正常创建不会受到影响(因为您让它们使用默认实现),但您可以在测试时进行检测。这是标准的依赖注入路径,可以让您从上面进行参数化. 所有这一切的缺点是你有一个间接层,你需要在每个实例中存储一个指向工厂的指针(所以你可能在空间和时间上都丢失了)。
  • 或者,您可以尝试从下面模拟 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()实际上很少需要。

于 2010-03-11T08:39:28.027 回答