• [x] Purpose and types (Coverage, principles, unit/integration/functional/ testing)

  • [x] UT frameworks (MSTest, Nunit, xUnit)

  • [x] Mock/Stub conception (mocks/stubs, dummy, fake object)

  • [x] Mock/Stub frameworks (Moq, Nmock, Fakes)

  • [x] TDD

  • [x] Data driven tests

Как использовать

Purpose and types

Зачем это нужно

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

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

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

Дополнительные цели:

  • как документация. (информация, записанная в виде тестов, может быть полезна при ознакомлении с устройством программы);

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

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

Coverage

Степень покрытия кода тестами бывает разной.

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

  • Хорошие тесты редко приходится править.
  • Чем дольше выполняются тесты, тем реже их запускают, хорошие тесты должны работать быстро.
  • Независимые тесты будут выполняться быстрее и их можно запускать в произвольном порядке (позволяет найти специфические ошибки).

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

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

Модульное / юнит-тестирование — процесс, позволяющий проверить на корректность отдельные модули исходного кода программы.

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

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

Принцип: написание тестов для каждой нетривиальной функции / метода.

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

Отличие от других видов тестирования:

Тестируется не вся система в целом, а небольшие ее части: тестирование с высокой гранулярностью.

Функциональное тестирование рассматривает заранее указанное поведение и основывается на анализе спецификаций функциональности компонента или системы в целом. Проверка соответствия продукта функциональным требованиям и спецификациям.

Для проверки функциональности (functionality) ПО необходимо испытать приложенние на выполнение функциональных требований к нему (сценариев использования и др.). Для этого используются функциональные тесты, а также тесты безопасности / объема и другие.

Интеграционное тестирование

Проверяется взаимодействие между компонентами системы / модулями сцелью определения корректности их совместного функционирования.

Цель: Выявление потенциальных проблем в совместном функционировании компонент

Сравнение Unit vs functional testing

UT frameworks (MSTest, Nunit, xUnit)

MSTest

Тестовый фреймворк по умолчанию, устанавливается одновременно со студией.
Это довольной простой в освоении с малым количеством функциональности.
Подойдет для простых в реализации тестов.

В последнее время популярность MSTest сильно упала: отсутствие активности в поддержке и усовершенствованию от Microsoft.

Nunit

Лидер среди аналогов с большим отрывом.

Ставить необходимо Nuget — пакетом.

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

xUnit

Причина создания — переосмысление unit — тестирования используя предыдущий опыт.
Отличие: Первым делом стоит отметить отсутствия setup (заменён параметризированным конструктором) и teardown (вместо него можно использовать IDisposable интерфейс) методов. Вместо [Test] используется [Fact]. В дополнение к этом разработчики убрали [ExpectedException] атрибут, а проверка на эксепшен теперь происходит в ассерте.

Сравнение

C Visual Studio лучше всех работает MSTest, для остальных необходимо устанавливать адаптеры, ReSharper или отдельные приложения для запуска.

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

В плане интеграции с платформами: xUnit.net начинает всех опережать, работа с ним поддерживается во многих видах современных приложений: UWP, Xamarin, .NET Core.

В плане гибкости и возможности написания тестов, интеграцию с CI и скорость: MSTest всем уступает. xUnit.Net кроме конфигурации с помощью XML поддерживает JSON.

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

Mock/Stub conception

Проверять работоспособность тестируемого объекта (system uder test - SUT) можно двумя способами: оценивая состояние объекта или его поведение.

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

Во-втором, мы проверяем набор и порядок действий (вызовов методов взаимодействующих объектов, других методов SUT), которое должен совершить метод SUT.

В одном случае используется Stub, а в другом Mock - объекты, которые создаются и используются взамен реальных объектов, с которым взаимодействует SUT в процессе своей работы.

Используется термин Test Double(дублер), как обозначение для объекта, который заменяет реальный объект, от которого зависит SUT, в тестовых целях.

Когда использовать:

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

Определенны следующие типы дублеров:

  • Dummy - это объекты, которые передаются в методы, но на самом деле не используются. В основном, это параметры методов (если конечно, они не влияют в тесте на то, что мы хотим проверить). Например, new object(), null, «Ignored String». (аля, throw not implemented, не используется, но просто для простройки приложения, индикатор проблемы при тестировании, например, в случае TDD)
  • Fake- это объекты, которые имеют внутреннюю реализацию, но обычно она сильно урезанная и их нельзя использовать в готовом коде. Используется в основном чтобы запускать (незапускаемые) тесты (быстрее) и ускорения их работы. Аналог замены тяжеловесного внешнего зависимого объекта его легковесной реализацией (например, фальшивый вебсервис).
  • Stubs - обеспечивают жестко зашитый ответ на вызовы во время тестирования. Применяются для замены тех объектов, которые обеспечивают SUT входными данными.Также могут сохранять в себе информацию о вызове (например параметры или количество этих вызовов). Такая "запись" позволяет оценить работу SUT, если состояние самого SUT не меняется. (Что бы мы не передавали внутрь такого объекта, он всегда нам возвращает одно и тоже значение. Типа заглушки на сервере например. Это значение в реальных условиях может нам вернуться (смоделированный реальный кейс))

  • Mocks - объекты, которые настраиваются (например, специфично каждому тесту) и позволяют задать ожидания в виде своего рода спецификации вызовов, которые планируется получить. Не записывает последовательность вызовов с переданными параметрами для последующей проверки, а может сам выкидывать исключения при некорректно переданных данных. (Используя fake тест свалиться во время ответа от SUT, в случае мока мы будем контролировать что пришло и в фейковый мок интерфейс системы (Test Dubler)), внутренняя система и ее поведение)

Отличия (Моки и Стабы): Только моки работают на верификацию поведения. Остальные, как правило, используются для проверки состояния. Моки требуют настроек перед запуском и позволяют оценить процесс выполнения. (воспринимать можно как test smell - тестировании too deep)

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

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

Наглядное представление:

Вставь сюда картинку от Сереги.

Комментарий: В классическом варианте модульного тестирования реальные объекты используются везде, где это возможно (за исключением случаев описанных выше). В мокирующем (mockist) варианте - мокируется все, кроме того объекта, который мы тестируем (SUT).

Недостатки:

  • При использовании классического подхода с использованием реальных объектов, в случае ошибки в одном из методов, "красными" станут все тесты, в которых этот метод используется (независимо от того, тестируется он в них или просто используется другим объектом). Локализация проблемы может стать трудной задачей, если при написании тестов, изначально не задумывалась гранулярность теста.
  • При мокировании поведенческие тесты жестко фиксируют внутреннюю реализацию SUT. Что и как вызывается, с какими аргументами, какое API используется и тп. Все это создает трудности при рефакторинге и возможных изменениях в SUT - вместе с кодом все тесты придется писать заново.

Mock/Stub frameworks (Moq, Nmock, Fakes)

TDD

Тест— это процедура, которая позволяет либо подтвердить, либо опровергнуть работоспособность кода.

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

При первоначальном запуске, тесты не скомпилируются, задача — написать код, что бы тесты позеленели.

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

Преимущества:

  • TDD не бывает без тестов, а значит она предостерегает нас от написания жестко сцепленного кода
  • Так как нет участков кода, не покрытых тестами, все поведение написанного кода будет документировано
  • Возможность лучше разобраться в коде
  • oтесты позволяют убедиться, что разработчик правильно понимает, как работает код;
  • oбудут служить документацией для тех, кто будет читать код после вас;
  • При планировании рефакторинга, тесты помогут убедиться в корректности изменений
  • Необходимость использовать отладчик становится реже
  • Детали интерфейса имплементируются до реализации
  • Код становится более приспособленным для тестирования
  • Общее время, затраченное на разработку, сокращается: тесты защищают от ошибок. Поэтому время, затрачиваемое на отладку, снижается многократно. Сокращается вероятность появления хронических и дорогостоящих ошибок
  • Тесты позволяют производить рефакторинг кода без риска его испортить

Требования

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

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

  • Добавление теста
  • Запуск всех тестов: убедиться, что новые тесты не проходят
  • Написать код
  • Запуск всех тестов: убедиться, что все тесты проходят
  • Рефакторинг
  • Повторить цикл

Стиль разработки

Разработка через тестирование тесно связана с такими принципами как keep it simple, stupid, KISS и you ain’t gonna need it, YAGNI

Слабые места

  • Существуют задачи, которые невозможно решить только при помощи тестов. TDD не позволяет механически продемонстрировать адекватность разработанного кода в области безопасности данных и взаимодействия между процессами.
  • Разработку через тестирование сложно применять в тех случаях, когда для тестирования необходимо прохождение функциональных тестов. (например, разработка интерфейсов пользователя, программ, работающих с базами данных, а также того, что зависит от специфической конфигурации сети)
  • Требуется больше времени на разработку и поддержку, как следствие необходимость одобрения со стороны руководства
  • Большое количество используемых тестов могут создать ложное ощущение надежности, приводящее к меньшему количеству действий по контролю качества

Data driven tests

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

Схема:

Преимущества

  • Возможность ввода входных значений и эталонного результата в виде,удобном для всех ролей на проекте - начиная от мануального тестировщика и заканчивая менеджером проекта, даже product owner-а
  • Загрузка мануальных тестировщиков увеличением покрытия и увеличением наборов данных - удешевляет тестирование
  • Документация тестирования
  • Руководителю проекта становится легче контролировать разработку, ему не приходиться вникать в суть алгоритмов, и даже качества сделанного кода

Когда нужно DDT

  • Если какой-то компонент системы можно описать с помощью входных параметров и выходного состояния или результата.

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

  • Используется свой DSL (domain specific language)

  • Покрываемая система расширяема - плагины, скрипты

  • В базе данных происходят сложные калькуляции или присутствуют сложные связи

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


Верификация (verification)— это процесс оценки системы / её компонентов с целью определения удовлетворяют ли результаты текущего этапа разработки условиям, сформированным в начале этого этапа. Т.е. выполняются ли наши цели, сроки, задачи по разработке проекта, определенные в начале текущей фазы.

Verification — ’is the system correct to specification?’.

Валидация (validation)— это определение соответствия разрабатываемого ПО ожиданиям и потребностям пользователя, требованиям к системе.
Также можно встретить иную интерпритацию:
Процесс оценки соответствия продукта явным требованиям (спецификациям) и есть верификация (verification), в то же время оценка соответствия продукта ожиданиям и требованиям пользователей — есть валидация (validation).

Validation — ’is this the right specification?’.

results matching ""

    No results matching ""