单元测试引用了关键部分类
Posted
技术标签:
【中文标题】单元测试引用了关键部分类【英文标题】:Unit Testing Refcounted Critical Section Class 【发布时间】:2010-03-11 05:18:21 【问题描述】:我正在看一个简单的类,我必须管理关键部分和锁,我想用测试用例来介绍它。这是否有意义,人们将如何去做呢?这很困难,因为验证类工作的唯一方法是设置非常复杂的线程场景,即使那样,也没有一个好的方法来测试 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(§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) ;
【问题讨论】:
【参考方案1】:这里有三个选项,我个人更喜欢最后一个...
您可以创建一个“临界区工厂”接口,该接口可以传递给您的构造函数。这将具有包装您需要使用的 API 级别函数的函数。然后,您可以模拟这个接口,并在测试时将模拟传递给代码,您可以确保调用了正确的 API 函数。通常,您还会有一个不采用此接口的构造函数,而是使用直接调用 API 的工厂的静态实例初始化自身。对象的正常创建不会受到影响(因为您让它们使用默认实现),但您可以在测试时进行检测。这是标准的依赖注入路径,使您能够parameterise from above。所有这一切的缺点是你有一个间接层,你需要在每个实例中存储一个指向工厂的指针(所以你可能在空间和时间上都丢失了)。 或者,您可以尝试从下面模拟 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()
实际上很少需要。
【讨论】:
这个问题很少。 #1。不能移动或复制 CRITICAL_SECTION 结构。这就是首先进行引用计数的原因。因此,这里某种形式的引用计数是必不可少的,因为我不希望客户不得不担心类的内存管理。 #2:我更喜欢你对锁的处理。非常感谢 :) #3:我将研究以某种方式模拟 API 函数——这很讽刺,因为临界区对象的一半目的是能够使用它来测试代码。 我从来没有发现 #1 是个问题;也许这只是我使用锁的方式。我只是将CCriticalSection
的实例添加到需要能够锁定自身区域的类中,然后使用RAII 'owner' 类来管理锁定生命周期。对于拥有自己锁的对象,我很少有复制 ctor 或赋值操作;它只是没有意义,所以我没有问题,也不需要参考计数......
我想我可以让临界区对象不可复制,但现在我处于一个场景中,我实际上需要临界区对象具有“shared_ptr
”语义。如果关键部分不需要特殊拆卸,我只需使用shared_ptr
并完成它,但不幸的是他们这样做了:(
就我个人而言,我会认真思考拥有一个可以复制、可以锁定的对象以及副本共享原始对象的锁定意味着什么......
我有一个类来管理报告的生成。报告的每个部分都需要很长时间才能生成,并且通常受 I/O 限制,因此我希望它们并行运行。需要在线程之间共享一个部分,因为当每个线程完成它的任务时,它 push_back
s 到已完成报告字符串的向量。一旦所有线程都完成了它们的工作,最终的报告就会被组装起来。因此,每个线程以及进行最终组装的组件都需要访问临界区。以上是关于单元测试引用了关键部分类的主要内容,如果未能解决你的问题,请参考以下文章