Véase más abajo para FindFirstFile FindNextFile/ejemplo/FindClose
utilizo googlemock. Para una API externa, generalmente creo una clase de interfaz. Asumo que iba a llamar a 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;
};
La implementación real sería este
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);
}
};
Luego, utilizando googlemock hago una clase MockFileIO como esto
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));
}
Esto hace escribir las pruebas fácil. No tengo que proporcionar una implementación de prueba de Abrir/Escribir/Cerrar. googlemock maneja eso para mí. como en. (Nota que utilizo para mi googletest marco de pruebas de unidad.)
Supongamos que tengo una función como ésta, que necesita pruebas
// 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;
}
Y aquí está la prueba.
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));
}
No tuve que proporcionar una implementación de prueba de fopen/fwrite/fclose. googlemock maneja esto para mí. Puedes hacer el simulacro estricto si quieres. Una simulación estricta fallará las pruebas si se llama a alguna función que no se espera o si se llama a alguna función que se espera con los argumentos incorrectos. Googlemock proporciona una gran cantidad de ayudantes y adaptadores por lo que generalmente no es necesario escribir mucho código para que el simulacro haga lo que usted desea.Lleva unos días aprender los diferentes adaptadores, pero si lo usa a menudo se convierten en una segunda naturaleza.
Aquí hay un ejemplo usando FindFirstFile, FindNextFile, FindClose
En primer lugar la interfaz
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;
};
A continuación, la aplicación efectiva
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();
}
};
la maqueta usando 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());
};
La función que necesito probar.
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;
}
La unidad prueba.
#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);
}
No tuve que implementar ninguna de las funciones simuladas.
Esto suena como un caso de "si la única herramienta que tengo es un martillo, cada problema parece un clavo". Quizás puede ignorar TDD cuando juega con la API y cuando obtiene algún tipo de sensación sobre cómo funciona, envuélvala en una interfaz y proporcione una implementación de maqueta para la API con fines de prueba y haga la TDD en la aplicación que usa la interfaz. . – Laserallan
He estado en el mismo barco antes y no tengo una buena solución. Sin embargo, al menos incluso si primero escribe la implementación y luego escribe las pruebas en función de cómo se comporta la implementación (puede automatizar con la generación de código), está creando una red de seguridad que le avisará si cambia algo más adelante en la línea que rompe el comportamiento preexistente. Esto es realmente útil si tiene mucha reutilización de código, pero se encuentra teniendo que actualizar el código reutilizable para que se aplique a nuevos escenarios. Las pruebas preexistentes de la unidad asegurarán que no rompa nada con sus "mejoras". – AaronLS
@Laserallan: Eso es lo que estoy haciendo. No tengo problemas para no usar TDD en los pequeños programas de prueba desechables. Tengo un problema al escribir toneladas de pequeñas aplicaciones de prueba desechables. –