2010-03-11 67 views
0

我正在看一個簡單的類,我必須管理關鍵段和鎖,我想用測試用例來說明這一點。這是否有意義,以及如何去做呢?這很困難,因爲驗證類的唯一方法是設置非常複雜的線程場景,即使如此,也沒有一種好方法來測試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) {} ; 

}} 

回答

4

這裏有三個選項,並親自我喜歡最後一...

  • 您可以創建一個'critical section factory'接口可以傳遞給你的構造函數。這可能會包含您需要使用的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()實際上是非常罕見的。

+0

這個問題很少。 #1。 CRITICAL_SECTION結構不能被移動或複製。這就是首先要考慮的原因。因此,這裏的某種形式的refcount是必不可少的,因爲我不希望客戶不必擔心班級的記憶管理。 #2:我更喜歡你對鎖的處理。非常感謝你:)#3:我將以某種方式嘲笑API函數 - 這很具有諷刺意味,因爲關鍵部分對象的一半是能夠使用它來測試代碼。 – 2010-03-11 14:21:19

+0

我從未發現#1是一個問題;也許這只是我用我的鎖的方式。我只需將一個'CCriticalSection'實例添加到需要能夠鎖定自身區域的類中,然後使用RAII'所有者'類來管理鎖的生命週期。對於有自己的鎖的對象,我很少有一個副本或分配操作;它只是從來沒有道理,所以我沒有問題,不需要裁判計數... – 2010-03-11 14:55:07

+0

我想我可以讓臨界區對象不可複製,但現在我在一個場景,我真的需要臨界區對象具有「'shared_ptr」「語義。如果關鍵部分不需要特別拆卸,我只需使用'shared_ptr'並完成它,但不幸的是他們會這樣做:( – 2010-03-11 15:17:06