Upload
victor-yastrebov
View
1.623
Download
1
Embed Size (px)
Citation preview
Использование юнит-тестов для
повышения качества разработки
Ястребов Виктор / разработчик компании “Тен"зор”
cppconf.ru - 2017
Ястребов Виктор Разработчик компании “Тензор”
1. Характеристики хорошего юнит-теста
2. Подходы к созданию тестируемого кода
3. Виды поддельных объектов
4. Пример тестирования класса
5. Приемы создания хороших юнит-тестов
О чем будем говорить
2/43
Допущения
• Код упрощен
•Используется Google Test
https://github.com/google/googletest/tree/master/googletest
3/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
Поддельные реализации объектов
Fake-объект
Stub-объект
Задача
разделения
Mock-объект
Задача
распознавания
12/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
Вместо конкретной реализации – интерфейс
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
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
Выделение зависимости
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
Выделение зависимости
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
Практические приемы
• Один тест - один результат работы
• Тестируем только для публичные методы
• Нет ветвления
• операторы: switch, if, else
• циклы: for, while, std::for_each
• Юнит тест - последовательность вызовов методов + assert
• Используем фабрики
41/43
Где почитать подробнее
• Roy Osherove “The art of unit testing”. 2nd edition
•Майкл Физерс “Эффективная работа
с унаследованным кодом”
•Кент Бек “Экстремальное программирование.
Разработка через тестирование”
42/43
Спасибо за внимание!
Ястребов Виктор / [email protected]
cppconf.ru - 2017
Ястребов Виктор Разработчик компании “Тензор”