我在看一个简单的类,我必须管理关键部分和锁,我想用测试用例来介绍它。这是否有意义,人们将如何去做呢?这很困难,因为验证类工作的唯一方法是设置非常复杂的线程场景,即使那样也没有好的方法来测试 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(§ionStructure); };
void Enter() { EnterCriticalSection(§ionStructure); };
bool TryEnter() { if (TryEnterCriticalSection(§ionStructure)) return true; else return false; };
void Leave() { LeaveCriticalSection(§ionStructure); };
public:
~CriticalSectionImpl() { DeleteCriticalSection(§ionStructure); };
};
void CriticalSection::Enter() { impl->Enter(); };
bool CriticalSection::TryEnter() { return impl->TryEnter(); };
void CriticalSection::Leave() { impl->Leave(); };
CriticalSection::CriticalSection() : impl(new CriticalSectionImpl) {} ;
}}
最佳答案
这里有三个选项,我个人更喜欢最后一个...
- 您可以创建一个可以传递给您的构造函数的“关键部分工厂”接口(interface)。这将包含包装您需要使用的 API 级函数的函数。然后您可以模拟此接口(interface)并在测试时将模拟传递给代码,您可以确保调用了正确的 API 函数。通常,您还会有一个不采用此接口(interface)的构造函数,而是使用直接调用 API 的工厂静态实例初始化自身。对象的正常创建不会受到影响(因为您让它们使用默认实现)但您可以在测试时进行检测。这是标准的依赖注入(inject)路径,结果你能够 parameterise from above .所有这一切的缺点是你有一个间接层,你需要在每个实例中存储一个指向工厂的指针(所以你可能在空间和时间上都失败了)。
- 或者,您可以尝试从底层模拟 API...很久以前,我研究过使用 API Hook 来测试这种低级 API 的使用情况;这个想法是,如果我 Hook 实际的 Win32 API 调用,我可以开发一个“模拟 API 层”,它的使用方式与更普通的模拟对象相同,但将依赖于“从下面进行参数化”而不是从上面进行参数化。虽然这很有效,而且我在项目中走了很长一段路,但要确保你只模拟被测代码是非常复杂的。这种方法的好处是,在我的测试中,我可以在受控条件下导致 API 调用失败;这使我能够测试否则很难执行的故障路径。
- 第三种方法是接受某些代码无法使用合理的资源进行测试并且依赖注入(inject)并不总是合适的事实。使代码尽可能简单,观察它,为你可以编写的位编写测试,然后继续。这就是我在这种情况下倾向于做的事情。
然而……
我对您的设计选择持怀疑态度。首先,类里面发生了太多事情(恕我直言)。引用计数和锁定是正交的。我将它们分开,这样我就有了一个简单的类来管理关键部分,然后在它的基础上构建我发现我真的需要引用计数……其次是引用计数和锁函数的设计;与其返回释放其 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()
。
关于c++ - 单元测试引用关键部分类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2422704/