Andriy Shyrokoryadov

.Net developer, data scientist

Юнит-тестирование

Текст к видео "Юнит-тестирование " на канале YouTube

Привет всем. Без лишней скромности я могу сказать, что на моем канале достаточно много интересных видео на темы, связанные с программированием, но сегодняшняя тема особенно интересна. Это юнит-тесты. Тема, на которую написано множество книг и статей; на каждый конференции обязательно всплывает тема юнит-тестирования; на собеседованиях всегда появляются вопросы на эту тему, даже если в коде компании нет ни одного юнит теста. Какой у нас на сегодня план:

  • поговорим о том какие задачи решают юнит тесты. Тут сразу стоит отметить, что эти задачи решаются только хорошо написанными юнит-тестами. Тесты, написанные «на отвали» никаких задач не решают, а только подкидывают проблем программистам.
  • обсудим, как можно сразу понять, что перед нами как-то гавно, а не юнит-тест;
  • вспомним теорию, которая пригодится на собеседовании – большинство рекрутеров считают хорошим тоном задать какой-нибудь вопрос на тему юнит-тестов;
  • применим теорию на практике в рамках практического примера – по сути будет много примеров, которые будут показывать различные подходы. А также познакомимся с примером задания на собеседование, которое мне, когда – то задали и я его хорошо запомнил. Юнит-тесты – это такой же код, как и код основного приложения, а это значит, что нельзя писать код основного приложения хорошо, соблюдая принципы чистого кода, а тесты писать, как получится. Это равноправный код, как и код основного приложения и ему следует уделять столько же внимания, как и коду основного приложения.

Какую задачу решает юнит-тестирование? Таких задач несколько.

  • прежде всего мы проверяем работу определенной функциональности. Иногда вызов определенного метода класса делает несколько действий: скачивает данные, обрабатывает их и сохраняет результат. Конечно, можно написать тест для такого метода, однако мы не сможем называть этот тест юнит-тестом. Слово юнит в данном контексте означает действительно единичную функциональность в прямом смысле этого слова. Чтобы протестировать метод, о котором мы говорили, мы должны написать наш код таким образом, чтобы можно было протестировать отдельно 3 функциональности: скачивание данных, обработку данных, сохранение результата. В теории программирования к юнит-тестам предъявляются 4 требования, которые образуют аббревиатуру ACID. Так вот, первая буква А в этой аббревиатуре означает atomicity – атомарность или неразделимость. Мы должны тестировать в юнит тестах наименьшие, нераздельные функциональные единицы нашего кода.
  • теория программирования нам завещала, чтобы мы писали юнит-тесты перед написанием любого кода. Собственно, этим и объясняется появление концепции TDD – Test Driven Development, то есть программирование, управляемое тестами. В видео о чистом коде я упоминал, что только для хорошо написанного кода можно написать юнит-тесты. Можно рискнуть и сказать, что хорошо написанные юнит тесты определенной функциональности — это фундамент хорошего кода. То есть хороший юнит тест – это такой стражник, который дисциплинирует нас и требует, что бы мы писали хороший качественный код.
  • у нас есть определенный код, для которого написаны хорошие тесты. Эти тесты могут нам помочь понять, как работает код и, в этом случае, данные тесты служат, как бы, документацией и примерами использования кода, с которым мы должны работать. Поэтому если мы получаем доступ к новому коду, то для начала необходимо пробежаться по юнит тестам.

Все эти блага, которые нам дают юнит-тесты возможны лишь при одном условии – юнит-тесты должны быть написаны хорошо. Хорошие юнит-тесты также дают шанс, что основной код написан хорошо. Однако мы же в мире, где не всё идеально и иногда мы можем встретиться с юнит-тестами написанными плохо. Как это понять?

  • хороший юнит тест это, как правило, очень короткий метод, на пару строчек. Если метод юнит теста большой, и вы видите, что там внутри происходит слишком много действий как для одного юнит -теста, то, к сожалению, это может говорить о проблемах.
  • не будет хорошим юнит-тест, в котором проверяется слишком много предположений, теории или, как говорят англичане, ассерций, от слова assert – утверждать. Лучшие практики программирования утверждают, что в одном юнит тесте должна проверяться только одна теория, а если теорий несколько, то они должны касаться только одного объекта.
  • если в юнит тестах вы видите, что есть попытки связаться с каким-то внешним источником данных: базой данных, файлом или веб услугой, то скорей всего автор такого юнит-теста забыл о требовании I в аббревиатуре ACID, которая означает isolation. Т. е. «изоляция». Хороший юнит-тест — это всегда изолированный код, который ни от чего не зависит. Если так называемый юнит-тест зависит от чтения данных например из базы данных, то если база данных будет недоступна, то юнит-тест не пройдет. А ведь юнит-тест должен проверять определенную функциональность, а не наличие соединения с базой данных.
  • Если вы заметили, что после нескольких запусков некоторые юнит-тесты то проходят, то нет, то здесь мы имеем дело с нарушением принципа C в аббревиатуре ACID, который означает consistency, то есть «последовательность» или «логичность». Результат теста должны быть всегда одинаков. Иногда нарушение тербования изоляции приводит к нарушению требования последовательности – так называемый юнит тест зависит от внешнего источника данных и в зависимости от того есть ли доступ к этому источнику данных тест проходит или нет.

Подводя итог можно сказать, что если вы были в состоянии понять, что проверяют юнит-тесты и как они работают, то это хороший знак – есть очень большие шансы того, что код, который проверяют данные юнит-тесты, также написан хорошо.

Мы познакомились с аббревиатурой ACID и знаем, что означают первые 3 буквы. Пора познакомиться с последней буквой – это durability, то есть «прочность». Если юнит-тест прошел один раз, то он должен проходит всегда. Не должно быть условий при которых юнит тест может не пройти. В практическом примере мы увидим ситуацию, когда юнит тест вроде бы работает, но по факту он не соответствует требованию D.

Кроме аббревиатуры ACID, есть еще аббревиатура AAA. Она характеризует структуру каждого юнит-теста. AAA обозначает 3 последовательных действия: arrangeactassert, то есть в свободном переводе это может означать «приведи в порядок – действуй - проверь». А рамках действия «приведи в порядок» необходимо подготовить объект, который мы тестируем к тестам. То есть создать такой объект. Если объект имеет зависимости создать так называемые моки эти зависимостей, то есть имитаторы. Если наш класс для тестов имеет другие тесты, то на этапе упорядочивания необходимо позаботиться чтобы результат одних тестов не влияли на другие тесты. Далее выполняем действие «действуй»

  • здесь, собственно, вызывается функциональность, которую мы хотели бы протестировать. Далее в действии «проверь» - мы должны проверить наши утверждения на основания вызова тестируемой функциональности и сделать выводы о результатах теста.

Я понимаю что то, что я сейчас сказал может звучать абстрактно, поэтому давайте запустим практический пример и посмотрим на различные примеры тестов. В рамках практического примера мы также познакомимся с тремя библиотеками, которые используются для написания юнит-тестов: nUnit, Moq, AutoFixture. После практического примера будет небольшой бонус – не переключайтесь.

Как вам данное видео? Надеюсь, сегодня вы узнали что-то новенькое. Напоминаю, что код из видео есть на моей страничке Github, а ссылка на файл – бонус будет на моей странице с текстом к данному видео. Поддержите канал лайком и комментарием. Если не подписаны, то подпиской. Ну а пока мне пора заканчивать. Спасибо за внимание и до новых встреч.

Пример кода из видео на GitHub

Бонус файл

Для открытия файла проекта необходимо Visual Studio 2019. Visual Studio должно быть запущено с правами администратора.