Для чего нужны unit тесты

Юнит тесты. Первый шаг к качеству

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

Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тесты

Несмотря на различные трактовки юнит тестирования, есть несколько вещей которые объединяют этот термин.

Но есть моменты, в определении юнит тестирования, которые до сих являются спорными. В частности, что рассматривается под юнитом (единицей тестирования)? Подход ООП рассматривает класс как юнит, процедурный (или функциональный) подход, рассматривает одну функцию как юнит. Некоторые разработчики берут несколько классов и считают это юнитом, или берут набор методов в качестве юнита. Но на самом деле это ситуационная вещь, команда сама решает, что должно быть единицей тестирования в их системе.

Преимущества юнит-тестирования очевидны:

Важное различия в юнит тестировании, это какой тип тестирования вы выберите: Solitary (одинокий) и Sociable (общительный) тест. Термины впервые ввел Jay Fields.
Sociable (общительный) тест — это тест который использует реальные методы (или классы), которые входят в тестируемую единицу. Например, вы тестируете метод «цена» из класса заказов. Методу «цена» необходимо вызвать методы из класса клиент и продукт. В данном виде тестов будут вызваны именно эти методы, и ошибка в этих методах приведет к ошибке теста. Методы из классов клиент и продукт называется партнеры (collaborators).
Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тесты
Solitary (одинокий) тест — это тест, который в качестве партнеров использует дубли (TestDouble). Тест-дубли — это общий термин для любого случая, в котором вы заменяете реальный объект, исключительно для целей тестирования.
Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тесты
Хорошую классификация дублей сделал Жерар Мезарос (Gerard Meszaros), более подробно об этом можно почитать здесь

Каждый из этих методов тестирования имеет свои достоинства и недостатки, и между сторонниками этих двух методов ведутся горячие споры. Сторонников Solitary (одинокий) тестов также условно называют Mock-исты (Mock — подделка), а сторонников Sociable (общительный) тестов условно называют Classicists (не смог найти аналогов в русском языке). Хочется отметить, что сторонники Sociable (общительного) тестирования, также используют дубль-тесты для доступа к внешним ресурсам, например, к БД. Отчасти, это делается по причине скорости доступа. Но использовать дубли для доступа к внешним ресурсам это не абсолютное правило, если доступ к ним стабилен и достаточно быстр, то можно обойтись и без дублей. В любом случае разработчик сам решает, когда ему лучше применить дубли.

Одно из достоинств техники тестирования Solitary (одинокий) в том, что разработчики фокусируются на поведении приложения, а не на состоянии. Недостаток в том, что подделки могут замаскировать ошибку, которая присутствует в методе-партнере. Поэтому, при использовании тестов-дублей необходимо выполнять интеграционное тестирование. Достоинство тестирования Sociable (общительный) в том, что это уже по сути начальное интеграционное тестирование, но недостаток в том, что если упадет один метод это приведет к падению всех тестов, связанных с этим методом, что затрудняет отладку.

Я не буду подробно останавливаться на достоинствах и недостатках того или иного подхода в тестировании, об этом можно почитать у Фаулера в статье Mocks Aren’t Stubs

Основные свойства unit-тестирования – это небольшой объем, сделанный самим программистом, и скорость – что означает, что они могут выполняется часто во время программирования.

Разработчики могут выполнять их после любого изменения в коде. Но не обязательно запускать всегда все тесты, достаточно выполнить только те тесты, которые взаимодействуют с кодом, над котором вы трудитесь в текущий момент.

В конце 1990-х годов Кент Бек разработал технику «разработка через тестирование» (Test-Driven Development, TDD), как часть экстремального программирования. Эта техника для построения ПО, которая управляет процессом разработки через написание тестов. В сущности, повторяет три простых правила:

Написание теста первым дает два преимущества:

1. Это способ получить само-тестируемый код
2. Думая сначала о тесте вы заставляете себя думать об интерфейсе самого кода. Эта фокусировка на интерфейсе и на том как вы используете класс помогает вам разделить интерфейс от реализации.

Самая большая ошибка при использовании данной методологии — это пренебрежение третьим шагом, рефакторинг. Это приводит к тому, что код будет “грязным” (но по крайней мере, будут тесты).

BDD (Behaviour Driven Development) или разработка на основе поведения, появилось в процессе эволюции unit-тестирования и разработана Дэном Нортом (Dan North) в 2006г. Как утверждает сам автор, методология должна помочь людям изучить TDD. Она появилось из agile практик и предназначена сделать их более доступными и эффективными для команд-новичков в Agile.
Со временем, BDD стало охватывать более широкую картину agile-анализа и автоматическое приемочное тестирование.

Это привело к тому, что сами тесты стали переименовывать в поведение (спецификации), что позволило сфокусироваться на том, что объекту нужно сделать. Таким образом, разработчики стали создавать для себя документацию и записывать названия тестов в виде предложений. Они обнаружили, что созданная документация, стала доступна бизнесу, разработчикам и тестерам.

Считается, что разработка на основе поведения одно из ответвлений Mock-стилей (или Solitary-тест), т.е. тесты преимущественно строятся с использованием дублей.

Позднее, появился стиль написания тестов Given-When-Then, или, как его стали называть, спецификация поведения системы. Стиль был разработан Дэном Нортом (Dan North), совместно с Крисом Маттисом (Chris Matts). Идея заключается в том, чтобы разбить написание тестового сценария на три раздела:

Описание: Пользователь продает акции.
Сценарий: Пользователь запрашивает продажу до закрытия торгов
Дано (Given): У меня есть 100 акций MSFT и 150 акций APPL и время до закрытия торгов.
Когда (When): Я прошу продать 20 акций MSFT
Тогда (Then): У меня должно остаться 80 акций MSFT и 150 акций APPL и заявка на продажу 20 акций должна быть выполнена.

Не взирая на то, что с момента появлений методологий TDD и BDD прошло довольно много времени, многие разработчики до сих пор спорят друг с другом о целесообразности их применения. Кто-то утверждает, что нет необходимости писать тесты перед кодом, другие заявляют, что написание тестов после кода бессмысленно. Но и та и другая стороны согласны в одном, что тесты нужно писать! Методология BDD с точки зрения программистов, как утверждает сам ее автор (BDD IS LIKE TDD IF…), не отличается от TDD. Там используются все те же правила, что и в TDD: тест, код, рефакторинг. Отличие заключается в том, что BDD охватывает более широкую публику. Спецификации становятся доступными не только программистам, но и людям, не разбирающимся в коде, но имеющим отношение к разработке ПО. Таким образом, в процесс создания тестов подключается вся команда: аналитики, тестеры, менеджеры.

Одно очевидное преимущество юнит-тестов в том, что они могут радикально уменьшить число ошибок, которые попадают в продукт. В основе этого лежит культура, в результате которой разработчики думают о написании кода и тестов вместе.

Но самое большое преимущество не в том, чтобы просто избегать ошибок в продукте, а в уверенности в том, что вы можете вносить изменения в систему. Старый код часто является ужасной картиной, где разработчики боятся его менять. Даже исправление одной ошибки может быть опасно, т.к. вы можете создать больше ошибок, чем исправите. В таких случаях, добавление новых возможностей происходит очень медленно, вы также боитесь сделать рефакторинг системы, увеличивая тем самым технический долг (TechnicalDebt) и попадаете в плохую спираль, где каждое изменение заставляет людей боятся еще большего изменения.
С тестами другая картина. Здесь люди уверены, что фиксация ошибок, может быть сделана безопасно, потому что, если вы допустили оплошность, то детектор ошибок сработает, и вы можете быстро восстановить и продолжить. С помощью этой системы безопасности, вы можете всегда поддерживать код в хорошей форме и уже не окажетесь в плохой спирали.

В качестве детектора ошибок (само-тестируемая система) выступает процесс выполнения серии автоматических тестов (не только юнит), и вы уверены, что тесты пройдут и ваш код не содержит существенных дефектов. Если кто-то в команде случайно сделает ошибку, сработает детектор. Выполняя тесты часто, несколько раз в день, вы можете обнаружить ошибки сразу после их появления, поэтому вы можете просто посмотреть последние изменения, что значительно облегчает поиск ошибок. Никакой программный эпизод не завершен без рабочего кода, и тестов, поддерживающих его работу.
Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тесты
Само тестируемая система – это часть Continuous Integration (непрерывная интеграция) и Continuous Delivery (непрерывная доставка), но это тема уже выходит за рамки данной статьи.

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

В качестве детектора ошибок или автоматических тестов, выступают не только unit-тесты, но также интеграционные тесты, и другие автоматические тесты. Но unit-тесты здесь играют основу, т.к. написать их просто и выполняются они очень быстро.

Высокоуровневые тесты — это вторая линия обороны. Если вы получили ошибку в высокоуровневом тестировании, то это не просто ошибка в коде, это отсутствующий или некорректный юнит тест!

Источник

Unit тесты на практике

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

Итак…

Какими должны быть модульные тесты?

Помимо того, что модульные тесты должны соответствовать функциональности программного продукта, основное требование — скорость работы. Если после запуска набора тестов разработчик может сделать перерыв (в моём случае на перекур), то подобные запуски будут происходить всё реже и реже (опять же, в моём случае, из-за опасения получить передозировку никотином). В результате может получиться так, что модульные тесты вообще не будут запускаться и, как следствие, потеряется смысл их написания. Программист должен иметь возможность запустить весь набор тестов в любой момент времени. И этот набор должен выполниться настолько быстро, насколько это возможно.

Какие требования необходимо соблюсти для того, чтобы обеспечить скорость выполнения модульных тестов?

Тесты должны быть небольшими

В идеальном случае — одно утверждение (assert) на тест. Чем меньше кусочек функциональности, покрываемый модульным тестом, тем быстрее тест будет выполняться.

Кстати, на тему оформления. Мне очень нравится подход, который формулируется как «arrange-act-assert».
Суть его заключается в том, чтобы в модульном тесте чётко определить предусловия (инициализация тестовых данных,
предварительные установки), действие (собственно то, что тестируется) и постусловия (что должно быть в
результате выполнения действия). Подобное оформление повышает читаемость теста и облегчает его
использование в качестве документации к тестируемой функциональности.

Если в разработке используется ReSharper от JetBrains, то очень удобно настроить template, с помощью которого будет создаваться заготовка для тестового случая. Например, template может выглядеть вот так:

И тогда тест, оформленный подобным образом, может выглядеть примерно так (все имена вымышленные, совпадения случайны):

Тесты должны быть изолированы от окружения (БД, сеть, файловая система)

Этот пункт, наверное, самый спорный из всех. Зачастую в литературе рассматриваются примеры использования TDD на таких задачах как разработка калькулятора или телефонного справочника. Понятно, что эти задачи оторваны от реальности и разработчик, возвращаясь в свой проект, не знает, как применить полученные знания и навыки в повседневной работе. Простейшие случаи, похожие на пресловутый калькулятор, покрываются модульными тестами, а остальная часть функциональности разрабатывается на прежних рельсах. В итоге пропадает понимание того, зачем тратить время на модульные тесты, если простой код можно и так отладить, а сложный всё равно тестами не покрывается.

На самом же деле нет ничего страшного, если в модульных тестах будет использоваться база данных или файловая система. Так, по крайней мере, можно быть уверенным в работоспособности той функциональности, которая по большому счёту и составляет ядро системы. Вопрос заключается только в том, как использовать внешнее окружение наиболее эффективным способом, соблюдая баланс между изолированностью тестов и скоростью их выполнения?

Случай 1. Слой доступа к данным (MS SQL Server)

Если в разработке проекта используется MS SQL сервер, то ответом на этот вопрос может быть использование установленного экземпляра MS SQL сервер (Express, Enterprise или Developer Edition) для разворачивания тестовой базы данных. Подобную базу данных можно создать с помощью стандартных механизмов, используемых в MS SQL Management Studio и поместить её в проект с модульными тестами. Общий подход к использованию такой базы данных заключается в разворачивании тестовой БД перед выполнением теста (например, в методе, отмеченном атрибутом SetUp в случае использования NUnit), наполнении БД тестовыми данными и проверками функциональности репозиториев или шлюзов на этих, заведомо известных, тестовых данных. Причём разворачиваться тестовая БД может как на жёстком диске, так и в памяти, используя приложения, создающие и управляющие RAM диском. Допустим, в проекте, над которым я работаю в данное время, используется приложение SoftPerfect RAM Disk. Использование RAM диска в модульных тестах позволяет снизить задержки, возникающие при операциях ввода/вывода, которые возникали бы при разворачивании тестовой БД на жёстком диске. Конечно, данный подход не идеален, так как требует внесения в окружение разработчика стороннего ПО. С другой стороны, если учесть, что среда для разработки разворачивается, как правило, один раз (ну, или достаточно редко), то это требование не кажется таким уж обременяющим. Да и выигрыш от использования такого подхода достаточно заманчив, ведь появляется возможность контролировать корректность работы одного из важнейших слоёв системы.

Кстати, если есть возможность использовать в модульных тестах LINQ2SQL и SMO для MS SQL Server, то можно воспользоваться следующим базовым классом для тестирования слоя доступа к данным:

После использования которого тесты на взаимодействие с БД будут выглядеть примерно так:

Вуаля! Мы получили возможность тестирования слоя доступа к БД.

Случай 2. ASP.NET MVC WebAPI

При тестировании WebAPI один из вопросов заключается в том, каким образом построить модульные тесты так, чтобы можно было протестировать вызов нужных методов нужных контроллеров с нужными аргументами при отправке запроса на определенный url. Если предположить, что ответственность контроллера заключается только в том, чтобы перенаправить вызов соответствующему классу или компоненту системы, то ответ на вопрос о тестировании контроллера сведется к тому, чтобы перед запуском тестов динамически построить некое окружение, в котором контроллеру можно было бы отправлять нужные HTTP запросы и, используя mock’и, проверить правильность настроенного роутинга. При этом совершенно не хочется использовать для разворачивания тестового окружения IIS. В идеале, тестовое окружение должно создаваться перед запуском каждого теста. Это поможет модульным тестам быть достаточно изолированными друг от друга. С IIS в этом плане было бы достаточно непросто.

Теперь можно использовать эти классы для тестирования выдуманного контроллера, который возвращает сериализованные даты и объекты некоего класса Platform.

Вот, собственно и все. Наши контроллеры покрыты модульными тестами.

Случай 3. Все остальное

А со всем остальным достаточно просто. Примеры модульных тестов на классы, которые содержат чистую логику, без взаимодействия с каким-либо внешним окружением, практически не отличаются от тех, которые предлагаются в популярной литературе типа «TDD by Example» Кента Бека. Поэтому каких-то особых хитростей здесь нет.

Добавлю, что помимо снижения количества ошибок в логике программы, от использования модульных тестов также можно получить следующие преимущества:

На сегодняшний день в проекте, над которым работает наша команда, около 1000 модульных тестов. Время сборки и запуска всех тестов на TeamCity составляет чуть больше 4 минут. Описанные в статье подходы позволяют нам тестировать практически все слои системы, контролируя изменение и развитие кода. Надеюсь, что наш опыт окажется для кого-нибудь полезным.

Источник

Зачем нужны юнит-тесты

Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тесты

Многие разработчики говорят о юнит-тестах, но не всегда понятно, что они имеют в виду. Иногда неясно, чем они отличаются от других видов тестов, а порой совершенно непонятно их назначение.

Доказательство корректности кода

Автоматические тесты дают уверенность, что ваша программа работает как задумано. Такие тесты можно запускать многократно. Успешное выполнение тестов покажет разработчику, что его изменения не сломали ничего, что ломать не планировалось.

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

Отличие от других видов тестов

Все вышесказанное справедливо для любых тестов. Там даже не упомянуты юнит-тесты как таковые. Итак, в чем же их отличие?

Ответ кроется в названии: «юнит» означает, что мы тестируем не всю систему в целом, а небольшие ее части. Мы проводим тестирование с высокой гранулярностью.

Это основное отличие юнит-тестов от системных, когда тестированию подвергается вся система или подсистема, и от интеграционных, которые проверяют взаимодействие между модулями.

Основное преимущество независимого тестирования маленького участка кода состоит в том, что если тест провалится, ошибку будет легко обнаружить и исправить.

И все-таки, что такое юнит?

Часто встречается мнение, что юнит — это класс. Однако это не всегда верно. Например, в C++, где классы не обязательны.

«Юнит» можно определить как маленький, связный участок кода. Это вполне согласуется с основным принципом разработки и часто юнит — это некий класс. Но это также может быть набор функций или несколько маленьких классов, если весь функционал невозможно разместить в одном.

Юнит — это маленький самодостаточный участок кода, реализующий определенное поведение, который часто (но не всегда) является классом.

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

Отсутствие сцепления необходимо для написания юнит-тестов.

Другие применения юнит-тестов

Кроме доказательства корректности, у юнит-тестов есть еще несколько применений.

Тесты как документация

Юнит-тесты могут служить в качестве документации к коду. Грамотный набор тестов, который покрывает возможные способы использования, ограничения и потенциальные ошибки, ничуть не хуже специально написанных примеров, и, кроме того, его можно скомпилировать и убедиться в корректности реализации.

Я думаю, что если тесты легко использовать (а их должно быть легко использовать), то другой документации (к примеру, комментариев doxygen) не требуется.

Тем не менее, в этом обсуждении после поста про комментарии видно, что не все разделяют мое мнение на этот счет.

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

При разработке через тестирование (test-driven development, TDD) вы сначала пишете тесты, которые проверяют поведение вашего кода. При запуске они, конечно, провалятся (или даже не скомпилируются), поэтому ваша задача — написать код, который проходит эти тесты.

Основа философии разработки через тестирование — вы пишете только тот код, который нужен для прохождения тестов, ничего лишнего. Когда все тесты проходят, код нужно отрефакторить и почистить, а затем приступить к следующему участку.

Об этой технике разработки можно много дискутировать, но ее неоспоримое преимущество в том, что TDD не бывает без тестов, а значит она предостерегает нас от написания жестко сцепленного кода.

И, поскольку TDD предполагает, что нет участков кода, не покрытых тестами, все поведение написанного кода будет документировано.

Возможность лучше разобраться в коде

Когда вы разбираетесь в плохо документированном сложном старом коде, попробуйте написать для него тесты. Это может быть непросто, но достаточно полезно, так как:

Источник

Unit-тесты: что, как и когда тестировать?

Тестирование программного кода — кропотливый и сложный процесс. Львиную долю работы в нем совершают unit-тесты. Пока они не «загорятся зеленым», тестировать дальше смысла нет.

Как же писать unit-тесты правильно? Стоит ли гнаться за 100% покрытием? С какими сложностями приходится сталкиваться инженерам на практике? Своим опытом делятся Marc Philipp и Всеволод Брекелов.

Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тесты

Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тестыMarc Philipp – один из основных разработчиков фреймворка JUnit 5 – инструмента для Java-тестировщиков. В данный момент работает в качестве инженера в немецкой компании LogMeIn над облачными SaaS-решениями.

Для чего нужны unit тесты. Смотреть фото Для чего нужны unit тесты. Смотреть картинку Для чего нужны unit тесты. Картинка про Для чего нужны unit тесты. Фото Для чего нужны unit тестыВсеволод Брекелов — Senior QA Engineer в компании Grid Dynamics, более 5 лет занимается тестированием, имеет опыт построения автоматизации тестирования с нуля.

— В статьях про unit-тестирование в качестве примеров обычно приводят тестирование методов и классов калькулятора. Такие примеры могут показать сложность реальных задач? С чем приходится сталкиваться тестировщику полнофункциональных программ?

Marc Philipp: Действительно, на примерах с калькулятором невозможно показать сложность реальных задач. Они выбраны в статьях для того, чтобы читатели могли сосредоточиться на понимании подходов unit-тестирования без необходимости разбора сложного кода. Хотя эти примеры очень простые, они хорошо демонстрируют основную идею и принципы unit-тестирования. В реальной жизни тестируемый код должен быть изначально написан с учетом того, что по нему будет проводиться Unit-тестирование. Один из способов обеспечить это — писать тесты до написания кода или практически одновременно с ним. Когда у вас есть код, адаптированный к тестированию, написание unit-тестов не на много сложнее, чем для калькулятора.

Всеволод Брекелов: Думаю, что сложность реальных задач можно понять только на реальных задачах. Если серьезно, то есть и хорошие статьи, где весьма подробно рассматриваются нетривиальные примеры. Думаю, что они помогут приблизиться в реальности.

К примеру, по запросу «unit тестирование java» можно быстро найти статью на Хабре. Она опубликована довольно давно, но не потеряла своей актуальности.

Что касается особенностей работы, я бы выделил следующие группы тестировщиков (надеюсь никого не обидеть):

Хотел бы обратить внимание на процесс. Я считаю, что каждый тестировщик должен хорошо разбираться в построении процесса разработки, так как в моей практике ноги, баги и основная трата времени на имплементацию того, что не нужно, растут как раз оттуда.

— Каждый тест должен проверять одну вещь. Насколько полно на практике удается выполнить это условие? Как вы боретесь с зависимостями, какие фреймворки используете?

Marc Philipp: При написании unit-тестов обычно берется один образец входных данных из класса эквивалентности в тестируемой проблемной области. Конечно, вы должны сначала определить эти самые классы эквивалентности. В каждом тесте вы добавляете assertion только для тех свойств, которые релевантны вашему тесту. Не следует копипастить одни и те же assertions в каждый новый тест и прогонять их. Когда у вас есть зависимости, влияющие на работу юнита, подумайте об использовании стабов или моков, чтобы сохранить независимость теста.

Многие наши юнит-тесты для JUnit 5 используют моки, создаваемые mocking-фреймворком (Mockito в нашем случае). Как я уже говорил выше, они очень полезны для тестирования изолированного кода. Главная задача при этом — убедиться, что ваш мок ведет себя аналогично реальному коду. В противном случае тесты станут бессмысленными.

Всеволод Брекелов: Да, есть мнение: один юнит тест — один assertion. На практике такое я видел очень редко. Думаю, что это уже философия команды. Множественные assertions вполне себе имеют место.

Если мы проводим юнит тесты, а не компонентные, то все зависимости изолируем (моки, стабы — все в ваших руках). Тут нет каких-то сложностей на мой взгляд. А если и появляются, то StackOverflow точно поможет.

Так как я пишу на Java/JavaScript(Angular), то использую обычные популярные тулы:
на Java – Mockito/EasyMock. Для компонентных тестов написать свой responsive mock — тоже хорошая идея! Всем советую.

JavaScript – ngMock. Кстати, для компонентых тестов очень классная тема – AngularPlayground.

— Как найти компромисс между трудовыми и финансовыми затратами на тестирование и качеством итогового софта при реализации «горящих» проектов? Как обычно вы аргументируете важность полноценного тестирования в таких случаях?

Marc Philipp: По моему опыту, вы не можете спасти «горящий» проект, пропустив тесты. Написание unit-тестов является неотъемлемой частью разработки программного обеспечения. Без него у вас нет возможности узнать, действительно ли ваш код выполняет то, что, по вашему мнению, он должен делать. Вы не сможете ничего быстро починить, так как не поймете, где что сломалось. Как сказал UncleBob, «единственный способ быстро поехать — это хорошо идти».

Всеволод Брекелов: Думаю, тут нет однозначного ответа. Скорее, помогает опыт работы и тип проекта. Если вы делаете медицинский проект или строите ракету, то о важности тестирования не приходиться говорить. Если пилите стартап за неделю – то какие тесты?

Очень важно организовать процесс, чтобы избежать внезапных багов и неправильно реализованных требований. Что такое правильный процесс? Конечно, есть Agile Manifesto, на который многие смотрят при организации процесса, но все равно что-то не выходит. Можно взять и построить процесс ради процесса. А можно и наоборот, последовать за http://programming-motherfucker.com/.

Мне кажется, главное – иметь требования, детализация которых устраивает разработчиков и тестировщиков в команде. Это значит, что у них одинаковое понимание того, что будет на выходе.

— Какие приемы помогают сократить время и трудовые затраты на тестирование?

Marc Philipp: «Тестирование» — перегруженный термин. Это может означать что угодно: модульное тестирование, ручное тестирование, тестирование производительности… По моему опыту, ручное тестирование, то есть ручное выполнение плана пошагового прохождения тестовых примеров, действительно дорого и часто не так эффективно, как вы думаете. Более того, автоматизация этих скучных тестов имеет смысл только в определенной степени. Тем не менее, вы должны действительно следовать тестовой пирамиде, а не писать слишком много этих end-to-end/UI тестов. Большинство ваших тестов должны быть реальными unit-тестами: независимые, быстрые тесты, которые вы можете выполнять очень часто. Написание этих тестов относительно дешево, особенно если вы знаете свои инструменты. Они очень надежны, поэтому вы не будете тратить время на их актуализацию. UI и Integration тесты всегда будут более хрупкими из-за огромного количества задействованных компонентов.

Всеволод Брекелов: Есть хороший прием — писать меньше кода.

Главное – это понимание процесса и того, что вы хотите решить (или протестировать).
Всегда нужно адекватно оценивать бюджет и время. Что это значит? Если вы можете себе позволить вливать кучу денег в приближение к 100% coverage — why not? Хозяин – барин.

Если у вас нет денег на автотесты (которые, как известно, отбиваются в основном в долгоиграющих проектах), то толпа ручных тестировщиков – ваш вариант.

Если не впадать в крайности, то самая частая ошибка — это написание e2e тестов пачками до потери пульса до того, как написаны юнит тесты, компонентные тесты, интеграционные тесты на Backend, Frontend, DB, Performance и тд. Эта тенденция, вероятно, следует от модных BDD подходов (я их не очень люблю). К чему это все приводит?

Первая степень «опьянения» — у вас начинает реально работать автоматизация. Ручные тест кейсы вы заменяете на автоматические. Тестировщики начинают радоваться. Менеджеры начинают думать, что вот-вот сэкономят.

Вторая степень — тестов становится много, почему-то некоторые из них периодически падают. Тестировщики уже не очень рады. Нужно сидеть и разбираться в причинах. А баги все равно пролезают. И, вероятно, даже находятся на QA окружении путем ручного (может, даже monkey) тестирования.

Третья степень — все начинают ходить на конференции про Selenium (ничего не имею против этих конференций), узнавать как бороться с Flaky тестами, пробовать различные решения. Пускать тесты в параллель.

Четвертая степень — строить целые суперархитектуры по запуску 500 e2e тестов на 50 агентах, чтобы все летало быстро, аж за 10 минут (я тут утрирую, конечно). И все равно баги есть.

Пятая степень — я назову ее недостижимой. Приходит осознание того, что бОльшая часть e2e тестов не нужна. Нужны другие тесты, которых никто никогда не писал. Например, компонентные тесты на back-end или они же на UI. А может, не они, может, системные тесты? А может, и тесты на верстку? А может, Ваш, вариант?

Безусловно есть проекты, где все сделано «правильно». Но зачастую встречается проблема непонимания того, что нужно протестировать. Только правильное понимание может сохранить ваше время и финансы. И более того, улучшить качество продукта.

— Как влияет на инструменты и подходы тестировщиков развитие средств разработки и подходов к созданию кода? Что из новшеств облегчает
unit-тестирование (например, представление методов в виде лямбда-функций)?

Marc Philipp: Новые инструменты стараются облегчить жизнь разработчикам, предоставляя им большую гибкость. Однако, в конце концов, я считаю, что не имеет значения, представляете ли вы свои тесты как методы или в виде лямбда-функций. Понять, что тестировать и как тестировать, — это самая сложная часть.

Всеволод Брекелов: Развитие средств и подходов влияет позитивно, если ими пользуются. Не всегда есть возможность применить хайповые технологии или подходы на работе. Мы все-таки решаем бизнес-задачи. Но находить баланс всегда можно.

Что облегчает тестирование — странный вопрос. Думаю, что технологии не могут сильно облегчить жизнь. Так как, чтобы использовать что-то новое (технология, инструмент), его нужно изучить всей команде, принять какую-ту «полиси», code style. Это в перспективе может, конечно, облегчить жизнь, но на коротких дистанциях не очень полезно, так как трудозатратно, имхо.

Кстати, вариант перехода на Kotlin (если мы говорим про Java тесты) – может и неплохая идея. Я в своей практике пока не пробовал.

Касательно новшеств языка (лямбды и прочие полезности) — это все хорошо, конечно, но мне трудно сказать, насколько они облегчают жизнь, так как нужно это измерить. Я не измерял. Но только не записывайте меня в противники прогресса, я считаю, что практика по изучению/использованию чего-то нового должна присутствовать всегда. Это обычная continuos improvement история.

— Насколько вы покрываете unit-тестами ваши продакшн проекты? Стоит ли тратить время на 100% покрытие?

Marc Philipp: В зависимости от языка программирования и фреймворков, которые вы используете, в проекте может быть некоторый шаблонный код, который не содержит никакой логики. Но кроме таких кусков, на мой взгляд, вы должны написать unit-тесты для всего вашего кода. Таким образом, я бы посоветовал охват более 90%.

Всеволод Брекелов: В проектах, в которых мне приходилось работать, чаще всего разработчики стараются довести тесты до покрытия в 90%. Стоит ли тратить время – обычно решается менеджерами. Я не менеджер, но по мне юнит тесты – это очень хорошая практика, 100% покрытие хорошо иметь, когда есть на это ресурсы.

Главное, надо помнить, что 100% покрытие, к сожалению, не гарантирует, что у вас нет багов.

Из того, что кажется более полезным, чем гонка с 90% до 100% coverage, — это написание мутационных тестов. Ничего не скажу нового относительно статьи 2012 года. Но на практике не очень часто видел, чтобы применяли этот подход (да и сам я тоже, каюсь). Так может быть пора начинать?

— Как тестовые фреймворки помогают с unit-тестами? Какую часть работ они берут на себя? Чего не стоит ждать при использовании фреймфорков?

Marc Philipp: Хороший фреймворк позволяет очень быстро и легко писать простые unit-тесты и в то же время содержать мощные механизмы для проведения более сложных тестов. Например, он должен помочь вам подготовить тестовые данные и предоставить точки расширения, которые позволят вам повторно использовать одну и ту же логику во многих тестах. Но никакой фреймворк не решит за вас, что и как тестировать. Также он не может волшебным образом улучшить ваш проект, чтобы сделать его хорошо тестируемым.

— Какие элементы кода сложнее всего поддаются unit-тестированию? Как решается эта проблема у вас?

Всеволод Брекелов: Чем больше зависимостей — тем больше рутины, тем сложнее писать юнит тест. А в целом, не вижу каких-то особенных проблем, если честно. Хотя на тему unit тестов написано большое количество книг, из которых я ни одну не прочитал до конца. Может, поэтому я не обременен проблемами.

Например, сложно написать unit-тест, когда, скажем, конструктор объекта содержит в себе вермишели кода, но тогда можно советовать товарищам прочитать книжки,
например и ввести code review практику.

Что касается JavaScript кода, то там можно встретиться с различными сложностями и внезапностями (да, я очень люблю JavaScript), скорее связанными с используемым фреймворком, например, работа с digest’ом. Я использовал только AngularJS/Angular2/Angular4. Несмотря на старания команды Angular сделать удобно-тестируемый фреймворк, все равно периодически сталкиваешься с проблемами, которые безусловно имеют решения, мы ведь инженеры.

Огромный массив информации о всех аспектах тестирования ждет участников на ближайшем Гейзенбаге, где Mark Phillip прочтет доклад «JUnit 5 — The New Testing Framework for Java and Platform for the JVM».

О том, какие еще знаковые фигуры выступят на конференции и смогут ответить на самые актуальные вопросы в кулуарах, можно узнать на сайте.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *