2010-04-09 33 views
11

Tôi đang sử dụng Boost :: Kiểm tra thư viện cho kiểm tra đơn vị, và tôi đã nói chung được hack lên các giải pháp chế nhạo của riêng tôi mà giống như thế này:Mocking với Boost :: Kiểm tra

//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 
} 

tôi đã trở nên thất vọng với điều này bởi vì nó đòi hỏi tôi phải thực hiện gần như tất cả mọi thứ như một mẫu, và nó là rất nhiều mã soạn sẵn để đạt được những gì tôi đang tìm kiếm.

Có cách nào tốt để tạo mã bằng cách sử dụng Boost :: Test over my Ad-hoc method?

Tôi đã thấy một số người đề xuất Google Mock, nhưng nó đòi hỏi rất nhiều hack xấu xí nếu chức năng của bạn không phải là virtual, mà tôi muốn tránh.

Oh: Một điều cuối cùng. Tôi không cần khẳng định rằng một đoạn mã cụ thể được gọi. Tôi chỉ cần có thể tiêm dữ liệu mà thông thường sẽ được trả về bởi các hàm Windows API.

EDIT: Đây là một bộ ví dụ lớp và các bài kiểm tra tôi có cho nó:

Các lớp dưới kiểm tra:

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

}} 

Các xét nghiệm cho lớp này:

#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

Nếu bạn muốn thử các cuộc gọi Windows API, tôi sẽ không lo lắng về việc thực hiện các chức năng ảo. Tại sao bạn tránh điều này? –

+1

@epronk: Đơn giản vì không có lý do gì để làm như vậy - nó tệ hơn phương pháp hiện tại của tôi. Ít nhất với các mẫu không có hình phạt thời gian chạy. –

+0

Vì vậy, các bài kiểm tra của bạn chạy rất nhanh. Biên soạn và liên kết thử nghiệm mất bao lâu? –

Trả lời

6

Tránh sử dụng xây dựng mẫu này cho mục đích này trừ khi bạn có một số đoạn mã thực sự cốt lõi phải chạy nhanh nhất có thể. Nếu bạn muốn tránh ảo vì lý do hiệu suất, hãy đo sự khác biệt.

Chỉ sử dụng việc tạo mẫu ở những nơi thực sự tạo sự khác biệt.

Dùng thử Google Mock. EXPECT_CALL thực sự mạnh mẽ và tiết kiệm rất nhiều thời gian mã so với viết một mô hình tùy chỉnh.

Tránh trộn cụm từ Fake và Mock vì chúng có ý nghĩa khác nhau.

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

# 1 Tại sao tôi nên tránh các mẫu? Một nửa mã tôi đang thử nghiệm đã là một mẫu anyway, vì vậy tôi không thấy lý do tại sao tôi muốn bận tâm tạo ra các cơn đau đầu thừa kế bổ sung bằng cách xác định đa hình thời gian chạy là tốt. # 2: Làm cách nào để cung cấp dữ liệu thử nghiệm cho 'EXPECT_CALL' của Google Mock? Thành thật mà nói, tôi không quan tâm đến chức năng được gọi. Tôi chỉ muốn có thể tiêm dữ liệu thử nghiệm. # 3 Ok, tôi sẽ cố gắng tránh trộn lẫn ý nghĩa. Bạn có quan tâm để giải thích sự khác biệt? –

+0

@epronk: Đối với các mẫu, tôi nghĩ bạn nên biết rằng tôi đã có ý tưởng từ Google Mock: http://code.google.com/p/googlemock/wiki/CookBook#Mocking_Nonvirtual_Methods –

+0

Bạn đang yêu cầu trợ giúp với mocks và không quan tâm đến việc thiết lập kỳ vọng? Không có gì sai với kỹ thuật mocking phi ảo này, nhưng bạn không nên áp dụng nó trong bối cảnh sai. –

2

Disclaimer: Tôi là tác giả của HippoMocks

tôi đề nghị sử dụng một khuôn khổ mocking có thể trực tiếp thử những chức năng API. HippoMocks có thể giả lập các hàm C đơn giản và không gặp vấn đề gì với việc tạo ra các hàm Windows API một cách trực tiếp. Tôi sẽ cho nó chạy thử nghiệm tối nay và xem nó có chạy không.

Hy vọng bạn vẫn đang đọc trả lời :-)

14

Fwiw, tôi chỉ chạy trên một mới (c. 2011) khuôn khổ giả gọi là "turtle" được thiết kế để bổ sung tăng :: thử nghiệm. Đó là trên sourceforge. Tôi chỉ mới bắt đầu con đường nhạo báng trên một dự án và con rùa sẽ là lựa chọn đầu tiên của tôi trừ khi nó không hoạt động đúng.

+3

Tôi thích giao diện của Turtle (http://sourceforge.net/apps/mediawiki/turtle/index.php). –

Các vấn đề liên quan