Структурирование логических тестов |
10.12.2024 00:00 |
Автор: Ноэми Феррера (Noemi Ferrera) В предыдущей статье я утверждала, что тесты не следует делить на тесты черного и белого ящика. Однако многие знакомы с этими терминами, и для них, возможно, «отбеливание тестов черного ящика» будет понятнее, чем «структурирование логических тестов». Опишу контекст, чтобы пояснить, что конкретно я имею в виду. Когда тесты создают разные команды, зачастую они хотят убедиться, что все покрыто и все тесты верны – особенно если между командами или их участниками нет доверия, или создатели тестов уже покинули компанию. Вместо того, чтобы поделиться определениями тестов (в некоторых командах они даже не зафиксированы) и вникать в логику интеграционных и E2E-тестов, некоторые стремятся разработать автоматизированные проверки, верифицирующие, что все покрыто. Тут и начинается структурирование логических тестов: они хотят превратить логические тесты в структурные. Типичный пример – когда люди хотят узнать покрытие кода интеграционными или end-to-end тестами. Это возможно, но очень сложно и дорого, да и смысла особенного не несет. Как логический тест превращается в структурный? Для начала нужно сделать код инструментальным. Это значит, что код изменится – он будет компилироваться и сохраняться вместе с номерами строк и другими наборами инструкций, измеряющими покрытие, когда в результате вызова из другой части кода отрабатывает каждая строчка. Затем этот инструментальный код нужно сделать доступным для тест-пакета. Это можно сделать при помощи упаковщика вроде docker, напрямую соединяя их локально или занимаясь этим в облаке. Как бы это ни было сделано, это значит, что понадобится много места – как для обеих частей кода, так и для инструментария. Запустив тесты, вы увидите количество строк кода, отработавших в ходе выполнения тестов. Но что это значит с точки зрения качества? Покрытие кода – универсальный знак качества? Мы говорим о покрытии кода, имея дело с юнит-тестами, потому что юнит-тесты нацелены на проверку структуры кода. В этом контексте использовать покрытие кода для оценки качества имеет смысл. Мы знаем, как выглядит или будет выглядеть код, когда пишем тесты, мы хотим проверить приложение на структурном уровне и таким образом его валидировать. Следовательно, мы валидируем, что все строки или ветки кода выполняются при прогоне тестов, и выясняем, есть ли тут проблемы. Мы можем также валидировать качество наших тестов – если какой-то код не выполнился, нам, скорее всего, не хватает тестов. Когда мы имеем дело с интеграционными тестами, мы хотим проверить интеграцию между различными компонентами. Хотя зачастую это и проводится со знанием структуры кода (юнит-тесты как бы напрямую используют интеграции, а не имитируют их), хорошие практики этого не требуют. В целом удобнее знать список вызовов API или служб, предоставляемых системой и необходимых ей, и убедиться, что эти вызовы как следует протестированы. Имея дело с end-to-end-тестами, мы, как правило, хотим проверить поведение/путь пользователя. Нам на самом деле неважно, как это происходит – мы просто хотим знать, что оно происходит. Мы проверяем валидность логики приложения, а не правильность кода. Нет никакого смысла проверять количество строк кода, покрытых этими типами тестов, потому что мы тестируем совершенно иную концепцию. Более того, чем выше мы идем по тест-пирамиде, тем меньше у нас должно быть тестов. Поэтому их измерение в строках кода противоречит этому принципу, так как на уровне end-to-end-тестов мы можем пропустить большинство этих строк. К тому же на этом этапе они должны уже быть проверены юнит-тестами. Единственная ситуация, когда нам может понадобиться замерить покрытие кода в ходе интеграционного тестирования – это отсутствие имитаторов в юнит-тестах и наличие некоего гибридного решения, чего-то среднего между юнит- и интеграционным тестированием – это не очень хорошая практика, но иногда встречается. Что надо делать взамен? Мне кажется чудесным, что всем небезразлично качество и все в него вовлечены, но не надо изобретать колесо – есть альтернативы. Для начала убедитесь, что все команды согласны со списком тест-кейсов. Если он существует, проведите ревью, выясните, чего не хватает. Если его не существует, согласуйте с ответственной за тестирование командой совместное создание такого списка (и пожалуйста, не делайте это в Экселе). Убедитесь, что у вас заранее составлены кейсы для будущих фич. И как бы ни было противно это говорить, но если вам тяжко, попробуйте какое-нибудь BDD-решение, но убедитесь, что пользуетесь им с умом и согласно его предназначению. Возможно, вам будет скучно письменно фиксировать все, что вы хотите протестировать, и обсуждать это с командой, но если все сделано правильно – это хорошо работает. Имея на руках список кейсов, вам надо договориться о создании автоматизации, чтобы понимать тестовое покрытие (а не покрытие кода) – в смысле, сколько кейсов было автоматизировано или выполнено. Теперь, когда вы знаете, что должен покрывать каждый тест, вы имеете представление, насколько хорошо ваше покрытие, еще до того, как эти тесты написаны. Можно сделать еще шажок и автоматизировать проверки на основе тест-определений, или даже автоматизировать эти определения из существующей тест-автоматизации. Для этого можно даже воспользоваться какой-нибудь умной системой. Наконец, убедитесь, что у вас есть какие-либо «контрактные» тесты, валидирующие предыдущий уровень. Отделите эти тесты от обычных тестов так же, как и юнит-тесты – можете оставить только жизненно необходимые тесты базовых сценариев. Однако будьте осторожны, используйте их бережливо, иначе все кончится именно той проблемой, которая здесь обсуждается. В конце концов лучше добиться согласья в товарищах, чем пытаться протестировать все на свете просто на всякий случай, но это уже другая история. |