Автотесты, как известно, работа пыльная и ресурсозатратная. А уж если речь идет об интеграционных, то тем более: сначала осуществляешь сборку теста, затем добавляешь его в нужную среду, а потом еще тот самый деплой, подготовка которого может занимать критически много времени… Но будучи SDET-ом, то есть совмещая в себе навыки разработчика, тестировщика и DevOps, я постигаю архитектуру тестов и иногда нахожу интересные решения по ее оптимизации :)



В этой статье расскажу, как тестировать ASP.NET-приложения максимально быстро, не закрывая IDE и вообще не запуская деплой! Покажу, как при таком подходе создавать не только, например, тесты REST API, но и веб-тесты с использованием Selenium или Playwright. Объясню, что такое TestServer и WebApplicationFactory на конкретных примерах, и продемонстрирую, как с ними можно работать!

Статья будет максимально полезна тем, кто так или иначе уже погружен в непростой мир .NET и C# (и не утонул в нем). Для вас это готовый туториал по интеграции всех этих инструментов! Впрочем, это у нас в компании широко используются .NET и «шарпы» — но даже если ваши тесты пишутся не на C#, все равно рекомендую пробежаться по моей статье! Уверен, вы сможете почерпнуть для себя пару хороших идей, ведь все описанное ниже можно реализовать и на других языках программирования.

Какие вообще бывают тесты и в чем между ними разница


Сначала немного теории и проблематики. Наша команда тестирования в департаменте Cloud Infrastructure & Web Portals Development «Лаборатории Касперского» обслуживает более 20 небольших систем, в каждой из которых есть порядка пяти микросервисов. Весь этот зоопарк взаимодействует между собой, и это нужно тестировать.

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



Что ж, с подходом «черного ящика» можно тестировать что угодно. Но рано или поздно у вас в пирамиде накопится очень много интеграционных тестов, и это плохо, потому что:

  • Когда у вас ломается какой-нибудь один микросервис, возможность тестировать другие пропадает. Большое количество сценариев тестирования блокируется.
  • Интеграционные тесты долгие. Любые изменения нужно задеплоить на интеграционную среду, а чтобы что-то проверить — пройти весь путь end-to-end теста. Если в тесте участвует 20 микросервисов, нужно много времени, чтобы процесс дошел до всех. В нашем случае это могло занять всю ночь, то есть обратная связь разработчику о том, работает ли его фича, приходила только на следующий день.

Один из вариантов решения — разделить тесты на небольшие группы.



Микросервисам нужны какие-то другие инструменты — базы данных, внешние ресурсы. Все это можно заменить на моки или какие-то одноразовые аналоги (ранее я об этом рассказывал в этой хабростатье).



Подход с разделением тестов на небольшие группы мы у себя зовем L2-тестами (от английского Level или Layer 2). L2-тесты решили ряд проблем интеграционных тестов — они стали короче и быстрее, но появилась пара новых сложностей. Чтобы запустить тесты L2, нужно создать какие-то временные ресурсы, моки и выборочно задеплоить изменения. Потребовались локально установленные Docker и Terraform, то есть процесс стал сложнее с точки зрения деплоя. А кроме того, такие тесты сложнее дебажить и развивать, потому что просто некуда подключиться, чтобы все отладить.

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



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



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

Как работать с test server и test host — и какие преимущества это дает?


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

  • backend реализует REST API, всего четыре стандартных метода Post, Get, Put, Delete, которые позволяют создавать заметки;
  • а frontend — UI для этого приложения.

Оба приложения написаны на C#.



Для тестирования этого приложения с помощью L1 без деплоя будем использовать библиотеку Microsoft.AspNetCore.Mvc.Testing. На Хабре и в других источниках ее часто можно встретить под названием Test Server или Test Host. И первое, и второе название — это просто классы из данной библиотеки.


Реализация: https://github.com/dotnet/aspnetcore/tree/main/src/Hosting

В мире C# и .NET эта библиотека очень популярна — в середине 2024 года у нее было уже 119 миллионов скачиваний. В самой Microsoft также тестируют с ее помощью. По ссылке — пара примеров, как это происходит.

Перед самим тестированием надо убедиться, что библиотека нам в принципе подходит. Для этого заглянем в код нашего сервиса от разработчиков.



Здесь не требуется погружаться глубоко. На таком этапе нам нужен файл Program.cs, с которого начинается выполнение приложения. Посмотрим, есть ли там строка, содержащая Host.CreateDefaultBuilder или WebApplication.CreateBuilder.





Ее может не быть в таком явном виде, первой строчкой внутри файла. Но если у вас веб-приложение, написанное на C#, она почти наверняка есть, просто где-то глубже — в выделенном классе или отдельном пакете.

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

Самый простой тест будет выглядеть так:



Здесь мы создаем переменную factory и используем класс WebApplicationFactory. Мы подаем ему на вход то, что хотим протестировать, — в данном случае я тестирую backend-приложение, файл и класс «Program» которого и указываю. У переменной factory можно вызвать метод CreateClient. А имея созданный объект httpClient, можно делать HTTP-запросы, например Get, Post и так далее.

Чтобы дернуть из приложения метод Get, воспользуемся внутренним методом api/todo/records, который возвращает записи. И записи действительно вернутся — тест будет работать.

Вот так просто можно написать тест. При его запуске где-то в памяти операционной системы запустится версия тестируемого приложения, в него отправится GET-запрос, и вернется ответ в json-формате. Конечно, это лишь простейший пример, который не демонстрирует всех возможностей библиотеки. Далее поговорим о других, более интересных возможностях :)

Доступ по сети


Предположим, нам нужен сетевой доступ. Например, у нас есть готовый проект api-контрактов для тестирования некого бэкенд-сервиса или мы собрались тестировать фронтенд приложения, запуская веб-тесты Selenium, Playwright и другие.

Вернемся к предыдущему тесту и добавим в него внешний RestHttpClient и попробуем с его помощью отправить Get-запрос:



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

Однако если это нужно для веб-тестов или самого HTTP-клиента, это можно реализовать. Создадим класс CustomWebAppliucationFactory и «отнаследуем» его от WebApplicationFactory. Так мы сможем внутри переопределять методы, начиная их с ключевого слова override.



На самом деле все это не методы библиотеки WebApplicationFactory (или TestServer), а методы самого приложения, которые разработчики регулярно используют. Чаще всего вызовы этих методов можно найти в файлах program или startup проекта веб-приложения. Мы же, выполнив override, можем дополнить все это своими действиями или исключить то, что не нужно для тестирования. В этом и заключается основная возможность библиотеки — она позволяет менять поведение запуска приложения.

Мы создали класс CustomWebAppliucationFactory. Остается написать внутри свою реализацию доступа к сети. Для этого достаточно добавить builder.UseKestrel().



Не буду загружать вас деталями, но если кратко, Kestrel — это веб-сервер в мире .NET, который позволяет приложению регистрироваться в операционной системе и расположиться где-то в сети. Соответственно, как только мы сказали приложению, что оно должно зарегистрироваться в ОС, можно открыть, например, Postman и кинуть запрос или открыть браузер и перейти по ссылке — и вот теперь-то по ссылке ответит запущенное прямо из теста приложение.

Доступ по сети можно дополнительно сконфигурировать.

  • Если тестируемый сервис сконфигурирован на основе вызова WebApplication.CreateBuilder(), можно добавить вызов метода UseSettings и указать конкретный порт, на котором должно работать приложение, или вписать нулевой порт. Тесты можно будет запускать в параллели («0» означает, что приложение будет запущено на любом свободном порту).
  • Если тестируемый сервис сконфигурирован на основе вызова Host.CreateDefaultBuilder, то путь будет чуть сложнее, нужно будет прокинуть порт через IConfiguration, но я подготовил для вас пример, как это сделать.

На примере уже написанного теста создаем кастомный класс WebApplicationFactory, внешний HTTP-клиент и кидаем Get-запрос. И такой тест уже сработает, несмотря на то что HttpClient здесь внешний!



Подмена зависимостей


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

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

В нашем случае есть интерфейс IStorage и класс, который его реализует, — TodoDatabaseStorage, где внутри есть то самое подключение к базе. Значит, от этого класса и нужно избавиться.



Для этого создадим свой класс, назвав его, допустим, ToDoDatabaseMock. Пусть он реализует тот же интерфейс. Нам потребуется описать какие-то методы, необходимые этому интерфейсу. Но в целом современные IDE позволяют это делать почти автоматически.



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



Чтобы это сделать, возвращаемся в класс CustomWebApplicationFactory и внутри метода ConfigureWebHost пишем builder.ConfigureServices.



Чтобы определить, что именно писать внутри, мы находим ту самую инструкцию, которую показал разработчик, — ServiceDescriptor (в нашем случае, допустим, это dbServicesDescriptor — инструкция, создающая объект типа IStorage). Дескрипторы описывают, как в системе создается тот или иной класс. Существующая инструкция создания объекта нам не подходит, потому что она создает объект класса, который ходит в реальную базу данных. Но если просто удалить все это, система, скорее всего, не запустится — выдаст ошибку, что понятия не имеет, как создавать объекты типа IStorage. Поэтому взамен нужно создать другую инструкцию, определяющую, что IStorage — это на самом деле наш мок (в него можно и тестовые заметки закинуть, если нужно).



Так мы заменили настоящий сервис на некий мок — теперь можно начинать тестирование.

Пример теста будет выглядеть так:



Здесь мы создаем тестовые заметки и WebAppFactory, получаем инструкцию для нужного объекта, удаляем старую инструкцию, добавляем новую со ссылкой на мок. При этом в мок передаем те самые тестовые заметки.

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

Замена значений


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

Покажу на примере реального теста.

Как и в прошлом примере, мы создаем фабрику, в ней идем внутрь и обращаемся к переменной Services, но теперь вызываем метод BuildServiceProvider. Это своего рода «завхоз» в мире кода — сервис, который знает, где находятся другие сервисы и как их создавать.



Мы запрашиваем объект сервиса, который реализует интерфейс IConfiguration (в мире .NET конфигурация часто реализуется этим интерфейсом).



В итоге у нас есть объект сonfig, где мы можем просто взять и обновить значение. В частности, можем взять адрес реального сервера и заменить его на адрес мока.



Все это будет работать с одной оговоркой. Важно то, как именно регистрировались те сервисы, в которых мы обновляли значения. Нужно понимать не только конкретный класс и интерфейс, который показал разработчик, но и что конкретно написано перед сервисом. Там могут быть указаны на первый взгляд не очень понятные слова — singleton, transient, scoped, perRequest…



Singleton и transient — это так называемые LifeStyle. Это поведение, описывающее жизненный цикл существования объектов. В целом это все можно назвать частью реализации паттерна «Dependency Injection». И про сам паттерн, и про LifeStyle я писал в этой статье (та самая статья про автосервис с таксой, кто в теме ;-)). Здесь повторяться не буду, но отмечу, что подход с обновлением конфигурации работает только с объектами singleton. Переводя на русский, это объекты-одиночки, которые после создания существуют в единственном экземпляре и не обновляются (всем раздается один и тот же объект). Transient и другие лайфстайлы работают немного по-другому, и для подмены значений в них нужно писать мок-сервисы либо перехватывать запросы, используя паттерн Decorator. В конце статьи будет ссылка на исходный код, там есть примеры реализации и такого подхода.

Использование http-моков


Заменять сервисы показанным ранее способом сложно. Часто хочется тестировать проще — просто заменить ссылку на реальный сервис на адрес мока. Если она HTTP и взаимодействие идет по REST, можно использовать HTTP-моки. Рекомендую обратить внимание на библиотеку WireMock.NET. Это копия проекта под Java и Python, который называется просто WireMock. По сути, это та же интерпретация мок-сервера, просто из мира .NET. Естественно, WireMock.NET — не единственный. Существует множество общедоступных популярных инструментов. Просто на его примере легко показать мою идею.

Проект WireMock.NET опенсорсный. Его можно скачать пакетом и подключить к своему репозиторию: https://github.com/WireMock-Net/WireMock.Net.

Чтобы использовать WireMock.NET, создадим новый класс. Назовем его, допустим, WireMockProvider. В нем достаточно написать одну единственную строчку — WireMockServer.Start, записав результат выполнения в переменную Server.



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



Этот пример показывает, что к мок-серверу может прийти Post-запрос с методом /api/email/, а ответить на него нужно кодом 201 и моделью, в которой есть поле Status со значением Success. Так мы настроили маппинг ответа — и этого достаточно, чтобы мок-сервер все понял.

Сам мок-сервер можно кастомизировать по-разному — у библиотеки очень много настроек: в частности, можно генерировать случайные или неслучайные данные.

Вот как будет выглядеть реальный тест:



Здесь мы создаем новый объект класса WireMockProvider и, обращаясь к нему, «мокаем» вызов.



Затем создаем объект фабрики. Мы уже так делали в предыдущем примере:



Здесь после BuildServiceProvider и GetService заменяем адрес реального сервера на адрес мока. Теперь все готово к реальному тесту, при этом в код мы не вмешивались — ссылку поправили только в конфигурации.

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

Прогон веб-тестов


На всякий случай напомню, что Get-запрос, который мы кидаем в каком-нибудь Postman, это не то же самое, что открыть ссылку в браузере. По Get мы получаем скелет страницы и только его, то есть HTTP-разметку в минимальном представлении. В отличие от этого запроса, браузер запускает JavaScript-код со страницы, который может перерисовать все несколько раз, выполнить еще множество запросов и в целом сильно поменять конечное состояние страницы. Поэтому если стоит задача протестировать фронт приложения, который так или иначе написан на JavaScript, нужны библиотеки, работающие с веб-драйвером, например Selenium или Playwright.

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

Чтобы написать веб-тесты L1, никаких новых знаний не нужно. Единственное, с чем стоит определиться сразу, самодостаточно ли тестируемое фронтенд-приложение. Если оно само обрабатывает запросы и отвечает, то можно его просто запускать через WebApplicationFactory и начинать тестировать. Однако чаще всего фронтенд-приложение реализует только UI, а запросы отправляет куда-то на бэкенд. Соответственно, придется думать, удастся ли запустить этот бэкенд тоже. В примере ниже так можно было сделать, потому я создаю объекты типа WebApplicationFactory дважды — один раз для frontend-приложения, и второй раз для backend’а.
Если же backend-приложение по каким-то причинам не может быть запущено через TestServer — то можно поднять мок вместо него, как я описывал выше.

Для начала нам нужно включить доступ по сети. Я пояснял в начале, как это сделать, — как минимум строкой UseKestrel, а возможно, и дополнительными настройками. Далее запускаем фронтенд-приложение, определяемся, по какой ссылке надо сходить, — в нашем случае «<адрес фронтенда>/Home».



Здесь мы запускаем веб-драйвер — Selenium на Chrome. Ну а дальше пишем стандартный веб-тест, как это было бы на интеграционном уровне автотестов: создаем объект page, инициализируем какие-то элементы. И, например, получаем список записей в таблице для проверки.

Все это никак не привязано к Selenium и может быть реализовано на любом веб-драйвере. На Playwright все будет работать точно так же — эти два веб-движка реализуют один и тот же подход.

Так ли хороши L1-тесты и стоит ли с ними работать?


Спойлер: да! Но есть нюансы…

Плюсы


Тесты стали быстрее!
Если мы однажды потратили время на то, чтобы заменить все зависимости, после
этого больше не придется ни к чему подключаться. Мы можем тестировать без Docker-а с Redis-ом или локального PostgreSQL. Можем спокойно запускать тесты даже без интернета. Все внешние ресурсы вполне заменяемы, и тестировать сервисы получается быстрее.

Упрощение интеграционных тестов
Раньше для запуска интеграционных тестов надо было сделать сборку, доставить ее на среду, задеплоить (а возможно, еще и подождать перед этим, пока среда освободится от приемки релиза) и уже только потом переходить непосредственно к тестам…
С L1-тестами же этого не нужно! Мы просто забираем все с конкретной ветки, где разработчик только что пилил эту фичу, — и запускаемся. И уже с этого момента можем заводить баги (если они есть), а заодно и делать выводы — работает все это или нет. Так разработчик будет получать ответ гораздо быстрее!

L1-тесты объединяют команду (внезапно)
Это плюс несколько из другой сферы — но все равно важный! Если раньше автотестеры жили в своем мире и никак не взаимодействовали с разработчиками, то теперь проекты приложения и тестов лежат рядом. Разработчики так или иначе будут заглядывать в код тестов, чтобы подебажить новые фичи. А тестировщики вынуждены обращаться напрямую к коду разработчиков, чтобы посмотреть, какие зависимости можно убрать. Команда начинает работать на единой волне, потому что у нее появляется общее пространство.

Ну и очевидно, что с L1-тестами, когда есть возможность заменить все что угодно, можно тестировать намного больше и находить ошибки там, где раньше мы и не предполагали. Например, при подключении к суперстабильному микросервису. Мы можем проверить, что будет, если он вернет неверные данные. А вот с помощью интеграционных тестов этого не сделать — микросервис никогда не падал, но на самом деле наша система и не была готова к его падениям.

Минусы


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

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

Не тестируем окружение
Стоит помнить, что L1-тесты запускаются локально — например, на вашем ноутбуке с Windows. В реальности же сервис будет крутиться в Docker, в Kubernetes, а там будет все по-другому. Платформу, где все будет запускаться на проде, мы никак не тестируем. Надо помнить, что L1-тесты только про бизнес-логику и, наверное, только про функциональные требования.

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

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

Еще один нюанс — дробление тестов
Если раньше был один большой супертест end-to-end, который сам проходил 20 микросервисов и через полчаса после запуска выдавал красивое окошко Success, то теперь мы не можем просто нажать кнопку и откинуться на стуле (а жаль…). Теперь придется писать 20 маленьких тестов: каждый из них будет в разы быстрее (потому что многое мы будем мокать) и в будущем это сэкономит много времени на поддержку, отладку и доработку. Однако вначале это время нужно будет откуда-то взять — сами по себе тесты не напишутся.

И немного советов


Если недостатки вас не смутили, а плюсы бодрят, дам несколько рекомендаций.

Старайтесь подменять только внешние библиотеки. Если в проекте есть библиотеки для работы с Redis или Kafka, скорее всего, ваш код использует ее публичную stable-версию. Поверьте, вы ничего в ней никогда не найдете, поскольку ее используют миллионы людей и компаний по всему миру. Баг в такой библиотеке — исключительный случай. На них можно положиться с определенной степенью доверия.

А вот если вы подключаете свою библиотеку, имейте в виду, что кроме вас ее никто не проверяет. Она нестабильная и вполне может содержать баги. Поэтому ее нужно либо не исключать, либо покрывать тестами более высокого уровня.

Еще один важный момент — L1-тесты не отменяют более высокоуровневого тестирования. На реальной среде может быть другая платформа со своими особенностями. Тут надо проверить сам деплой, инфраструктуру и настройку конфигурации, а также интеграции, которые мы замокали.

Но важно, что сам факт существования L1-тестов действительно сокращает время прогона. Нам уже не потребуется ждать целую ночь, потому что достаточно запустить всего один тест. Остальное же спустится на уровень ниже. Но этот один всеобъемлющий end-to-end-тест должен оставаться — интеграция обязательно должна хоть как-то тестироваться.

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

Test Driven Development
Еще рекомендую попробовать вариации подхода Test Driven Development (TDD). Если кратко — вместе с постановкой задачи можно уже написать один единственный L1-тест. На этом этапе он будет красным, поскольку не проверяет ничего рабочего, но по его структуре уже должно быть понятно, что будет на выходе. Тестировщик видит этот тест, и ему не придется устраивать дополнительный созвон и просить разъяснить постановку задачи. Его задача — накодить так, чтоб этот тест стал проходить. А тестировщику не надо идти к разработчику с вопросами, что он там накодил и как это теперь проверять. У обоих есть готовый пример.

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

И не могу не упомянуть еще один момент. В самом первом примере я показывал тест, в котором мы не подключались к реальной сети, а запускали Internal HttpClient, и его запросы обрабатывались в памяти. На первых порах это кажется излишним — тесты вроде бы можно запускать и с включенной сетью. Но когда тестов будет 1000 и более, вы заметите просадку производительности, как только начнутся конфликты портов или нехватка других ресурсов. Поэтому если есть возможность остаться только в памяти, это будет оптимальнее, а фидбэк будет быстрее.

На этом у меня все. Проект с примерами из статьи, и даже сильно больше, опубликован на GitHub, надеюсь, будет полезно!

Все это — лишь часть задач, с которыми сталкиваются SDET-ы в «Лаборатории Касперского». И если вас вдохновил мой кейс — скорее залетайте в наши SDET-команды, скучно точно не будет :)

Комментарии (0)