См. Ниже для FindFirstFile / FindNextFile / FindClose пример
Я использую googlemock . Для внешнего API я обычно создаю интерфейсный класс. Предположим, я собирался позвонить fopen, fwrite, fclose
class FileIOInterface {
public:
~virtual FileIOInterface() {}
virtual FILE* Open(const char* filename, const char* mode) = 0;
virtual size_t Write(const void* data, size_t size, size_t num, FILE* file) = 0;
virtual int Close(FILE* file) = 0;
};
Фактическая реализация будет такой
class FileIO : public FileIOInterface {
public:
virtual FILE* Open(const char* filename, const char* mode) {
return fopen(filename, mode);
}
virtual size_t Write(const void* data, size_t size, size_t num, FILE* file) {
return fwrite(data, size, num, file);
}
virtual int Close(FILE* file) {
return fclose(file);
}
};
Затем, используя googlemock, я создаю класс MockFileIO, подобный этому
class MockFileIO : public FileIOInterface {
public:
virtual ~MockFileIO() { }
MOCK_MEHTOD2(Open, FILE*(const char* filename, const char* mode));
MOCK_METHOD4(Write, size_t(const void* data, size_t size, size_t num, FILE* file));
MOCK_METHOD1(Close, int(FILE* file));
}
Это облегчает написание тестов. Мне не нужно предоставлять тестовую реализацию Open / Write / Close. GoogleMock обрабатывает это для меня. как в (обратите внимание, я использую googletest для моей платформы модульного тестирования.)
Предположим, у меня есть такая функция, которая требует тестирования
// Writes a file, returns true on success.
bool WriteFile(FileIOInterface fio, const char* filename, const void* data, size_size) {
FILE* file = fio.Open(filename, "wb");
if (!file) {
return false;
}
if (fio.Write(data, 1, size, file) != size) {
return false;
}
if (fio.Close(file) != 0) {
return false;
}
return true;
}
А вот и тесты.
TEST(WriteFileTest, SuccessWorks) {
MockFileIO fio;
static char data[] = "hello";
const char* kName = "test";
File test_file;
// Tell the mock to expect certain calls and what to
// return on those calls.
EXPECT_CALL(fio, Open(kName, "wb")
.WillOnce(Return(&test_file));
EXPECT_CALL(fio, Write(&data, 1, sizeof(data), &test_file))
.WillOnce(Return(sizeof(data)));
EXPECT_CALL(file, Close(&test_file))
.WillOnce(Return(0));
EXPECT_TRUE(WriteFile(kName, &data, sizeof(data));
}
TEST(WriteFileTest, FailsIfOpenFails) {
MockFileIO fio;
static char data[] = "hello";
const char* kName = "test";
File test_file;
// Tell the mock to expect certain calls and what to
// return on those calls.
EXPECT_CALL(fio, Open(kName, "wb")
.WillOnce(Return(NULL));
EXPECT_FALSE(WriteFile(kName, &data, sizeof(data));
}
TEST(WriteFileTest, FailsIfWriteFails) {
MockFileIO fio;
static char data[] = "hello";
const char* kName = "test";
File test_file;
// Tell the mock to expect certain calls and what to
// return on those calls.
EXPECT_CALL(fio, Open(kName, "wb")
.WillOnce(Return(&test_file));
EXPECT_CALL(fio, Write(&data, 1, sizeof(data), &test_file))
.WillOnce(Return(0));
EXPECT_FALSE(WriteFile(kName, &data, sizeof(data));
}
TEST(WriteFileTest, FailsIfCloseFails) {
MockFileIO fio;
static char data[] = "hello";
const char* kName = "test";
File test_file;
// Tell the mock to expect certain calls and what to
// return on those calls.
EXPECT_CALL(fio, Open(kName, "wb")
.WillOnce(Return(&test_file));
EXPECT_CALL(fio, Write(&data, 1, sizeof(data), &test_file))
.WillOnce(Return(sizeof(data)));
EXPECT_CALL(file, Close(&test_file))
.WillOnce(Return(EOF));
EXPECT_FALSE(WriteFile(kName, &data, sizeof(data));
}
Мне не нужно было предоставлять тестовую реализацию fopen / fwrite / fclose. GoogleMock обрабатывает это для меня. Вы можете сделать макет строгим, если хотите. Строгий макет провалит тесты, если будет вызвана какая-либо функция, которая не ожидается, или если любая функция, которая ожидается, вызывается с неправильными аргументами. Googlemock предоставляет массу помощников и адаптеров, поэтому вам, как правило, не нужно писать много кода, чтобы заставить модель делать то, что вы хотите. Требуется несколько дней, чтобы изучить различные адаптеры, но если вы используете его часто, они быстро становятся второй натурой.
Вот пример использования FindFirstFile, FindNextFile, FindClose
Первый интерфейс
class FindFileInterface {
public:
virtual HANDLE FindFirstFile(
LPCTSTR lpFileName,
LPWIN32_FIND_DATA lpFindFileData) = 0;
virtual BOOL FindNextFile(
HANDLE hFindFile,
LPWIN32_FIND_DATA lpFindFileData) = 0;
virtual BOOL FindClose(
HANDLE hFindFile) = 0;
virtual DWORD GetLastError(void) = 0;
};
Тогда фактическая реализация
class FindFileImpl : public FindFileInterface {
public:
virtual HANDLE FindFirstFile(
LPCTSTR lpFileName,
LPWIN32_FIND_DATA lpFindFileData) {
return ::FindFirstFile(lpFileName, lpFindFileData);
}
virtual BOOL FindNextFile(
HANDLE hFindFile,
LPWIN32_FIND_DATA lpFindFileData) {
return ::FindNextFile(hFindFile, lpFindFileData);
}
virtual BOOL FindClose(
HANDLE hFindFile) {
return ::FindClose(hFindFile);
}
virtual DWORD GetLastError(void) {
return ::GetLastError();
}
};
Мак, использующий gmock
class MockFindFile : public FindFileInterface {
public:
MOCK_METHOD2(FindFirstFile,
HANDLE(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData));
MOCK_METHOD2(FindNextFile,
BOOL(HANDLE hFindFile, LPWIN32_FIND_DATA lpFindFileData));
MOCK_METHOD1(FindClose, BOOL(HANDLE hFindFile));
MOCK_METHOD0(GetLastError, DWORD());
};
Функция, которую мне нужно проверить.
DWORD PrintListing(FindFileInterface* findFile, const TCHAR* path) {
WIN32_FIND_DATA ffd;
HANDLE hFind;
hFind = findFile->FindFirstFile(path, &ffd);
if (hFind == INVALID_HANDLE_VALUE)
{
printf ("FindFirstFile failed");
return 0;
}
do {
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
_tprintf(TEXT(" %s <DIR>\n"), ffd.cFileName);
} else {
LARGE_INTEGER filesize;
filesize.LowPart = ffd.nFileSizeLow;
filesize.HighPart = ffd.nFileSizeHigh;
_tprintf(TEXT(" %s %ld bytes\n"), ffd.cFileName, filesize.QuadPart);
}
} while(findFile->FindNextFile(hFind, &ffd) != 0);
DWORD dwError = findFile->GetLastError();
if (dwError != ERROR_NO_MORE_FILES) {
_tprintf(TEXT("error %d"), dwError);
}
findFile->FindClose(hFind);
return dwError;
}
Юнит-тесты.
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::Return;
using ::testing::DoAll;
using ::testing::SetArgumentPointee;
// Some data for unit tests.
static WIN32_FIND_DATA File1 = {
FILE_ATTRIBUTE_NORMAL, // DWORD dwFileAttributes;
{ 123, 0, }, // FILETIME ftCreationTime;
{ 123, 0, }, // FILETIME ftLastAccessTime;
{ 123, 0, }, // FILETIME ftLastWriteTime;
0, // DWORD nFileSizeHigh;
123, // DWORD nFileSizeLow;
0, // DWORD dwReserved0;
0, // DWORD dwReserved1;
{ TEXT("foo.txt") }, // TCHAR cFileName[MAX_PATH];
{ TEXT("foo.txt") }, // TCHAR cAlternateFileName[14];
};
static WIN32_FIND_DATA Dir1 = {
FILE_ATTRIBUTE_DIRECTORY, // DWORD dwFileAttributes;
{ 123, 0, }, // FILETIME ftCreationTime;
{ 123, 0, }, // FILETIME ftLastAccessTime;
{ 123, 0, }, // FILETIME ftLastWriteTime;
0, // DWORD nFileSizeHigh;
123, // DWORD nFileSizeLow;
0, // DWORD dwReserved0;
0, // DWORD dwReserved1;
{ TEXT("foo.dir") }, // TCHAR cFileName[MAX_PATH];
{ TEXT("foo.dir") }, // TCHAR cAlternateFileName[14];
};
TEST(PrintListingTest, TwoFiles) {
const TCHAR* kPath = TEXT("c:\\*");
const HANDLE kValidHandle = reinterpret_cast<HANDLE>(1234);
MockFindFile ff;
EXPECT_CALL(ff, FindFirstFile(kPath, _))
.WillOnce(DoAll(SetArgumentPointee<1>(Dir1),
Return(kValidHandle)));
EXPECT_CALL(ff, FindNextFile(kValidHandle, _))
.WillOnce(DoAll(SetArgumentPointee<1>(File1),
Return(TRUE)))
.WillOnce(Return(FALSE));
EXPECT_CALL(ff, GetLastError())
.WillOnce(Return(ERROR_NO_MORE_FILES));
EXPECT_CALL(ff, FindClose(kValidHandle));
PrintListing(&ff, kPath);
}
TEST(PrintListingTest, OneFile) {
const TCHAR* kPath = TEXT("c:\\*");
const HANDLE kValidHandle = reinterpret_cast<HANDLE>(1234);
MockFindFile ff;
EXPECT_CALL(ff, FindFirstFile(kPath, _))
.WillOnce(DoAll(SetArgumentPointee<1>(Dir1),
Return(kValidHandle)));
EXPECT_CALL(ff, FindNextFile(kValidHandle, _))
.WillOnce(Return(FALSE));
EXPECT_CALL(ff, GetLastError())
.WillOnce(Return(ERROR_NO_MORE_FILES));
EXPECT_CALL(ff, FindClose(kValidHandle));
PrintListing(&ff, kPath);
}
TEST(PrintListingTest, ZeroFiles) {
const TCHAR* kPath = TEXT("c:\\*");
MockFindFile ff;
EXPECT_CALL(ff, FindFirstFile(kPath, _))
.WillOnce(Return(INVALID_HANDLE_VALUE));
PrintListing(&ff, kPath);
}
TEST(PrintListingTest, Error) {
const TCHAR* kPath = TEXT("c:\\*");
const HANDLE kValidHandle = reinterpret_cast<HANDLE>(1234);
MockFindFile ff;
EXPECT_CALL(ff, FindFirstFile(kPath, _))
.WillOnce(DoAll(SetArgumentPointee<1>(Dir1),
Return(kValidHandle)));
EXPECT_CALL(ff, FindNextFile(kValidHandle, _))
.WillOnce(Return(FALSE));
EXPECT_CALL(ff, GetLastError())
.WillOnce(Return(ERROR_ACCESS_DENIED));
EXPECT_CALL(ff, FindClose(kValidHandle));
PrintListing(&ff, kPath);
}
Мне не нужно было реализовывать какие-либо фиктивные функции.