2010-04-09 3 views
11

비웃음, 나는 일반적으로했습니다 같은 식으로 내 자신의 조롱 솔루션을 해킹하고 거의 모든 것을 템플릿으로 구현해야하기 때문에 좌절감을 느꼈습니다. 그리고 내가 찾고있는 것을 성취하기위한 많은 보편적 인 코드입니다.내가 부스트 : : 단위 테스트를위한 테스트 라이브러리를 사용하고 있습니다 : 부스트와 함께 테스트

Ad-hoc 방식보다 Boost :: Test를 사용하여 코드를 조롱하는 좋은 방법이 있습니까?

여러 사람들이 Google Mock을 추천하는 것을 보았지만 기능이 virtual이 아닌 경우이를 피하기 위해 많은 해킹이 필요합니다.

오 : 마지막으로 한 가지. 특정 코드 조각이 호출되었다는 주장은 필요하지 않습니다. Windows API 함수에서 일반적으로 반환되는 데이터를 주입 할 수 있어야합니다.

EDIT :

클래스를 피 시험 :이 클래스

#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) { 
    }; 
}; 

}} 

테스트 :

#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

Windows API 호출을 조롱하려면 가상 함수 만들기에 대해 걱정하지 않아야합니다. 왜 이것을 피합니까? –

+1

@epronk : 그렇게 할 이유가 없기 때문에 - 현재의 방법보다 나쁩니다. 최소한 템플릿에는 런타임 불이익이 없습니다. –

+0

그래서 테스트가 정말 빨라졌습니다. 테스트를 컴파일하고 링크하는 데 얼마나 걸리나요? –

답변

6

않도록하여 여기에 예시 클래스 세트 및 시험 I는 그것을 가지고있다 당신이 가능한 한 빨리 실행해야하는 코드의 핵심 부분을 가지고 있지 않다면이 목적을위한이 템플릿 구조. 성능상의 이유로 가상을 피하려면 차이를 측정하십시오.

실제로 차이가 나는 곳에서만 템플리트 구성을 사용하십시오.

Google 모크 사용해보기. EXPECT_CALL은 정말 강력하고 커스텀 모의를 작성하는 것과 비교하여 많은 코드 시간을 절약합니다.

가짜와 모의라는 용어는 다른 의미로 사용하지 마십시오.

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

# 1 왜 템플릿을 피해야합니까? 필자가 테스트중인 코드의 절반은 이미 어쨌든 템플릿이기 때문에 런타임 다형성을 정의하여 추가 상속 골칫거리를 만들지 않는 이유를 알 수 없습니다. # 2 : Google Mock의 'EXPECT_CALL'테스트 데이터를 어떻게 제공합니까? 솔직히 말해서, 나는 함수가 불려도 상관 없다. 난 그냥 테스트 데이터를 삽입 할 수 있어야합니다. # 3 좋아, 나는 그 의미를 혼합하는 것을 피하려고 노력할 것이다. 차이점을 설명해 주시겠습니까? –

+0

@epronk : 템플릿의 경우 Google Mock에서 아이디어를 얻었 음을 알아야합니다. http://code.google.com/p/googlemock/wiki/CookBook#Mocking_Nonvirtual_Methods –

+0

mock 및 기대를 설정하는 데 관심이 없습니까? 가상이 아닌 메소드를 조롱하는이 기술에는 아무런 문제가 없지만 잘못된 컨텍스트에서 적용하면 안됩니다. –

2

면책 조항 : 내가 직접 그 API 기능을 조롱 할 수 조롱 프레임 워크를 사용하는 것이 좋습니다 HippoMocks

의 저자입니다. HippoMocks는 일반 C 함수를 모의 처리 할 수 ​​있으며 Windows API 함수를 직접 조롱하는 데 문제가 없어야합니다. 오늘 밤에 시험을 치고 실행되는지 알아 보겠습니다.

는 여전히

14

FWIW :-) 답변을 읽고 희망, 난 그냥 새로운 가로 질러 (c. 2011)라는 모의 프레임 워크를 "turtle은"그이 부스트 :: 테스트를 보완하기 위해 설계되었습니다. 그것은 소스 포지에 있습니다. 저는 프로젝트에서 조롱 거리를 시작하고 있습니다. 거북이가 제대로 작동하지 않는 한 내 첫 번째 선택이 될 것입니다.

+3

나는 Turtle (http://sourceforge.net/apps/mediawiki/turtle/index.php)의 외모도 좋아한다. –

관련 문제