Top Banner
Использование юнит-тестов для повышения качества разработки cppconf.ru - 2017 Ястребов Виктор Разработчик компании “Тензор”
43

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

Mar 03, 2017

Download

Technology

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Использование юнит-тестов для повышения качества разработки

Использование юнит-тестов для

повышения качества разработки

Ястребов Виктор / разработчик компании “Тен"зор”

cppconf.ru - 2017

Ястребов Виктор Разработчик компании “Тензор”

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

1. Характеристики хорошего юнит-теста

2. Подходы к созданию тестируемого кода

3. Виды поддельных объектов

4. Пример тестирования класса

5. Приемы создания хороших юнит-тестов

О чем будем говорить

2/43

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

Допущения

• Код упрощен

•Используется Google Test

https://github.com/google/googletest/tree/master/googletest

3/43

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

Внешняя зависимость

Взаимодействие есть, а контроля нет

4/43

Жесткий диск, время, база данных

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

Интеграционный тест VS юнит-тест

Полный контроль

над внешними зависимостями

Интеграционный тест

Юнит тест

нет да

5/43

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

Признаки хорошего юнит-теста

Полный контроль над внешними зависимостями

Автоматизация

запуска

Результат

• стабилен

• повторим

• независим

• Малое время выполнения

• Простота чтения

6/43

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

Принцип наименования юнит-теста

Sum_ByDefault_ReturnsZero()

Sum_WhenCalled_CallsTheLogger()

[ИмяТестируемойРабочейЕденицы]_

[СценарийТеста]_

[ОжидаемыйРезультат]

7/43

Page 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

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

Рабочие единицы тестируемого кода

• Возвращаемый результат

• Изменение состояния

системы

• Взаимодействие между

объектами

9/43

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

Рабочие единицы тестируемого кода

• Возвращаемый результат

• Изменение состояния

системы

Задача

распознавания

Задача

разделения

Разрыв

зависимости

• Взаимодействие между

объектами

10/43

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

Поддельные объекты (Fakes)

Поддельные

объекты

(Fakes)

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

Поддельные реализации объектов

Fake-объект

Stub-объект

Задача

разделения

Mock-объект

Задача

распознавания

12/43

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

Stub-объект

Взаимоде

йствие

Тестовый код

Тестируемый код Stub Взаимодействие

13/43

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

Stub-объект

Взаимоде

йствие

Тестовый код

Тестируемый код Mock Взаимодействие

14/43

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

Разбор на примере

Разбор

на примере

Page 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

Page 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

Page 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

Page 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

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

Разрыв зависимости от базы данных

База данных

IsValid( std::string ename )

IDatabaseManager

FakeDatabaseManager DatabaseManager

20/43

Page 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

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

Использование stub

для разрыва зависимости

Параметризация

конструктора.

Вариант 1

Page 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

Page 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

Page 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

Внедрение зависимости

Page 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

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

Использование stub

для разрыва зависимости

Параметризация

конструктора.

Вариант 2

Page 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

Page 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

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

Использование stub

для разрыва зависимости

“Выделить и

переопределить”

Page 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

Page 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

Page 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

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

Использование Mock для разрыва зависимости

Использование

Mock для разрыва

зависимости

Page 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

Page 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

Page 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

Page 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

Page 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

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

Приемы создания

хороших

unit-тестов

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

Практические приемы

• Один тест - один результат работы

• Тестируем только для публичные методы

• Нет ветвления

• операторы: switch, if, else

• циклы: for, while, std::for_each

• Юнит тест - последовательность вызовов методов + assert

• Используем фабрики

41/43

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

Где почитать подробнее

• Roy Osherove “The art of unit testing”. 2nd edition

•Майкл Физерс “Эффективная работа

с унаследованным кодом”

•Кент Бек “Экстремальное программирование.

Разработка через тестирование”

42/43

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

Спасибо за внимание!

Ястребов Виктор / [email protected]

cppconf.ru - 2017

Ястребов Виктор Разработчик компании “Тензор”

[email protected]