Тестирование с инструментами Microsoft — полевой опыт
Эта статья создана нашими друзьями, партнерами из компании Лаборатория Касперского и описывает реальный опыт использования инструментов тестирования от Microsoft с рекомендациями. Автор — инженер по тестированию в Лаборатории Касперского, Игорь Щегловитов.
Привет всем. Я работаю инженером по тестирования в Лаборатории Касперского в команде, занимающейся разработкой серверной облачной инфраструктуры на облачной платформе Microsoft Azure.
Команда состоит из разработчиков и тестировщиков (примерно в соотношении 1 к 3). Разработчики пишут код на C# и практикуют TDD и DDD, благодаря этому код получается пригодным для тестирования и слабосвязанным. Тесты, которые пишут разработчики, запускаются либо вручную из Visual Studio, либо автоматически при сборке билда на TFS. Для запуска билда у нас установлен триггер Gated Check-In, таким образом он запускается при чекине в Source Control. Особенностью данного триггера является, то что если по каким-то причинам (будь то ошибка компиляции либо тесты не прошли) билд падает, то сам чекин, который запустил билд не попадает в SourceControl.Вы, наверное, сталкивались с утверждением, что код протестировать сложно? Некоторые прибегают к парному программированию. В других компаниях специально выделены отделы тестирования. У нас же это обязательное код-ревью и автоматизированное интеграционное тестирование. В отличие от модульных — интеграционные тесты разрабатываются специально выделенными инженерами по тестированию, к которым отношусь и я.Клиенты взаимодействуют с нами через удаленное SOAP и REST API. Сами сервисы основаны на WCF, MVC, данные же хранятся в Azure Storage. Для надежности и масштабирования длительных бизнес-процессов используются Azure Service Bus и Azure Cloud Queue.
Немного лирики: есть мнение, что тестировщик — это некая ступень, чтобы стать разработчиком. Это не совсем так. Грань между программистами и инженерами по тестированию с каждым годом исчезает. Тестировщики должны обладать большим техническим бекграундом. Но в то же время иметь несколько иное мышление, нежели разработчики. Тестировщики должны быть нацелены на разрушение в первую очередь, разработчики же на создание. Вместе же они должны стараться сделать качественный продукт.
Интеграционные тесты, также как и основной код, разрабатываются на C#. Имитируя действия конечного потребителя, они проверяют бизнес процессы на сконфигурированных и запущенных сервисах. Для написания этих тестов используется Visual Studio и фреймворк MsTest. Разработчики используют то же самое. В процессе тестировщики и разработчики производят взаиморевью кода, благодаря чему члены команды могут всегда говорить на одном языке.
Тестовые сценарии (тест-кейсы) живут и управляются через MTM (Microsoft Test Manager). Тест кейс в данном случае — такая же сущность TFS, как баг или таск. Тест-кейсы бывают ручными или автоматическими. В связи со спецификой нашей системы, мы используем только вторые.
Автоматические тест кейсы связываются по полному CLR наименованию с тестовыми методами (один кейс — один тестовый метод). До момента появления MTM (Visual Studio 2010) я запускал тесты как модульные, ограничиваясь студией. Было сложно строить отчеты по тестированию, управлять тестами. Теперь это делается напрямую через MTM.
Появилась возможность объединять тест кейсы в планы тестирования, а внутри планов строить плоские и иерархические группировки через TestSuite.
У нас Feature Branch разработка, т.е. крупные доработки делаются в отдельных ветках, стабилизационных и релизных. Под каждую ветку у нас создан тест план (чтобы избежать хаоса). После завершения стабилизации в Feature Branch ветке, в основную ветку переносится код, а тесты переносятся в соответствующий ей тест план. Тест-кейсы очень легко добавлять в тест-планы (Ctrl+C Ctrl+V, либо через TFS query).
Немного рекомендаций. Лично я стараюсь для каждой ветки разделять стабильные тесты от новых. Это легко делать через TestSuite. Особенность здесь в том, что стабильные (или регрессионные) тесты должны проходить на 100%. Если у вас не так, то стоит задуматься. Ну, а после того, как новый функционал становится стабильным, то соответствующие ему тесты просто переносятся в регрессионный Test Suite.
Лично для меня одним из самых скучных и рутинных процессов было создание тест-кейсов. Процесс разработки автотестов у всех разный. Кто-то в начале пишет подробные тестовые сценарии, а потом на основе создает авто тесты. У меня наоборот. Я пишу в коде (в тестовом классе) тесты-пустышки (без логики). Потом уже идет реализации логики, архитектуры компонент тестирования, тестовых данных и так далее.
После того как тесты созданы, их надо перенести в TFS. Одно дело, когда тестов штук 10, а если 50 или 100, придется потратить кучу времени на рутинное заполнение шагов, связывания каждого нового тест-кейса с тестовым методом.
Для упрощение данного процесса Microsoft придумал утилиту tcm.exe, которая умеет автоматически, создавать тестовые сценарии в TFS и включать в план тестирования. Данная утилита имеет ряд недостатков, в один тест план почему-то нельзя добавить тесты из разных сборок или для тест-кейса нельзя задать шаги и нормальное название. Кроме того, сама утилита появилась сравнительно недавно. Когда ее еще не было — мы создали самописную утилиту которая автоматизирует процесс создания тестовых сценариев. Также разработаны специальные кастомные атрибуты TestCaseName и TestCaseStep, для задания имени и шагов тестового сценария.
Сама утилита может как обновлять, так и создавать тестовые сценарии, включать в нужный план. Утилита может работать в silent режиме, ее запуск включен в Worfklow TFS билда. Таким образом она запускается автоматически сама и добавляет либо обновляет существующие тестовые сценарии. В результате мы имеем актуальный тест-план. Статус прогона тестов в тест-плане (что эквивалентно качеству кода) виден в специальных отчетах. В TFS есть набор шаблонов готовых отчетов, которые на основе данных OLAP кубов строят диаграммы по статусам тест кейсов и тест планов. Но у нас отчет самописный и упрощенный. Мы сделали его таким, чтобы каждый, кто на него бы ни посмотрел, сразу все понял.
Вот пример:
Особенностью отчета является то, что он строится не на основе кубов, которые перестраиваются не синхронно и могут содержать еще не актуальные данные, наш отчет использует данные, которые подтягиваются напрямую через МТМ API. Таким образом мы в любой момент времени получаем актуальный статус плана тестирования.Отчет строится в HTML формате и рассылается на всю команду через SmtpClient. Делается это все с помощью простой утилиты, запуск которой включен прямо в Workflow билда: скачать утилиту. Кроме управления тестовыми сценариями, МTM используется и для управления окружениями тестирования и настройкой агентов, запускающих тесты. Чтобы ускорить выполнение тестов (здесь идет речь о тестах, проверяющих длительные асинхронные бизнес процессы) мы используем 6 агентов тестирования, т.е. за счет горизонтального масштабирования агентов у нас достигается параллельность выполнения тестов. Сами по себе интеграционные авто тесты можно вручную запускать как через MTM, так и через Visual Studio. Но ручной запуск происходит во время отладки (либо тестов, либо кода). У нас в команде почти на каждый баг заводится воспроизводящий тест. Далее баг вместе с тестом передается в разработку. И разработчикам проще смоделировать проблемную ситуацию и исправить ее.
Теперь о процессе регресса.В нашем проекте есть специальный билд, который инициирует полный прогон тестов, он запускается по триггеру как Rolling Build. Т.е., если прошел успешно быстрый Gated Checkin билд, то запускается Rolling Build. Особенность данного билда — в один момент времени собирается максимум один билд.
Начало данного билда такое же, как и у предыдущего — сбор последних версий тестового и основного проекта, и далее следует прогон модульных тестов.
Если все прошло успешно, то запускаются специально написанные Power Shell скрипты, которые деплоят пакет собранных сервисов в Azure. Для управлением деплоем используется REST API Azure. Сам деплой осуществляется на промежуточное развертывание облачной службы. Если деплой прошел успешно и роль запускается без ошибок, то стартую скрипты проверки конфигурации. Если все хорошо, то происходит переключение ролей (промежуточное развертывание становится основным) и запускаются сами интеграционные тесты.После прогона тестов на всю команду рассылается письмо, которое содержит отчет о статусе выполнения тестов.
Прежде чем писать тесты, классические модульные, так и интеграционные я рекомендую ознакомиться с книгами The Art of Unit Testing и xUnit Test Patterns: Refactoring Test CodeЭти книги (лично я люблю первую), содержат большое число советов и приемов, как надо и не надо писать тесты на любом xUnit фреймворке. Я бы хотел закончить свою статью перечислением основных принципов написания кода авто-тестов в нашей команде:
«Понятное» CLR имя тестового метода. Здесь основная идея заключается в том, что посмотрев на одно название теста, программист может понять, что он проверят, с какими данными, и что ожидается. Имя теста должно состоять из 3х частей, разделенных нижним подчеркиванием, и выглядеть примерно так: FunctionalUnderTest_Conditions_ExpectedResult Строгая структура теста — Arrange-Act-Assert. Подготовка данных, некое действие и проверка. Смысл такой, что тесты должны иметь максимально простую и компактную структуру, по сути состоящую из 3х описанных ранее шагов. Благодаря этому, достигается хорошая читабельность, а также простота поддержки и отладки тестов. Тесты не должны содержать условной логики и циклов. Здесь без комментариев, если бы у нас были такие тесты, то пришлось бы писать тесты на тесты. Один тест — одна проверка. Часто люди вставляют в свои тесты кучу Assert`s, а потом не могут понять, на каком именно из них тест упал. Старайтесь делать в тесте одну проверку, это очень упрощает отладку и поиск проблем. Здесь немного добавлю, что в некоторых случаях, после одного действия, требуется проверить состояние нескольких объектов, причем состояние каждого из них характеризируется множеством свойств. В таком случае целесообразно сделать два теста, а проверки всех свойств вынести в отдельные методы хелперы с нормальным именем, так чтобы вместо Assert.IsTrue вызывать ваш переопределенный ассерт. Еще, во время написания проверок на свойства, старайтесь писать читабельные сообщения ошибки. Здесь я просто привел пример. На самом деле существует масса различных шаблонов. Тесты, даже интеграционные, должны быть быстрыми. Старайтесь писать их с учетом этого. Я видел множество примеров тестов вот с такими вставками: Thread.Sleep (60000). Избегайте этого. Если вы ожидаете выполнения какого-то асинхронного действия, то прикиньте, как можно отследить его выполнение (например, асинхронный процесс меняет состояние в базе и тп). Или может стоит разбить тест на несколько? Возможно вы это пока не делаете, но соблюдайте потокобезопасность в тестах. Т.к. в дальнейшем вы скорее всего придете к тому, что захотите ускорить выполнение тестов и чуть ли не одним из способов достижения данного ускорения окажется многопоточность. Независимость. Тесты не должны зависеть от порядка их выполнения, да и друг от друга. Пишите тесты с учетом этого принципа. Старайтесь делать очистку тестовых данных, так как часто они приводят к различным непредвиденным последствия и замедляют выполнение тестов. Ну и напоследок хочу добавить, что правильные тесты действительно делают разработку быстрой, а код качественным.