2010-04-09 11 views
11

estoy usando el impulso :: biblioteca de prueba para las pruebas unitarias, y he sido, en general Hacking mis propias soluciones burlones que miran algo como esto:Burlándose con Boost :: Prueba

//In header for clients 
struct RealFindFirstFile 
{ 
    static HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) { 
     return FindFirstFile(lpFileName, lpFindFileData); 
    }; 
}; 

template <typename FirstFile_T = RealFindFirstFile> 
class DirectoryIterator { 
//.. Implementation 
} 

//In unit tests (cpp) 
#define THE_ANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING 42 
struct FakeFindFirstFile 
{ 
    static HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) { 
     return THE_ANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING; 
    }; 
}; 
BOOST_AUTO_TEST_CASE(MyTest) 
{ 
    DirectoryIterator<FakeFindFirstFile> LookMaImMocked; 
    //Test 
} 

I Crecí frustrado con esto porque requiere que implemente casi todo como una plantilla, y es un código repetitivo para lograr lo que estoy buscando.

¿Existe un buen método para burlar código usando Boost :: Test sobre mi método Ad-hoc?

He visto a varias personas recomendar Google Mock, pero requiere un montón de hacks feos si sus funciones no son virtual, lo que me gustaría evitar.

Oh: Una última cosa. No necesito afirmaciones de que se haya llamado a un fragmento de código en particular. Simplemente necesito poder inyectar datos que normalmente serían devueltos por las funciones API de Windows.

EDIT: He aquí un conjunto de ejemplo de la clase y las pruebas que tengo para ella:

Las clases bajo prueba:

#include <list> 
#include <string> 
#include <boost/noncopyable.hpp> 
#include <boost/make_shared.hpp> 
#include <boost/iterator/iterator_facade.hpp> 
#include <Windows.h> 
#include <Shlwapi.h> 
#pragma comment(lib, "shlwapi.lib") 
#include "../Exception.hpp" 

namespace WindowsAPI { namespace FileSystem { 

//For unit testing 
struct RealFindXFileFunctions 
{ 
    HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) { 
     return FindFirstFile(lpFileName, lpFindFileData); 
    }; 
    BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData) { 
     return FindNextFile(hFindFile, lpFindFileData); 
    }; 
    BOOL Close(HANDLE hFindFile) { 
     return FindClose(hFindFile); 
    }; 
}; 

class Win32FindData { 
    WIN32_FIND_DATA internalData; 
    std::wstring rootPath; 
public: 
    Win32FindData(const std::wstring& root, const WIN32_FIND_DATA& data) : 
     rootPath(root), internalData(data) {}; 
    DWORD GetAttributes() const { 
     return internalData.dwFileAttributes; 
    }; 
    bool IsDirectory() const { 
     return (internalData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; 
    }; 
    bool IsFile() const { 
     return !IsDirectory(); 
    }; 
    unsigned __int64 GetSize() const { 
     ULARGE_INTEGER intValue; 
     intValue.LowPart = internalData.nFileSizeLow; 
     intValue.HighPart = internalData.nFileSizeHigh; 
     return intValue.QuadPart; 
    }; 
    std::wstring GetFolderPath() const { 
     return rootPath; 
    }; 
    std::wstring GetFileName() const { 
     return internalData.cFileName; 
    }; 
    std::wstring GetFullFileName() const { 
     return rootPath + L"\\" + internalData.cFileName; 
    }; 
    std::wstring GetShortFileName() const { 
     return internalData.cAlternateFileName; 
    }; 
    FILETIME GetCreationTime() const { 
     return internalData.ftCreationTime; 
    }; 
    FILETIME GetLastAccessTime() const { 
     return internalData.ftLastAccessTime; 
    }; 
    FILETIME GetLastWriteTime() const { 
     return internalData.ftLastWriteTime; 
    }; 
}; 

template <typename FindXFileFunctions_T> 
class BasicEnumerationMethod : public boost::noncopyable { 
protected: 
    WIN32_FIND_DATAW currentData; 
    HANDLE hFind; 
    std::wstring currentDirectory; 
    FindXFileFunctions_T FindFileFunctions; 
    BasicEnumerationMethod(FindXFileFunctions_T functor) : 
     hFind(INVALID_HANDLE_VALUE), 
     FindFileFunctions(functor) {}; 
    void IncrementCurrentDirectory() { 
     if (hFind == INVALID_HANDLE_VALUE) return; 
     BOOL success = 
      FindFileFunctions.FindNext(hFind, &currentData); 
     if (success) 
      return; 
     DWORD error = GetLastError(); 
     if (error == ERROR_NO_MORE_FILES) { 
      FindFileFunctions.Close(hFind); 
      hFind = INVALID_HANDLE_VALUE; 
     } else { 
      WindowsApiException::Throw(error); 
     } 
    }; 
    virtual ~BasicEnumerationMethod() { 
     if (hFind != INVALID_HANDLE_VALUE) 
      FindFileFunctions.Close(hFind); 
    }; 
public: 
    bool equal(const BasicEnumerationMethod<FindXFileFunctions_T>& other) const { 
     if (this == &other) 
      return true; 
     return hFind == other.hFind; 
    }; 
    Win32FindData dereference() { 
     return Win32FindData(currentDirectory, currentData); 
    }; 
}; 

template <typename FindXFileFunctions_T> 
class BasicNonRecursiveEnumeration : public BasicEnumerationMethod<FindXFileFunctions_T> 
{ 
public: 
    BasicNonRecursiveEnumeration(FindXFileFunctions_T functor = FindXFileFunctions_T()) 
     : BasicEnumerationMethod<FindXFileFunctions_T>(functor) {}; 
    BasicNonRecursiveEnumeration(const std::wstring& pathSpec, 
      FindXFileFunctions_T functor = FindXFileFunctions_T()) 
      : BasicEnumerationMethod<FindXFileFunctions_T>(functor) { 
     std::wstring::const_iterator lastSlash = 
      std::find(pathSpec.rbegin(), pathSpec.rend(), L'\\').base(); 
     if (lastSlash != pathSpec.begin()) 
      currentDirectory.assign(pathSpec.begin(), lastSlash-1); 
     hFind = FindFileFunctions.FindFirst(pathSpec.c_str(), &currentData); 
     if (hFind == INVALID_HANDLE_VALUE 
      && GetLastError() != ERROR_PATH_NOT_FOUND 
      && GetLastError() != ERROR_FILE_NOT_FOUND) 
      WindowsApiException::ThrowFromLastError(); 
     while (hFind != INVALID_HANDLE_VALUE && (!wcscmp(currentData.cFileName, L".") || 
       !wcscmp(currentData.cFileName, L".."))) { 
      IncrementCurrentDirectory(); 
     } 
    }; 
    void increment() { 
     IncrementCurrentDirectory(); 
    }; 
}; 

typedef BasicNonRecursiveEnumeration<RealFindXFileFunctions> NonRecursiveEnumeration; 

template <typename FindXFileFunctions_T> 
class BasicRecursiveEnumeration : public BasicEnumerationMethod<FindXFileFunctions_T> 
{ 
    //Implementation ommitted 
}; 

typedef BasicRecursiveEnumeration<RealFindXFileFunctions> RecursiveEnumeration; 

struct AllResults 
{ 
    bool operator()(const Win32FindData&) { 
     return true; 
    }; 
}; 

struct FilesOnly 
{ 
    bool operator()(const Win32FindData& arg) { 
     return arg.IsFile(); 
    }; 
}; 

template <typename Filter_T = AllResults, typename Recurse_T = NonRecursiveEnumeration> 
class DirectoryIterator : 
    public boost::iterator_facade< 
     DirectoryIterator<Filter_T, Recurse_T>, 
     Win32FindData, 
     std::input_iterator_tag, 
     Win32FindData 
    > 
{ 
    friend class boost::iterator_core_access; 
    boost::shared_ptr<Recurse_T> impl; 
    Filter_T filter; 
    void increment() { 
     do { 
      impl->increment(); 
     } while (! filter(impl->dereference())); 
    }; 
    bool equal(const DirectoryIterator& other) const { 
     return impl->equal(*other.impl); 
    }; 
    Win32FindData dereference() const { 
     return impl->dereference(); 
    }; 
public: 
    DirectoryIterator(Filter_T functor = Filter_T()) : 
     impl(boost::make_shared<Recurse_T>()), 
     filter(functor) { 
    }; 
    explicit DirectoryIterator(const std::wstring& pathSpec, Filter_T functor = Filter_T()) : 
     impl(boost::make_shared<Recurse_T>(pathSpec)), 
     filter(functor) { 
    }; 
}; 

}} 

Las pruebas de esta clase:

#include <queue> 
#include "../WideCharacterOutput.hpp" 
#include <boost/test/unit_test.hpp> 
#include "../../WindowsAPI++/FileSystem/Enumerator.hpp" 
using namespace WindowsAPI::FileSystem; 

struct SimpleFakeFindXFileFunctions 
{ 
    static std::deque<WIN32_FIND_DATAW> fakeData; 
    static std::wstring insertedFileName; 

    HANDLE FindFirst(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData) { 
     insertedFileName.assign(lpFileName); 
     if (fakeData.empty()) { 
      SetLastError(ERROR_PATH_NOT_FOUND); 
      return INVALID_HANDLE_VALUE; 
     } 
     *lpFindFileData = fakeData.front(); 
     fakeData.pop_front(); 
     return reinterpret_cast<HANDLE>(42); 
    }; 
    BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW lpFindFileData) { 
     BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile); 
     if (fakeData.empty()) { 
      SetLastError(ERROR_NO_MORE_FILES); 
      return 0; 
     } 
     *lpFindFileData = fakeData.front(); 
     fakeData.pop_front(); 
     return 1; 
    }; 
    BOOL Close(HANDLE hFindFile) { 
     BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile); 
     return 1; 
    }; 
}; 

std::deque<WIN32_FIND_DATAW> SimpleFakeFindXFileFunctions::fakeData; 
std::wstring SimpleFakeFindXFileFunctions::insertedFileName; 

struct ErroneousFindXFileFunctions 
{ 
    virtual HANDLE FindFirst(LPCWSTR, LPWIN32_FIND_DATAW) { 
     return reinterpret_cast<HANDLE>(42); 
    }; 
    virtual BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW) { 
     BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile); 
     return 1; 
    }; 
    virtual BOOL Close(HANDLE hFindFile) { 
     BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile); 
     return 1; 
    }; 
}; 

struct ErroneousFindXFileFunctionFirst : public ErroneousFindXFileFunctions 
{ 
    HANDLE FindFirst(LPCWSTR, LPWIN32_FIND_DATAW) { 
     SetLastError(ERROR_ACCESS_DENIED); 
     return INVALID_HANDLE_VALUE; 
    }; 
}; 

struct ErroneousFindXFileFunctionNext : public ErroneousFindXFileFunctions 
{ 
    BOOL FindNext(HANDLE hFindFile, LPWIN32_FIND_DATAW) { 
     BOOST_CHECK_EQUAL(reinterpret_cast<HANDLE>(42), hFindFile); 
     SetLastError(ERROR_INVALID_PARAMETER); 
     return 0; 
    }; 
}; 

struct DirectoryIteratorTestsFixture 
{ 
    typedef SimpleFakeFindXFileFunctions fakeFunctor; 
    DirectoryIteratorTestsFixture() { 
     WIN32_FIND_DATAW test; 
     wcscpy_s(test.cFileName, L"."); 
     wcscpy_s(test.cAlternateFileName, L"."); 
     test.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; 
     GetSystemTimeAsFileTime(&test.ftCreationTime); 
     test.ftLastWriteTime = test.ftCreationTime; 
     test.ftLastAccessTime = test.ftCreationTime; 
     test.nFileSizeHigh = 0; 
     test.nFileSizeLow = 0; 
     fakeFunctor::fakeData.push_back(test); 

     wcscpy_s(test.cFileName, L".."); 
     wcscpy_s(test.cAlternateFileName, L".."); 
     fakeFunctor::fakeData.push_back(test); 

     wcscpy_s(test.cFileName, L"File.txt"); 
     wcscpy_s(test.cAlternateFileName, L"FILE.TXT"); 
     test.nFileSizeLow = 1024; 
     test.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; 
     fakeFunctor::fakeData.push_back(test); 

     wcscpy_s(test.cFileName, L"System32"); 
     wcscpy_s(test.cAlternateFileName, L"SYSTEM32"); 
     test.nFileSizeLow = 0; 
     test.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; 
     fakeFunctor::fakeData.push_back(test); 
    }; 
    ~DirectoryIteratorTestsFixture() { 
     fakeFunctor::fakeData.clear(); 
    }; 
}; 

BOOST_FIXTURE_TEST_SUITE(DirectoryIteratorTests, DirectoryIteratorTestsFixture) 

BOOST_AUTO_TEST_CASE(BasicEnumeration) 
{ 
    typedef DirectoryIterator<AllResults 
     ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType; 
    testType begin(L"C:\\Windows\\*"); 
    testType end; 
    BOOST_CHECK_EQUAL(fakeFunctor::insertedFileName, L"C:\\Windows\\*"); 
    BOOST_CHECK(begin->GetFolderPath() == L"C:\\Windows"); 
    BOOST_CHECK(begin->GetFileName() == L"File.txt"); 
    BOOST_CHECK(begin->GetFullFileName() == L"C:\\Windows\\File.txt"); 
    BOOST_CHECK(begin->GetShortFileName() == L"FILE.TXT"); 
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024); 
    BOOST_CHECK(begin->IsFile()); 
    BOOST_CHECK(begin != end); 
    begin++; 
    BOOST_CHECK(begin->GetFileName() == L"System32"); 
    BOOST_CHECK(begin->GetFullFileName() == L"C:\\Windows\\System32"); 
    BOOST_CHECK(begin->GetShortFileName() == L"SYSTEM32"); 
    BOOST_CHECK_EQUAL(begin->GetSize(), 0); 
    BOOST_CHECK(begin->IsDirectory()); 
    begin++; 
    BOOST_CHECK(begin == end); 
} 

BOOST_AUTO_TEST_CASE(NoRootDirectories) 
{ 
    typedef DirectoryIterator<AllResults 
     ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType; 
    fakeFunctor::fakeData.pop_front(); 
    fakeFunctor::fakeData.pop_front(); 
    testType begin(L"C:\\Windows\\*"); 
    testType end; 
    BOOST_CHECK_EQUAL(fakeFunctor::insertedFileName, L"C:\\Windows\\*"); 
    BOOST_CHECK(begin->GetFolderPath() == L"C:\\Windows"); 
    BOOST_CHECK(begin->GetFileName() == L"File.txt"); 
    BOOST_CHECK(begin->GetFullFileName() == L"C:\\Windows\\File.txt"); 
    BOOST_CHECK(begin->GetShortFileName() == L"FILE.TXT"); 
    BOOST_CHECK_EQUAL(begin->GetSize(), 1024); 
    BOOST_CHECK(begin->IsFile()); 
    BOOST_CHECK(begin != end); 
    begin++; 
    BOOST_CHECK(begin->GetFileName() == L"System32"); 
    BOOST_CHECK(begin->GetFullFileName() == L"C:\\Windows\\System32"); 
    BOOST_CHECK(begin->GetShortFileName() == L"SYSTEM32"); 
    BOOST_CHECK_EQUAL(begin->GetSize(), 0); 
    BOOST_CHECK(begin->IsDirectory()); 
    begin++; 
    BOOST_CHECK(begin == end); 
} 

BOOST_AUTO_TEST_CASE(Empty1) 
{ 
    fakeFunctor::fakeData.clear(); 
    typedef DirectoryIterator<AllResults 
     ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType; 
    testType begin(L"C:\\Windows\\*"); 
    testType end; 
    BOOST_CHECK(begin == end); 
} 

BOOST_AUTO_TEST_CASE(Empty2) 
{ 
    fakeFunctor::fakeData.erase(fakeFunctor::fakeData.begin() + 2, fakeFunctor::fakeData.end()); 
    typedef DirectoryIterator<AllResults 
     ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType; 
    testType begin(L"C:\\Windows\\*"); 
    testType end; 
    BOOST_CHECK(begin == end); 
} 

BOOST_AUTO_TEST_CASE(Exceptions) 
{ 
    typedef DirectoryIterator<AllResults,BasicNonRecursiveEnumeration<ErroneousFindXFileFunctionFirst> > 
     firstFailType; 
    BOOST_CHECK_THROW(firstFailType(L"C:\\Windows\\*"), WindowsAPI::ErrorAccessDeniedException); 
    typedef DirectoryIterator<AllResults,BasicNonRecursiveEnumeration<ErroneousFindXFileFunctionNext> > 
     nextFailType; 
    nextFailType constructedOkay(L"C:\\Windows\\*"); 
    BOOST_CHECK_THROW(constructedOkay++, WindowsAPI::ErrorInvalidParameterException); 
} 

BOOST_AUTO_TEST_CASE(CorrectDestruction) 
{ 
    typedef DirectoryIterator<AllResults 
     ,BasicNonRecursiveEnumeration<SimpleFakeFindXFileFunctions> > testType; 
    testType begin(L"C:\\Windows\\*"); 
    testType end; 
    BOOST_CHECK(begin != end); 
} 

BOOST_AUTO_TEST_SUITE_END() 
+0

Si quiere burlarse de las llamadas a la API de Windows, no me preocuparía hacer que las funciones sean virtuales. ¿Por qué evitas esto? –

+1

@epronk: Simplemente porque no hay ninguna razón para hacerlo, está peor que mi método actual. Al menos con las plantillas no hay penalización de tiempo de ejecución. –

+0

Entonces, sus pruebas se ejecutan realmente rápido. ¿Cuánto tiempo lleva compilar y vincular la prueba? –

Respuesta

6

evitar el uso esta construcción de plantilla para este propósito a menos que tengas una pieza de código realmente central que deba ejecutarse lo más rápido posible. Si desea evitar virtual por razones de rendimiento, mida la diferencia.

Utilice la construcción de la plantilla solo en lugares donde realmente hace la diferencia.

Pruebe Google Mock. EXPECT_CALL es realmente potente y ahorra mucho tiempo de código en comparación con la creación de un simulacro personalizado.

Evita mezclar los términos Fake y Mock porque tienen un significado diferente.

DirectoryIterator<FakeFindFirstFile> LookMaImMocked; // is it a fake or a mock? 
+1

# 1 ¿Por qué debería evitar las plantillas? La mitad del código que estoy probando ya es una plantilla de todos modos, así que no veo por qué me molestaría crear dolores de cabeza de herencia adicionales definiendo también el polimorfismo de tiempo de ejecución. # 2: ¿Cómo proporciono datos de prueba para el 'EXPECT_CALL' de Google Mock? Honestamente, no me importa que se llame la función. Solo quiero poder inyectar datos de prueba. # 3 Ok, intentaré evitar mezclar los significados. ¿Te importaría explicar la diferencia? –

+0

@epronk: en cuanto a las plantillas, creo que debes saber que obtuve la idea de Google Mock: http://code.google.com/p/googlemock/wiki/CookBook#Mocking_Nonvirtual_Methods –

+0

Estás pidiendo ayuda con burlas y no están interesados ​​en establecer expectativas? No hay nada de malo en esta técnica de burlarse de los métodos no virtuales, pero simplemente no debes aplicarla en el contexto equivocado. –

2

responsabilidad: yo soy el autor de HippoMocks

sugiere emplear un marco de burla que pueden burlarse directamente esas funciones de la API. HippoMocks puede simular funciones simples de C y no debería tener problemas para burlarse de las funciones de la API de Windows directamente. Le daré una prueba esta noche y veré si funciona.

Espero que todavía está leyendo las respuestas :-)

14

Fwiw, sólo encontré con una nueva (c. 2011) marco maqueta llamada "turtle" que está diseñado para complementar impulso :: prueba. Está en sourceforge. Estoy empezando a seguir el camino de la burla en un proyecto y la tortuga será mi primera opción a menos que simplemente no funcione bien.

+3

Me gusta el aspecto de Turtle (http://sourceforge.net/apps/mediawiki/turtle/index.php) también. –