unit tests final

50
Практики надежного модульного тестирования для С++ Юрий Ефимочев

Upload: corehardby

Post on 06-Jan-2017

138 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Unit tests final

Практики надежного модульного тестирования

для С++Юрий Ефимочев

Page 2: Unit tests final

О себе

Ведущий разработчик/архитектор в компании SolarWinds, в бэкап-решении MAXBackup.Специализация: высоконагруженные отказоустойчивые системы на C++

Page 3: Unit tests final

Польза > Затраты

Что такое модульные тесты?

Модульное тестирование(unit testing) - это процесс, позволяющий проверить на корректность отдельные модули программы.

Page 4: Unit tests final

● Раннее обнаружение ошибок● Обнаружение регрессионных ошибок● Моделирование сложных сценариев● Получение оценки производительности● Динамический анализ кода(valgrind, …)

Обнаружение ошибок

Page 5: Unit tests final

Дизайн

Page 6: Unit tests final

Дизайн

● Модульность● Поощрение рефакторинга● Ускорение разработки за счет повышения

качества кода● Foolproof design

Page 7: Unit tests final

Пример: foolproof design class ITransactionController { public: virtual void BeginTransaction() = 0; virtual void CommitTransaction() = 0; virtual void RollbackTransaction() = 0;

virtual ~ITransactionController() {} };

Page 8: Unit tests final

Пример: foolproof design BOOST_AUTO_TEST_CASE(DoubleBeginTest) { controller.BeginTransaction(); BOOST_REQUIRE_THROW(controller.BeginTransaction()); }

BOOST_AUTO_TEST_CASE(OutOfOrderCommitTest) { BOOST_REQUIRE_THROW(controller.CommitTransaction()); }

BOOST_AUTO_TEST_CASE(OutOfOrderRollbackTest) { BOOST_REQUIRE_THROW(controller.RollbackTransaction()); }

Page 9: Unit tests final

Пример: foolproof design class ITransactionGuard { public: virtual void Commit() = 0;

virtual ~ITransactionGuard() {} };

typedef std::unique_ptr<ITransactionGuard> ITransactionGuardPtr;

class ITransactionController { public: virtual ITransactionGuardPtr BeginTransaction() = 0;

virtual ~ITransactionController() {} };

Page 10: Unit tests final

Пример: foolproof design{ ITransactionGuardPtr guard = controller.BeginTransaction();

// do something

guard.Commit(); }

Page 11: Unit tests final

Документация

Page 12: Unit tests final

Документация

● Актуальная документация● Верификация на этапе компиляции● Готовые сценарии использования

Page 13: Unit tests final

Хороший тест:

● Актуален○ Запускается при сборке○ Ошибка теста = ошибка компиляции○ Покрытие кода тестами контролируется

Page 14: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку

○ Выполняется быстро○ Не спамит в лог компиляции○ Код соответствует guideline-ам○ Результат не зависит от окружения

Page 15: Unit tests final

Пример BOOST_AUTO_TEST_CASE(DnsResolverTest) { DnsResolver resolver; BOOST_REQUIRE_NO_THROW(resolver.Resolve("www.google.com")); }

Page 16: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку

○ Выполняется быстро○ Не спамит в лог компиляции○ Код соответствует guideline-ам○ Результат не зависит от окружения○ Ведет себя детерминировано

Page 17: Unit tests final

Пример void SimpleTask(std::atomic<int>& counter) { ++counter; }

BOOST_AUTO_TEST_CASE(ThreadPoolTest) { int const TaskCount = 100;

ThreadPool pool(4);

std::atomic<int> processedCount = 0;

for (int index = 0; index < TaskCount; ++index) { pool.AddTask(std::bind(&SimpleTask, std::ref(processedCount))); }

ThreadHelper::Sleep(Time::Seconds(1));

BOOST_CHECK_EQUAL(processedCount, TaskCount); }

Page 18: Unit tests final

Пример BOOST_AUTO_TEST_CASE(EncryptionTest) { int const IterationsCount = 1000000;

for (int index = 0; index < IterationsCount; ++index) { std::string const phrase = GenerateRandomPhrase(); BOOST_CHECK(Decrypt(Encrypt(phrase)) == phrase); } }

Page 19: Unit tests final

Пример BOOST_AUTO_TEST_CASE(EncryptionTest) { int const IterationsCount = 1000000;

for (int index = 0; index < IterationsCount; ++index) { std::string const phrase = GenerateRandomPhrase(); BOOST_CHECK_EQUAL(Decrypt(Encrypt(phrase)), phrase); } }

Page 20: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку

○ Выполняется быстро○ Не спамит в лог компиляции○ Код соответствует guideline-ам○ Результат не зависит от окружения○ Ведет себя детерминировано○ Ошибки информативны

Page 21: Unit tests final

Информативные ошибки

void CheckItem(Item const& item) { BOOST_REQUIRE(0 < item.Id && item.Id < 1000 && 0 < item.Name.size() && item.Name.size() < 50); // error }

BOOST_AUTO_TEST_CASE(CreateTest) { CheckItem(factory.Create(ParameterSet1)); CheckItem(factory.Create(ParameterSet2)); CheckItem(factory.Create(ParameterSet3)); }

BOOST_AUTO_TEST_CASE(SomeOtherTest) { // ... CheckItem(...); // ... }

Page 22: Unit tests final

Информативные ошибки

bool CheckItem(Item const& item) { return 0 < item.Id && item.Id < 1000 && 0 < item.Name.size() && item.Name.size() < 50; }

BOOST_AUTO_TEST_CASE(CreateTest) { BOOST_REQUIRE(CheckItem(factory.Create(ParameterSet1))); // error BOOST_REQUIRE(CheckItem(factory.Create(ParameterSet2))); BOOST_REQUIRE(CheckItem(factory.Create(ParameterSet3))); }

Page 23: Unit tests final

Информативные ошибки

bool CheckItem(Item const& item) { return 0 < item.Id && item.Id < 1000 && 0 < item.Name.size() && item.Name.size() < 50; }

BOOST_AUTO_TEST_CASE(CreateTest) { BOOST_CHECK(CheckItem(factory.Create(ParameterSet1))); // error BOOST_CHECK(CheckItem(factory.Create(ParameterSet2))); BOOST_CHECK(CheckItem(factory.Create(ParameterSet3))); // error }

Page 24: Unit tests final

Информативные ошибки

#define CHECK_ITEM(item) \ BOOST_CHECK_GT(item.Id, 0); \ BOOST_CHECK_LT(item.Id, 1000); \ BOOST_CHECK_GT(item.Name.size(), 0); \ BOOST_CHECK_LT(item.Name.size(), 50);

BOOST_AUTO_TEST_CASE(CreateTest) { CHECK_ITEM(factory.Create(ParameterSet1)); // error: invalid id CHECK_ITEM(factory.Create(ParameterSet2)); CHECK_ITEM(factory.Create(ParameterSet3)); // error: invalid name }

Page 25: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку● Прост для понимания

○ Хорошо структурирован

Page 26: Unit tests final

Примерclass IUserManager{public: virtual int AddUser(UserInfo const& user) = 0; virtual void UpdateUser(UserInfo const& user) = 0; virtual void DeleteUser(int const id) = 0;

virtual UserInfo GetUserById(int const id) const = 0; virtual UserInfoIteratorPtr EnumerateUsers() const = 0;

virtual ~IUserManager() {}};

Page 27: Unit tests final

Пример: организация тестов BOOST_AUTO_TEST_SUITE(UserManagerTestSuite);

BOOST_AUTO_TEST_CASE(Test) { // create manager // add 20 users // test EnumerateUsers // test GetUserById // add 2 more users // test add special case // test EnumerateUsers // test GetUserById // update 10 users // test update special case // test EnumerateUsers // test GetUserById // delete 10 users // ... }

BOOST_AUTO_TEST_SUITE_END()

Page 28: Unit tests final

Пример: организация тестов BOOST_AUTO_TEST_SUITE(UserManagerTestSuite)

BOOST_AUTO_TEST_CASE(InsertTest) BOOST_AUTO_TEST_CASE(InsertNonUniqueTest) BOOST_AUTO_TEST_CASE(InsertWithCustomIdTest) BOOST_AUTO_TEST_CASE(InsertWithAlreadyExistingIdTest)

BOOST_AUTO_TEST_CASE(UpdateTest) BOOST_AUTO_TEST_CASE(UpdateWithInvalidValueTest) BOOST_AUTO_TEST_CASE(UpdateNonExistingUserTest)

BOOST_AUTO_TEST_CASE(DeleteTest) BOOST_AUTO_TEST_CASE(DeleteNonExistingUserTest)

BOOST_AUTO_TEST_CASE(GetUserByIdTest) BOOST_AUTO_TEST_CASE(GetNonExistingUserByIdTest)

BOOST_AUTO_TEST_CASE(EnumerateTest) BOOST_AUTO_TEST_CASE(EnumerateEmptyCollectionTest)

BOOST_AUTO_TEST_SUITE_END()

Page 29: Unit tests final

Пример: тест-сценарий BOOST_AUTO_TEST_CASE(EnumerateTest) { // Given manager.AddUser(UserInfo1); manager.AddUser(UserInfo2); manager.AddUser(UserInfo3);

// When manager.DeleteUser(UserInfo2.Id);

// Then BOOST_CHECK_EQUAL(manager.Enumerate().Size(), 2); BOOST_CHECK_EQUAL(manager.GetUserById(UserInfo1.Id), UserInfo1); BOOST_CHECK_THROW(manager.GetUserById(UserInfo2.Id)); BOOST_CHECK_EQUAL(manager.GetUserById(UserInfo3.Id), UserInfo3); }

Page 30: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку● Прост для понимания

○ Хорошо структурирован○ Минималистичен

Page 31: Unit tests final

Пример: fixture BOOST_AUTO_TEST_SUITE(UserManagerTestSuite);

BOOST_AUTO_TEST_CASE(UpdateTest) { ManagerFactory const factory; IUserManagerPtr manager = factory.CreateUserManager();

// ... }

BOOST_AUTO_TEST_CASE(DeleteTest) { ManagerFactory const factory; IUserManagerPtr manager = factory.CreateUserManager();

// ... }

BOOST_AUTO_TEST_SUITE_END()

Page 32: Unit tests final

Пример: fixture struct Fixture { Fixture() : Manager(MangerFactory().CreateUserManager()) {}

IUserManagerPtr Manager; }

BOOST_FIXTURE_TEST_SUITE(UserManagerTestSuite, Fixture);

BOOST_AUTO_TEST_CASE(UpdateTest) { // ... }

BOOST_AUTO_TEST_CASE(DeleteTest) { // ...}

BOOST_FIXTURE_TEST_SUITE_END()

Page 33: Unit tests final

Пример: излишняя сложность BOOST_AUTO_TEST_CASE(Test) { int id = 0;

++id; BOOST_CHECK_EQUAL(manager.AddUser(Names[id]), id); ++id; BOOST_CHECK_EQUAL(manager.AddUser(Names[id]), id); ++id; BOOST_CHECK_EQUAL(manager.AddUser(Names[id]), id);

while (id >= 0) { BOOST_CHECK_EQUAL(manager.GetUserById(id).Name, Names[id]); --id; } }

Page 34: Unit tests final

Пример: излишняя сложность BOOST_AUTO_TEST_CASE(Test) { BOOST_CHECK_EQUAL(manager.AddUser(Name1), 1); BOOST_CHECK_EQUAL(manager.AddUser(Name2), 2); BOOST_CHECK_EQUAL(manager.AddUser(Name3), 3);

BOOST_CHECK_EQUAL(manager.GetUserById(1).Name, Name1); BOOST_CHECK_EQUAL(manager.GetUserById(2).Name, Name2); BOOST_CHECK_EQUAL(manager.GetUserById(3).Name, Name3); }

Page 35: Unit tests final

Пример: повторяющиеся проверки BOOST_AUTO_TEST_CASE(DeleteTest) { BOOST_CHECK_EQUAL(manager.AddUser(...), 1); BOOST_CHECK_EQUAL(manager.AddUser(...), 2); BOOST_CHECK_EQUAL(manager.AddUser(...), 3); BOOST_CHECK_EQUAL(manager.AddUser(...), 4);

BOOST_CHECK_NO_THROW(manager.DeleteUser(1)); BOOST_CHECK_NO_THROW(manager.DeleteUser(2)); BOOST_CHECK_NO_THROW(manager.DeleteUser(3)); BOOST_CHECK_NO_THROW(manager.DeleteUser(4)); }

BOOST_AUTO_TEST_CASE(DeleteTest) { BOOST_CHECK_EQUAL(manager.AddUser(...), 1); BOOST_CHECK_NO_THROW(manager.DeleteUser(1)); }

Page 36: Unit tests final

Пример: ортогональные тесты BOOST_AUTO_TEST_CASE(UpdateTest) { BOOST_CHECK_EQUAL(manager.AddUser(UserInfo1), 1); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo2), 2); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo3), 3); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo4), 4); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo5), 5);

// ... }

BOOST_AUTO_TEST_CASE(DeleteTest) { BOOST_CHECK_EQUAL(manager.AddUser(UserInfo1), 1); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo2), 2); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo3), 3); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo4), 4); BOOST_CHECK_EQUAL(manager.AddUser(UserInfo5), 5);

// ... }

Page 37: Unit tests final

Пример: ортогональные тесты BOOST_AUTO_TEST_CASE(UpdateTest) { manager.AddUser(UserInfo1); manager.AddUser(UserInfo2); manager.AddUser(UserInfo3);

// ... }

BOOST_AUTO_TEST_CASE(DeleteTest) { manager.AddUser(UserInfo1); manager.AddUser(UserInfo2);

// ... }

Page 38: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку● Прост для понимания

○ Хорошо структурирован○ Минималистичен○ Самодостаточен

Page 39: Unit tests final

Пример: скрытые данные/проверки

BOOST_FIXTURE_TEST_SUITE(UserManagerTestSuite, Fixture);

BOOST_AUTO_TEST_CASE(Test) { ReadDataFromFile("./Data.user", Manager); ValidateData(Manager); }

BOOST_FIXTURE_TEST_SUITE_END()

Page 40: Unit tests final

std::string Encrypt(std::string const& data) { return data; // todo: implement later }

std::string Decrypt(std::string const& data) { return data; // todo: implement later }

Пример: неявные проверки

std::string const SecretPhrase = "secret phrase";

BOOST_AUTO_TEST_CASE(Test) { BOOST_CHECK_EQUAL(Decrypt(Encrypt(SecretPhrase)), SecretPhrase); }

Page 41: Unit tests final

Пример: неявные проверки

std::string const SecretPhrase = "secret phrase"; std::string const EncryptedPhrase = "aL8m52AHJ/Z7Z5G2MkqpIaXOEjRm41OaraiTLYcXM3o=";

BOOST_AUTO_TEST_CASE(EncryptorTest) { BOOST_CHECK_EQUAL(Encrypt(SecretPhrase), EncryptedPhrase); }

BOOST_AUTO_TEST_CASE(DecryptorTest) { BOOST_CHECK_EQUAL(Decrypt(EncryptedPhrase), SecretPhrase); }

Page 42: Unit tests final

Хороший тест:

● Актуален● Не затрудняет разработку● Прост для понимания

○ Хорошо структурирован○ Минималистичен○ Самодостаточен○ Оптимизирован для чтения

Page 43: Unit tests final

Пример: синтаксический сахар BOOST_AUTO_TEST_CASE(TreeTraversalTest) { Tree tree; tree.InsertNode(1); tree.InsertNode(2, 1); tree.InsertNode(3, 2); tree.InsertNode(4, 2); tree.InsertNode(5, 1); tree.InsertNode(6, 5); tree.InsertNode(7, 5);

NodeCollection const nodes = TraversPerOrder(tree);

BOOST_CHECK(nodes.size(), 7); BOOST_CHECK(nodes[0], 1); BOOST_CHECK(nodes[1], 2); BOOST_CHECK(nodes[2], 3); BOOST_CHECK(nodes[3], 4); BOOST_CHECK(nodes[4], 5); BOOST_CHECK(nodes[5], 6); BOOST_CHECK(nodes[6], 7); }

Page 44: Unit tests final

Пример: выразительная сила С++ // http://www.eelis.net/C++/analogliterals.xhtml

assert( ( o-------------o |L \ | L \ | L \ | o-------------o | ! ! ! ! ! o | ! L | ! L | ! L| ! o-------------o ).volume == ( o-------------o | ! ! ! ! ! o-------------o ).area * int(I-------------I) );

Page 45: Unit tests final

Пример: синтаксический сахар BOOST_AUTO_TEST_CASE(TreeTraversalTest) { Tree const tree = Node(1) (Node(2) (Node(3)) (Node(4))) (Node(5) (Node(6)) (Node(7))));

BOOST_CHECK(TraversPreOrder(tree), List(1, 2, 3, 4, 5, 6, 7)); BOOST_CHECK(TraversInOrder(tree), List(3, 2, 4, 1, 6, 5, 7)); BOOST_CHECK(TraversPostOrder(tree), List(3, 4, 2, 6, 7, 5, 1)); }

Page 46: Unit tests final

Пример: синтаксический сахар BOOST_AUTO_TEST_CASE(EnumerateTest) { manager.AddUser(UserInfo("User1", "[email protected]", UserRole::Admin, ...)); manager.AddUser(UserInfo("User2", "[email protected]", UserRole::Reader, ...)); manager.AddUser(UserInfo("User3", "[email protected]", UserRole::Reader, ...));

IUserIteratorPtr iterator = manager.Enumerate(); UserInfo info; BOOST_CHECK(iterator.GetNext(info)); BOOST_CHECK_EQUAL(info.Name, "User1"); BOOST_CHECK(iterator.GetNext(info)); BOOST_CHECK_EQUAL(info.Name, "User2"); BOOST_CHECK(iterator.GetNext(info)); BOOST_CHECK_EQUAL(info.Name, "User3"); }

Page 47: Unit tests final

Пример: синтаксический сахар BOOST_AUTO_TEST_CASE(EnumerateTest) { User("User1"); User("User2"); User("User3");

BOOST_CHECK_EQUAL(Enumerate(), List("User1", "User2", "User3")); }

Page 48: Unit tests final

Пример: синтаксический сахар BOOST_AUTO_TEST_CASE(EnumerateByRoleTest) { User("User1").Role(UserRole::Admin); User("User2").Role(UserRole::Reader); User("User3").Role(UserRole::Reader);

BOOST_CHECK_EQUAL(EnumerateByRole(UserRole::Reader), List("User2", "User3")); }

Page 49: Unit tests final

Рекомендации● Предпочитать check require● Использовать fixture● Оформлять тесты в сценарии● Использовать синтаксический сахар● Убрать все лишнее● Показать все, что нужно для понимания● Написать необходимый минимум тестов