Использование юнит-тестов для
повышения качества разработки
cppconf.ru - 2017
Ястребов Виктор
Разработчик компании “Тензор”
1. Характеристики хорошего юнит-теста
2. Подходы к созданию тестируемого кода
3. Виды поддельных объектов
4. Пример тестирования класса
5. Приемы создания хороших юнит-тестов
О чем будем говорить
2/43
Допущения
• Код упрощен
•Используется Google Test
https://github.com/google/googletest/tree/master/googletest
3/43
Внешняя зависимость
Взаимодействие есть, а контроля нет
4/43
Жесткий диск, время, база данных
Интеграционный тест VS юнит-тест
Полный контроль
над внешними зависимостями
Интеграционный тест Юнит тест
нет да
5/43
Признаки хорошего юнит-теста
Полный контроль над внешними зависимостями
Автоматизация
запуска
Результат
• стабилен
• повторим
• независим
• Малое время выполнения
• Простота чтения
6/43
Принцип наименования юнит-теста
Sum_ByDefault_ReturnsZero()
Sum_WhenCalled_CallsTheLogger()
[ИмяТестируемойРабочейЕденицы]_
[СценарийТеста]_
[ОжидаемыйРезультат]
7/43
TEST_F( CalculatorTest,
Sum_ByDefault_ReturnsZero() )
{
Calculator calc;
int last_sum = calc.Sum();
ASSERT_EQ( 0, last_sum );
}
Структура юнит-теста
1. Arrange
2. Act
3. Assert
8/43
Рабочие единицы тестируемого кода
• Возвращаемый результат
• Изменение состояния
системы
• Взаимодействие между
объектами
9/43
Рабочие единицы тестируемого кода
• Возвращаемый результат
• Изменение состояния
системы
Задача
распознавания
Задача
разделения
Разрыв
зависимости
• Взаимодействие между
объектами
10/43
Поддельные
объекты
(Fakes)
Поддельные реализации объектов
Fake-объект
Stub-объект
Задача
разделения
Mock-объект
Задача
распознавания
12/43
Stub-объект
Взаимоде
йствие
Тестовый код
Тестируемый код Stub
Взаимодействие
13/43
Stub-объект
Взаимоде
йствие
Тестовый код
Тестируемый код Mock
Взаимодействие
14/43
Разбор
на примере
Исходный код
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
Исходный код
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
Исходный код
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
Исходный код
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
Разрыв зависимости от базы данных
База данных
IsValid( std::string ename )
IDatabaseManager
FakeDatabaseManager DatabaseManager
20/43
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
Использование stub
для разрыва зависимости
Параметризация
конструктора.
Вариант 1
Вместо конкретной реализации – интерфейс
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
Внедрение зависимости
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
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
Внедрение зависимости
Тестирование возвращаемого значения
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
Использование stub
для разрыва зависимости
Параметризация
конструктора.
Вариант 2
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
Тестирование возвращаемого значения
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
Использование stub
для разрыва зависимости
“Выделить и
переопределить”
Выделение зависимости
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
Переопределение зависимости
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
Тестирование возвращаемого значения
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
Использование
Mock для разрыва
зависимости
Выделение зависимости
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
Переопределение зависимости
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
Тестирование взаимодействия
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
Тестирование взаимодействия
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
Тестирование взаимодействия
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
Приемы создания
хороших
unit-тестов
Практические приемы
• Один тест - один результат работы
• Тестируем только для публичные методы
• Нет ветвления
• операторы: switch, if, else
• циклы: for, while, std::for_each
• Юнит тест - последовательность вызовов методов + assert
• Используем фабрики
41/43
Где почитать подробнее
• Roy Osherove “The art of unit testing”. 2nd edition
•Майкл Физерс “Эффективная работа
с унаследованным кодом”
•Кент Бек “Экстремальное программирование.
Разработка через тестирование”
42/43
Спасибо за внимание!
cppconf.ru - 2017
Ястребов Виктор
Разработчик компании “Тензор”
va.yastrebov@tensor.ru

Использование юнит-тестов для повышения качества разработки

  • 1.
    Использование юнит-тестов для повышениякачества разработки cppconf.ru - 2017 Ястребов Виктор Разработчик компании “Тензор”
  • 2.
    1. Характеристики хорошегоюнит-теста 2. Подходы к созданию тестируемого кода 3. Виды поддельных объектов 4. Пример тестирования класса 5. Приемы создания хороших юнит-тестов О чем будем говорить 2/43
  • 3.
    Допущения • Код упрощен •ИспользуетсяGoogle Test https://github.com/google/googletest/tree/master/googletest 3/43
  • 4.
    Внешняя зависимость Взаимодействие есть,а контроля нет 4/43 Жесткий диск, время, база данных
  • 5.
    Интеграционный тест VSюнит-тест Полный контроль над внешними зависимостями Интеграционный тест Юнит тест нет да 5/43
  • 6.
    Признаки хорошего юнит-теста Полныйконтроль над внешними зависимостями Автоматизация запуска Результат • стабилен • повторим • независим • Малое время выполнения • Простота чтения 6/43
  • 7.
  • 8.
    TEST_F( CalculatorTest, Sum_ByDefault_ReturnsZero() ) { Calculatorcalc; int last_sum = calc.Sum(); ASSERT_EQ( 0, last_sum ); } Структура юнит-теста 1. Arrange 2. Act 3. Assert 8/43
  • 9.
    Рабочие единицы тестируемогокода • Возвращаемый результат • Изменение состояния системы • Взаимодействие между объектами 9/43
  • 10.
    Рабочие единицы тестируемогокода • Возвращаемый результат • Изменение состояния системы Задача распознавания Задача разделения Разрыв зависимости • Взаимодействие между объектами 10/43
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 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 : publicIDatabaseManager { 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
  • 22.
    Использование stub для разрывазависимости Параметризация конструктора. Вариант 1
  • 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.
    Тестирование возвращаемого значения classFakeDatabaseManager : 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
  • 27.
    Использование stub для разрывазависимости Параметризация конструктора. Вариант 2
  • 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.
    Тестирование возвращаемого значения classDbMngFactory { 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
  • 30.
    Использование stub для разрывазависимости “Выделить и переопределить”
  • 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
  • 34.
  • 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
  • 40.
  • 41.
    Практические приемы • Одинтест - один результат работы • Тестируем только для публичные методы • Нет ветвления • операторы: switch, if, else • циклы: for, while, std::for_each • Юнит тест - последовательность вызовов методов + assert • Используем фабрики 41/43
  • 42.
    Где почитать подробнее •Roy Osherove “The art of unit testing”. 2nd edition •Майкл Физерс “Эффективная работа с унаследованным кодом” •Кент Бек “Экстремальное программирование. Разработка через тестирование” 42/43
  • 43.
    Спасибо за внимание! cppconf.ru- 2017 Ястребов Виктор Разработчик компании “Тензор” va.yastrebov@tensor.ru