SlideShare a Scribd company logo
Использование юнит-тестов для
повышения качества разработки
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

More Related Content

PDF
Parallel STL
Evgeny Krutko
 
PPTX
Григорий Демченко, Универсальный адаптер
Sergey Platonov
 
PPTX
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Sergey Platonov
 
PDF
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Sergey Platonov
 
PPTX
Павел Беликов, Как избежать ошибок, используя современный C++
Sergey Platonov
 
PDF
Догнать и перегнать boost::lexical_cast
Roman Orlov
 
PPTX
Повседневный С++: алгоритмы и итераторы @ C++ Russia 2017
Mikhail Matrosov
 
PDF
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Yauheni Akhotnikau
 
Parallel STL
Evgeny Krutko
 
Григорий Демченко, Универсальный адаптер
Sergey Platonov
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Sergey Platonov
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Sergey Platonov
 
Павел Беликов, Как избежать ошибок, используя современный C++
Sergey Platonov
 
Догнать и перегнать boost::lexical_cast
Roman Orlov
 
Повседневный С++: алгоритмы и итераторы @ C++ Russia 2017
Mikhail Matrosov
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Yauheni Akhotnikau
 

What's hot (20)

PDF
Антон Полухин, Немного о Boost
Sergey Platonov
 
PDF
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Sergey Platonov
 
PDF
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
Alexey Paznikov
 
PDF
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Sergey Platonov
 
PPTX
Фитнес для вашего кода: как держать его в форме
Ilia Shishkov
 
PPTX
Статический анализ кода
Pavel Tsukanov
 
PDF
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
Alexey Paznikov
 
PDF
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
Alexey Paznikov
 
PDF
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
Alexey Paznikov
 
PDF
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
Alexey Paznikov
 
PDF
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
Alexey Paznikov
 
PDF
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
Alexey Paznikov
 
PPTX
Современный статический анализ кода: что умеет он, чего не умели линтеры
corehard_by
 
PDF
Модель памяти C++ - Андрей Янковский, Яндекс
Yandex
 
PDF
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
Alexey Paznikov
 
PDF
20130429 dynamic c_c++_program_analysis-alexey_samsonov
Computer Science Club
 
PPTX
Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript
Sergey Platonov
 
PDF
C++ refelection and cats
corehard_by
 
PDF
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
Alexey Paznikov
 
PDF
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
Alexey Paznikov
 
Антон Полухин, Немного о Boost
Sergey Platonov
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Sergey Platonov
 
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
Alexey Paznikov
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Sergey Platonov
 
Фитнес для вашего кода: как держать его в форме
Ilia Shishkov
 
Статический анализ кода
Pavel Tsukanov
 
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
Alexey Paznikov
 
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
Alexey Paznikov
 
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
Alexey Paznikov
 
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
Alexey Paznikov
 
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
Alexey Paznikov
 
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
Alexey Paznikov
 
Современный статический анализ кода: что умеет он, чего не умели линтеры
corehard_by
 
Модель памяти C++ - Андрей Янковский, Яндекс
Yandex
 
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
Alexey Paznikov
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
Computer Science Club
 
Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript
Sergey Platonov
 
C++ refelection and cats
corehard_by
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
Alexey Paznikov
 
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
Alexey Paznikov
 
Ad

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

ODP
Unit test быстрый старт
Antonio
 
PPTX
Tdd webpack + testem + mocha + chai
Michael Chernobrov
 
PDF
Организация работы с API на Vue.js, Виталий Копачёв
Mail.ru Group
 
PPTX
ASP.NET MVC - как построить по-настоящему гибкое веб-приложение
Alexander Byndyu
 
PPTX
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Andrey Rebrov
 
PDF
"CommonJS для браузера", Антон Шувалов, MoscowJS 15
MoscowJS
 
PDF
FrontTalks: Алексей Андросов (Яндекс), «Ошибки, которые мы любим»
Yandex
 
PDF
Инструментируй это
Roman Dvornov
 
PDF
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
DevDay
 
PDF
"Ractive.js — реактивные усы!" Ефим Соловьев, MoscowJS 17
MoscowJS
 
PPTX
Статический анализ кода: Что? Как? Зачем?
Andrey Karpov
 
PPTX
статический анализ кода
Andrey Karpov
 
PDF
Выжить с помощью ООП. Максим Гопей
EatDog
 
PDF
'The best practices' by KONSTANTIN KULAKSYZ at OdessaJS'2020
OdessaJS Conf
 
PPT
Top 10 problems supporting Magento customers
aheadWorks
 
PPT
бегун
HighLoad2009
 
PDF
Пластилиновый код: как перестать кодить и начать жить
Moscow.pm
 
PPT
бегун
HighLoad2009
 
PDF
Степан Резников "Шаблонизация на клиенте"
Yandex
 
PDF
Js templating stepan_reznikov
yaevents
 
Unit test быстрый старт
Antonio
 
Tdd webpack + testem + mocha + chai
Michael Chernobrov
 
Организация работы с API на Vue.js, Виталий Копачёв
Mail.ru Group
 
ASP.NET MVC - как построить по-настоящему гибкое веб-приложение
Alexander Byndyu
 
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Andrey Rebrov
 
"CommonJS для браузера", Антон Шувалов, MoscowJS 15
MoscowJS
 
FrontTalks: Алексей Андросов (Яндекс), «Ошибки, которые мы любим»
Yandex
 
Инструментируй это
Roman Dvornov
 
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
DevDay
 
"Ractive.js — реактивные усы!" Ефим Соловьев, MoscowJS 17
MoscowJS
 
Статический анализ кода: Что? Как? Зачем?
Andrey Karpov
 
статический анализ кода
Andrey Karpov
 
Выжить с помощью ООП. Максим Гопей
EatDog
 
'The best practices' by KONSTANTIN KULAKSYZ at OdessaJS'2020
OdessaJS Conf
 
Top 10 problems supporting Magento customers
aheadWorks
 
бегун
HighLoad2009
 
Пластилиновый код: как перестать кодить и начать жить
Moscow.pm
 
бегун
HighLoad2009
 
Степан Резников "Шаблонизация на клиенте"
Yandex
 
Js templating stepan_reznikov
yaevents
 
Ad

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

  • 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
  • 8. 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. Рабочие единицы тестируемого кода • Возвращаемый результат • Изменение состояния системы • Взаимодействие между объектами 9/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
  • 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. Тестирование возвращаемого значения 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
  • 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. Тестирование возвращаемого значения 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
  • 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
  • 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
  • 43. Спасибо за внимание! cppconf.ru - 2017 Ястребов Виктор Разработчик компании “Тензор” [email protected]