Unit Testing Refcounted Critical Section-Klasse
-
19-09-2019 - |
Frage
Ich freue mich auf eine einfache Klasse I kritische Abschnitte und Sperren zu verwalten haben, und ich möchte diese Fälle mit Test decken. Macht das Sinn, und wie würde man gehen über es zu tun? Es ist schwierig, weil der einzige Weg, um die Klasse arbeitet, um zu überprüfen, um Setup sehr komplizierte Einfädeln Szenarien ist, und selbst dann gibt es keine guten Weg für ein Leck von einem kritischen Abschnitt in Win32 zu testen. Gibt es einen direkteren Weg, um sicherzustellen, dass es funktioniert richtig?
Hier ist der Code:
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) {} ;
}}
Lösung
Hier sind drei Optionen und persönlich favorisieren ich die letzten ...
- Sie könnten eine ‚kritischen Abschnitt Fabrik‘ Schnittstelle erstellen, die Ihr Konstruktor übergeben werden kann. Dies würde Funktionen, die die API-Level-Funktionen eingewickelt, die Sie verwenden müssen. Sie könnten dann diese Schnittstelle bis verspotten und die Mock auf den Code, wenn sie unter Test bestehen, und Sie können sicher sein, dass die richtigen API-Funktionen aufgerufen werden. Sie würden im Allgemeinen, haben auch einen Konstruktor, der diese Schnittstelle nicht nehmen hat und dass stattdessen mit einer statischen Instanz der Fabrik initialisiert selbst, die direkt an die API genannt. Normale Erstellung der Objekte nicht betroffen (wie Sie sie eine Standardimplementierung verwenden), aber Sie können Instrument, wenn im Test. Dies ist die Standard-Dependency Injection Route und führt Sie href="http://accu.org/index.php/journals/1420" rel="nofollow noreferrer"> parametrisieren von oben
- Alternativ können Sie versuchen, die API verspotten von unten aus ... Vor langer Zeit sah ich in die Prüfung dieser Art von Low Level API-Nutzung mit API Einhaken; die Idee ist, dass, wenn ich die tatsächlichen Win32-API-Aufrufe hakte ich könnte eine ‚mock API-Schicht‘ entwickeln, die in der gleichen Weise wie normalere Mock Objekte verwendet werden würde, aber von oben „von unten parametrisieren“ statt parametrisieren auf würde verlassen. Während dies gearbeitet, und ich habe einen ziemlich langen Weg in das Projekt, es war sehr komplex, um sicherzustellen, dass Sie nur den im Test befindlichen Code verspotteten. Das Gute an diesem Ansatz war, dass ich die API dazu führen könnte, ruft unter kontrollierten Bedingungen in meinem Test nicht; dies erlaubt mir Ausfall Pfade zu testen, die sonst sehr schwer zu trainieren.
- Der dritte Ansatz ist zu akzeptieren, dass einige Code nicht prüfbar mit angemessenen Ressourcen sind und dass Dependency Injection ist nicht immer geeignet. Machen Sie den Code so einfach wie möglich, Augapfel es, Schreibtests für die Bits, die Sie und bewegen kann. Dies ist, was ich neige dazu, in Situationen wie diese zu tun.
Allerdings ....
Ich bin zweifelhaft Ihre Designwahl. Zum einen ist es zu viel in der Klasse (IMHO) geht. Die Referenzzählung und die Verriegelungs sind orthogonal. Ich würde sie auseinandergerissen, so dass ich eine einfache Klasse hatte die kritischen Abschnitt Management tat und dann darauf gebaut Ich fand ich wirklich Referenzzählung benötigt ... Zweitens gibt es die Referenzzählung und das Design Ihrer Sperrfunktionen; anstatt ein Objekt zurückkehrt, der die Sperre in seiner dtor veröffentlicht warum nicht einfach ein Objekt, das Sie auf dem Stapel erstellen scoped Sperre zu erstellen. Dies würde viel von der Komplexität entfernen. In der Tat könnte man mit einem kritischen Abschnitt Klasse am Ende, die so einfach wie das ist:
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);
}
Welche paßt zu meiner Vorstellung von dieser Art von Code einfach genug zu sein Augapfel, anstatt der Einführung komplexen Tests ...
Sie könnten dann eine scoped Sperrklasse haben wie:
CCriticalSection::Owner::Owner(
ICriticalSection &crit)
: m_crit(crit)
{
m_crit.Enter();
}
CCriticalSection::Owner::~Owner()
{
m_crit.Leave();
}
Sie würden es so verwenden
void MyClass::DoThing()
{
ICriticalSection::Owner lock(m_criticalSection);
// We're locked whilst 'lock' is in scope...
}
Natürlich mein Code nicht TryEnter()
verwenden oder etwas komplexer zu tun, aber es gibt nichts, Ihre einfache RAII-Klassen zu stoppen mehr zu tun; aber meiner Meinung nach, ich denke TryEnter()
eigentlich sehr selten erforderlich ist.