More Related Content Similar to Использование юнит-тестов для повышения качества разработки (20) Использование юнит-тестов для повышения качества разработки2. 1. Характеристики хорошего юнит-теста
2. Подходы к созданию тестируемого кода
3. Виды поддельных объектов
4. Пример тестирования класса
5. Приемы создания хороших юнит-тестов
О чем будем говорить
2/43
5. Интеграционный тест VS юнит-тест
Полный контроль
над внешними зависимостями
Интеграционный тест Юнит тест
нет да
5/43
6. Признаки хорошего юнит-теста
Полный контроль над внешними зависимостями
Автоматизация
запуска
Результат
• стабилен
• повторим
• независим
• Малое время выполнения
• Простота чтения
6/43
10. Рабочие единицы тестируемого кода
• Возвращаемый результат
• Изменение состояния
системы
Задача
распознавания
Задача
разделения
Разрыв
зависимости
• Взаимодействие между
объектами
10/43
16. Исходный код
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
16/43
17. Исходный код
class WebService {
public:
void LogError( std::string msg ) {
/* логика, включающая
работу с сетевым соединением*/
}
};
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
17/43
18. Исходный код
class WebService {
public:
void LogError( std::string msg ) {
/* логика, включающая
работу с сетевым соединением*/
}
};
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
class DatabaseManager {
public:
bool IsValid( std::string ename ) {
/* логика, включающая
операции чтения из базы данных*/
}
};
18/43
19. Исходный код
class WebService {
public:
void LogError( std::string msg ) {
/* логика, включающая
работу с сетевым соединением*/
}
};
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
2. Внешняя зависимость
1. Внешняя зависимость
class DatabaseManager {
public:
bool IsValid( std::string ename ) {
/* логика, включающая
операции чтения из базы данных*/
}
};
19/43
20. Разрыв зависимости от базы данных
База данных
IsValid( std::string ename )
IDatabaseManager
FakeDatabaseManager DatabaseManager
20/43
21. class DatabaseManager :
public IDatabaseManager {
public:
bool IsValid( std::string ename ) override {
/* сложная логика, включающая
операции чтения из базы данных*/
}
};
class IDatabaseManager {
public:
virtual bool IsValid( std::string ename ) = 0;
virtual ~IDatabaseManager() = default;
};
class FakeDatabaseManager :
public IDatabaseManager {
public:
bool WillBeValid;
FakeDatabaseManager( bool will_be_valid ) :
WillBeValid( will_be_valid ) {
}
bool IsValid( std::string ename ) override {
return WillBeValid;
}
};
Разрыв зависимости от базы данных
21/43
23. Вместо конкретной реализации – интерфейс
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
class EntryAnalyzer {
public:
EntryAnalyzer() :
pDbManager( std::make_unique<DatabaseManager>() ) {
}
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == pDbManager->IsValid( ename ) ) {
return false;
}
return true;
}
private:
std::unique_ptr<IDatabaseManager> pDbManager;
WebService webService;
};
23/43
24. Внедрение зависимости
class EntryAnalyzer {
public:
EntryAnalyzer( std::unique_ptr<IDatabaseManager> &&p_db_mng ) :
pDbManager( std::move( p_db_mng ) ) {
}
bool Analyze( std::string ename ) {
...
if( false == pDbManager->IsValid( ename ) )
return false;
return true;
}
private:
std::unique_ptr<IDatabaseManager> pDbManager;
...
};
внедрение
зависимости
24/43
25. class EntryAnalyzer {
public:
EntryAnalyzer() : pDbManager(
std::make_unique<DatabaseManager>() ) {
}
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == pDbManager->IsValid( ename ) )
return false;
return true;
}
private:
std::unique_ptr<IDatabaseManager> pDbManager;
WebService webService;
};
class EntryAnalyzer {
public:
EntryAnalyzer() : pDbManager(
std::make_unique<DatabaseManager>() ) {
}
EntryAnalyzer(
std:: unique_ptr<IDatabaseManager> &&p_mng ) :
pDbManager( std::move( p_mng ) ) {
}
bool Analyze( std::string ename ) {
...
}
private:
std::unique_ptr<IDatabaseManager> pDbManager;
WebService webService;
};
25/43
Внедрение зависимости
26. Тестирование возвращаемого значения
class FakeDatabaseManager : public IDatabaseManager {
public:
bool WillBeValid;
FakeDatabaseManager( bool will_be_valid ) : WillBeValid( will_be_valid ) {
}
bool IsValid( std::string ename ) override {
return WillBeValid;
}
};
TEST_F( EntryAnalyzerTest, Analyze_ValidEntryName_ReturnsTrue )
{
EntryAnalyzer ea( std::make_unique<FakeDatabaseManager>( true ) );
bool result = ea.Analyze( "valid_entry_name" );
ASSERT_EQ( result, true );
}
26/43
28. class EntryAnalyzer {
public:
EntryAnalyzer() :
pDbManager( DbMngFactory::Create() ) {
}
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == pDbManager->IsValid( ename ) )
return false;
return true;
}
private:
std::unique_ptr<IDatabaseManager> pDbManager;
WebService webService;
};
Использование фабрики
class EntryAnalyzer {
public:
EntryAnalyzer() :
pDbManager( std::make_unique<DatabaseManager>() ) {
}
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == pDbManager->IsValid( ename ) )
return false;
return true;
}
private:
std::unique_ptr<IDatabaseManager> pDbManager;
WebService webService;
};
28/43
29. Тестирование возвращаемого значения
class DbMngFactory {
public:
static std::unique_ptr<IDatabaseManager> Create() {
if( nullptr == pDbMng )
return std::make_unique<DatabaseManager>();
return std::move( pDbMng );
}
static void SetManager(
std::unique_ptr<IDatabaseManager> &&p_mng ) {
pDbMng = std::move( p_mng );
}
private:
static std::unique_ptr<IDatabaseManager> pDbMng;
};
TEST_F( EntryAnalyzerTest,
Analyze_ValidEntryName_ReturnsTrue )
{
DbMngFactory::SetManager(
std::make_unique<FakeDatabaseManager>( true ) );
EntryAnalyzer ea;
bool result = ea.Analyze( "valid_entry_name" );
ASSERT_EQ( result, true );
}
29/43
31. Выделение зависимости
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
...
if( false == IsValid( ename ) )
return false;
return true;
}
protected:
bool IsValid( std::string ename ) {
return dbManager.IsValid( ename );
}
private:
DatabaseManager dbManager;
...
};
31/43
32. Переопределение зависимости
class TestingEntryAnalyzer :
public EntryAnalyzer {
public:
bool WillBeValid;
private:
bool IsValid( std::string ename ) override {
return WillBeValid;
}
};
наследование
внедрение
зависимости
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
...
if( false == IsValid( ename ) )
return false;
return true;
}
protected:
virtual bool IsValid( std::string ename ) {
return dbManager.EntryIsValid( ename );
}
private:
DatabaseManager dbManager;
...
};
тестируемый класс
32/43
33. Тестирование возвращаемого значения
TEST_F( EntryAnalyzerTest,
Analyze_ValidEntryName_ReturnsTrue)
{
TestingEntryAnalyzer ea;
ea.WillBeValid = true;
bool result = ea.Analyze( "valid_entry_name" );
ASSERT_EQ( result, true );
}
class TestingEntryAnalyzer :
public EntryAnalyzer {
public:
bool WillBeValid;
private:
bool IsValid( std::string ename ) override {
return WillBeValid;
}
};
33/43
35. Выделение зависимости
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
webService.LogError( "Error: " + ename );
return false;
}
if( false == dbManager.IsValid( ename ) )
return false;
return true;
}
private:
DatabaseManager dbManager;
WebService webService;
};
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
LogError( "Error: " + ename);
return false;
}
...
}
protected:
virtual void LogError( std::string err ) {
webService.LogError( err );
}
private:
...
WebService webService;
};
35/43
36. Переопределение зависимости
class EntryAnalyzer {
public:
bool Analyze( std::string ename ) {
if( ename.size() < 2 ) {
LogError( "Error: " + ename);
return false;
}
...
}
protected:
virtual void LogError( std::string err ) {
webService.LogError( err );
}
private:
DatabaseManager dbManager;
WebService webService;
};
class TestingEntryAnalyzer :
public EntryAnalyzer {
public:
TestingEntryAnalyzer(
std::shared_ptr<IWebService> p_service ) :
pWebService( p_service ) {
}
private:
void LogError( std::string err ) override {
pWebService->LogError( err );
}
std::shared_ptr<IWebService> pWebService;
};
36/43
37. Тестирование взаимодействия
TEST_F( EntryAnalyzerTest, Analyze_TooShortEntryName_LogsErrorToWebServer )
{
std::shared_ptr<FakeWebService> p_web_service = std::make_shared<FakeWebService>();
TestingEntryAnalyzer ea( p_web_service );
bool result = ea.Analyze( "e" );
ASSERT_EQ( p_web_service->lastError, "Error: e" );
}
37/43
38. Тестирование взаимодействия
TEST_F( EntryAnalyzerTest, Analyze_TooShortEntryName_LogsErrorToWebServer )
{
std::shared_ptr<FakeWebService> p_web_service = std::make_shared<FakeWebService>();
TestingEntryAnalyzer ea( p_web_service );
bool result = ea.Analyze( "e" );
ASSERT_EQ( p_web_service->lastError, "Error: e" );
}
class TestingEntryAnalyzer : public EntryAnalyzer {
public:
TestingEntryAnalyzer(
std::shared_ptr<IWebService> p_service ) :
pWebService( p_service ) {
}
private:
void LogError( std::string err ) override {
pWebService->LogError( err );
}
std::shared_ptr<IWebService> pWebService;
};
38/43
39. Тестирование взаимодействия
TEST_F( EntryAnalyzerTest, Analyze_TooShortEntryName_LogsErrorToWebServer )
{
std::shared_ptr<FakeWebService> p_web_service = std::make_shared<FakeWebService>();
TestingEntryAnalyzer ea( p_web_service );
bool result = ea.Analyze( "e" );
ASSERT_EQ( p_web_service->lastError, "Error: e" );
}
class FakeWebService : public IWebService {
public:
void LogError( std::string error ) override {
lastError = error;
}
std::string lastError;
};
class TestingEntryAnalyzer : public EntryAnalyzer {
public:
TestingEntryAnalyzer(
std::shared_ptr<IWebService> p_service ) :
pWebService( p_service ) {
}
private:
void LogError( std::string err ) override {
pWebService->LogError( err );
}
std::shared_ptr<IWebService> pWebService;
};
39/43
41. Практические приемы
• Один тест - один результат работы
• Тестируем только для публичные методы
• Нет ветвления
• операторы: switch, if, else
• циклы: for, while, std::for_each
• Юнит тест - последовательность вызовов методов + assert
• Используем фабрики
41/43
42. Где почитать подробнее
• Roy Osherove “The art of unit testing”. 2nd edition
•Майкл Физерс “Эффективная работа
с унаследованным кодом”
•Кент Бек “Экстремальное программирование.
Разработка через тестирование”
42/43