单元测试引用了关键部分类

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(&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】:

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

您可以创建一个“临界区工厂”接口,该接口可以传递给您的构造函数。这将具有包装您需要使用的 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_backs 到已完成报告字符串的向量。一旦所有线程都完成了它们的工作,最终的报告就会被组装起来。因此,每个线程以及进行最终组装的组件都需要访问临界区。

以上是关于单元测试引用了关键部分类的主要内容,如果未能解决你的问题,请参考以下文章

Python-调试&单元测试

Python连载22-调试&单元测试

十分钟了解单元测试

如何组织或分类单元测试 DAO 搜索方法

java 使用idea进行单元测试

(转)用JUnit4进行单元测试