К какому типу по доступу к коду и архитектуре приложения относится unit тестирование
Что такое тестирование “чёрного ящика” — это стратегия или метод тестирования, базируется только на тестировании по функциональной спецификации и требованиям, при этом не смотря во внутреннюю структуру кода и без доступа к базе данных. Фактически мы знаем какой должен быть результат при определённом наборе данных, которые подаются на вход. Результат проверяем с интерфейса на уровне простого пользователя. На данный момент такая стратегия является наиболее часто применима в IT-компаниях.
Чуть меньшее количество тестировщиков используют стратегиею “серого ящика”, который подразумевает частичный доступ, например, к структуре баз данных.
Наименьшая часть тестировщиков, способны анализировать чужой код и заниматься написанием тестов даже не запуская программу или приложение, а только используя программный код, эта стратегия называется “белым ящиком”. Может использоваться в дополнение к чёрному и серому ящикам. Таких специалистов очень мало, и они, скорее всего, бывшие разработчики, ушедшие в тестирование или тестировщики, занимающиеся автоматизацией и увлекающиеся программированием.
Юнит-тестирование, проводимое, как правило, разработчиками продукта, является примером whitebox тестирования.
По степени автоматизации
Ручное тестирование – это процесс поиска дефектов в работе программы, когда тестировщик проверяет работоспособность всех компонентов программы, как если бы он был пользователем. Для проведения тестирования тестировщики используют заранее заготовленные планы тестирования и тесты на основе требований к ПО.
При автоматизированном тестировании используются программные средства для выполнения тестов и проверки результатов. Применение автоматизированных тестов позволяет сократить время тестирования и упростить сам процесс. Однако не стоит думать, что автоматизация это просто и нужно все тесты сделать автоматизированными.
Автоматизированное тестирование бывает на разном уровне, часто разработчики автоматизируют тесты для проверки своих модулей или связи между модулями. Широкую популярность набрали автоматизированные тесты пользовательского интерфейса, которые эмулируют поведение пользователя, т.е. Передвигают указатель мышки по экрану, нажимают на кнопки, пишут тексты и.т.д.
При ручном и автоматизированном тестировании нужно составлять тесты и планы тестирования. Тесты со временем устаревают и их нужно актуализировать и поддерживать. Тесты для ручного тестирования оформляются в виде простого текста, а автоматизированные в виде программного кода. Если разработчики немного изменят интерфейс пользователя, то при ручном тестировании это изменение не будет критичным и тест, выполняемый человеком пройдёт успешно, а автоматизированный тест сломается, т.к. они очень чувствительны к различным изменениям.
Не стоит бросаться автоматизировать любой проект, но и исключать пользу автоматизированного тестирования нельзя.
По уровню детализации приложения
Модульное тестирование проверяет функциональность и ищет дефекты в частях приложения, которые доступны и могут быть протестированы по отдельности (модули программ, объекты, классы, функции и т.д.). Обычно модульное тестирование выполняют разработчики, разрабатывая и программируя специальные модульные тесты (юнит-тесты) в процессе разработки модуля программы.
Интеграционное тестирование предназначено для проверки связи между модулями ПО, а также взаимодействия с различными частями системы (операционной системой, оборудованием, взаимосвязи между различными системами).
Основной задачей системного тестирования является проверка как функциональных, так и нефункциональных требований в системе в целом. При этом выявляются дефекты, такие как неверное использование ресурсов системы, непредусмотренные комбинации данных пользовательского уровня, несовместимость с окружением, непредусмотренные сценарии использования, отсутствующая или неверная функциональность, неудобство использования и т.д. При проведении системного тестирования стараются создать специальные условия, приближенные к реальным или тем, которые будут у заказчика ПО.
В командной разработке тесты – это, как правило, задача QA- и SDET-специалистов. Вместе с тем навыки тестирования полезны и разработчикам, позволяя им проверить свои приложения и повысить стабильность их работы.
Эта статья предназначена в первую очередь начинающим мобильным разработчикам, которые хотят изучить процессы тестирования и свое участие в них.
На примере Android-разработки обсудим подходящие инструменты тестирования – от JUnit до Kaspresso, а также немного познакомимся с методологиями Test Driven Development (TDD) и Behaviour Driven Development (BDD). Наконец, рассмотрим их отличия на примере кейса.
Тестирование IT-системы охватывает множество проверок архитектуры, UI, кода и взаимодействия его частей, соответствия требованиям. По мере усложнения систем в отрасли растут потребности как в обеспечении качества (QA), так и в автоматизации тестирования (SDET), которая позволяет проводить некоторые тесты быстро и с минимальным участием людей.
Уровни тестирования
С появлением тестов для различных уровней программы возникла их абстрактная иерархия – Пирамида автотестов, в которую входят:
Модульные тесты. Проверяют взаимодействие кода внутри одного или нескольких классов со связанной функциональностью. Unit-тесты создают до, во время или после написания проверяемого кода, их должно быть много, они должны запускаться часто, работать быстро, быть легко поддерживаемыми.
Интеграционные тесты. Проверяют логику взаимодействия различных компонентов, подсистем без использования UI. В контексте Android сюда входят тесты БД (миграции, выборки, CRUD операции), тесты api-сервисов с моковыми данными и т.д.
UI-тесты. В контексте Android это полноценное автоматизированное тестирование экрана или набора экранов, проверка корректной работы пользовательского интерфейса. Вся логика при этом должна быть протестирована на нижних уровнях.
При выборе необходимых проверок, помимо пирамиды, можно использовать колесо автоматизации – подробнее об этом читайте здесь.
Рассмотрим инструменты, используемые на каждом из вышеупомянутых уровней.
Unit-тесты
Unit-тесты – это самый простой инструмент для вовлечения разработчика в процесс тестирования приложения. Они фокусируются на конкретном классе или участке кода и пишутся непосредственно разработчиками. Unit-тесты должны выполняться быстро и иметь однозначные результаты: правильно написанные тесты – отличный способ немедленной проверки произведенных изменений в коде. Также функционал Android Studio позволяет выполнять не весь набор тестов, а только те, которые необходимы разработчику для проверки. Помимо этого, unit-тесты – один из вариантов документации кода для разработчиков, они помогают увидеть, какие возможные результаты имеет метод и какие граничные случаи он обрабатывает.
Инструменты для модульного тестирования
Unit-тесты для Android по умолчанию располагаются в папке src/test проекта или модуля, запускаются с использованием фреймворка JUnit. В идеале, один тест должен тестировать открытый интерфейс одного класса и проверять все ветвления кода и граничные случаи в нем. Зависимости должны иметь поведение, необходимое для проверки тестируемого класса.
В современных Android приложениях для unit-тестов, в основном, используются следующие библиотеки:
JUnit. Для запуска тестов, вызова assertion’ов.
Mockk. Позволяет мокать final классы Котлина, suspend функции, имеет удобный DSL для работы.
kotlinx-coroutines-test. Тестирование suspend-функций внутри TestCoroutineScope, предоставляемого функцией runBlockingTest, подмены main dispatcher’а в рамках тестов.
turbine. Небольшая, но удобная библиотека для тестирования kotlinx.coroutines.Flow.
robolectric. Позволяет писать unit-тесты для классов, использующих Android SDK без непосредственного запуска устройства – фреймворк умеет симулировать различные части системы.
Инструменты для интеграционного тестирования
Эти тесты для Android по умолчанию располагаются в папке src/androidTest проекта или модуля и запускаются уже на устройстве, так как должны иметь доступ, например, к контексту приложения для создания БД Room. Для запуска тестов используется уже упомянутый фреймворк JUnit.
Инструменты для тестирования пользовательского интерфейса
Espresso. Официальный фреймворк для UI-тестирования от Android. Имеет множество примеров и хорошую документацию. При этом не может взаимодействовать с другими приложениями, достаточно плохо работает с асинхронными интерфейсами и списками.
Kaspresso. Обертка над Espresso и UI Automator, которая позволяет писать стабильные, быстрые, удобочитаемые тесты.
Для тестирования интерфейсов, реализованных с помощью Jetpack Compose, также появляются свои библиотеки, например, эта.
Также на Хабре можно прочитать больше об инструментах для UI-тестирования, например, в этой статье.
TDD и BDD
TDD и BDD – две популярные методики разработки через тестирование. Рассмотрим их отличия на примере следующего кейса:
Пользователь вводит сумму расхода, комментарий к расходу и выбирает категорию расхода.
Если сумма некорректна или не выбрана категория расхода, возвращается код ошибки, иначе – код успешной обработки.
TDD (Test Driven Development) – это методология разработки ПО, основанная на следующих коротких циклах:
Написать тест, проверяющий желаемое поведение.
Запустить тест. Test failed.
Написать программный код, реализующий требуемое поведение.
Запустить тест. Test succeeded.
Провести рефакторинг написанного программного кода, сопровождая прогонами теста.
Для начала создадим контракт нашей реализации.
Теперь напишем тест, проверяющий, что написанный код реализует указанные требования.
Запускаем тест, получаем ожидаемый результат:
Теперь напишем реализацию
Запустим тесты: все 4 теста проходят. Теперь настало время рефакторинга написанного кода.
Снова запускаем тесты, чтобы удостовериться, что наш рефакторинг ничего не сломал – и видим, что тесты проходят успешно.
Методология TDD имеет следующие преимущества:
Написанный код имеет более правильный и понятный дизайн, написан чище, так как должен запускаться из теста и быть идемпотентным.
Позволяет провести рефакторинг с меньшей вероятностью возникновения ошибок, поскольку есть способ сразу же проверить правильность написанного кода.
Позволяет локализовать ошибки быстрее.
В числе минусов можно выделить следующие:
Фокусировка на реализации задачи.
Код и описание тестов пишутся на одном языке.
В процесс вовлечена только команда разработки.
Подробнее про данную методологию можно прочитать в книге Кента Бека Экстремальное программирование. Разработка через тестирование.
BDD (Behaviour driven development) – методология разработки ПО, во многом схожая с TDD. Отличается тем, что тестовые сценарии пишутся на “человеческом” языке, а не на языке программирования.
Тестовые сценарии записываются в формате given-when-then. Например, given (имея) подключение к сети, when (когда) пользователь открывает ссылку, then (тогда) контент страницы отображается.
Перепишем наши требования с использованием BDD:
Сценарий: добавление траты.
Given Корректную сумму
And Введенный комментарий
And Выбранную категорию траты
When Пользователь нажимает кнопку добавления
Then Пользователь получает успешный результат
Для данного подхода существуют свои фреймворки. Например, для Java это фреймворк JBehave.
К особенностям данного подхода можно отнести следующее:
Тестовые сценарии на “человеческом языке” может писать как заказчик,так и аналитик, тестировщик. Это повышает уровень знаний всей команды о разрабатываемой системе.
Тестовые сценарии легко изменяются.
Результаты тестов также более понятны заинтересованным лицам, по сравнению с результатами выполнения кода.
Узнать подробнее о BDD можно в этой статье.
Заключение
Мало у кого возникают сомнения, что тесты необходимы для проектирования качественного ПО. Существует множество фреймворков, инструментов и методологий (DDD, FDD и другие *DD), которые помогают команде на всех этапах жизненного цикла ПО. Тесты помогают быстро найти и локализовать ошибки, а также, если они правильно спроектированы, могут служить тестовой документацией. Также благодаря тестам код реализации, скорее всего, будет написан чище и понятнее. В то же время главное – не 100% покрытие кода тестами, а его соответствие бизнес-задачам, поэтому важно избегать крайностей и не писать тесты ради тестов.
Спасибо за внимание! Надеемся, что этот материал был вам полезен.
Системное тестирование (system testing) — тест высокого уровня для проверки работы большего куска приложения или системы в целом.
Регрессионное тестирование (regression testing) — тестирование, которое используется для проверки того, не влияют ли новые фичи или исправленные баги на существующий функционал приложения и не появляются ли старые баги.
Функциональное тестирование (functional testing) — проверка соответствия части приложения требованиям, заявленным в спецификациях, юзерсторях и т. д.
Виды функционального тестирования:
- тест «белого ящика» (white box) на соответствие части приложения требованиям со знанием внутренней реализации системы;
- тест «черного ящика» (black box) на соответствие части приложения требованиям без знания внутренней реализации системы.
Unit — модульные тесты, применяемые в различных слоях приложения, тестирующие наименьшую делимую логику приложения: например, класс, но чаще всего — метод. Эти тесты обычно стараются по максимуму изолировать от внешней логики, то есть создать иллюзию того, что остальная часть приложения работает в стандартном режиме.
Данных тестов всегда должно быть много (больше, чем остальных видов), так как они тестируют маленькие кусочки и весьма легковесные, не кушающие много ресурсов (под ресурсами я имею виду оперативную память и время).
Integration — интеграционное тестирование. Оно проверяет более крупные кусочки системы, то есть это либо объединение нескольких кусочков логики (несколько методов или классов), либо корректность работы с внешним компонентом. Этих тестов как правило меньше, чем Unit, так как они тяжеловеснее.
UI — тесты, которые проверяют работу пользовательского интерфейса. Они затрагивают логику на всех уровнях приложения, из-за чего их еще называют сквозными. Их как правило в разы меньше, так они наиболее тяжеловесны и должны проверять самые необходимые (используемые) пути.
На рисунке выше мы видим соотношение площадей разных частей треугольника: примерно такая же пропорция сохраняется в количестве этих тестов в реальной работе.
Сегодня подробно рассмотрим самые используемые тесты — юнит-тесты, так как уметь ими пользоваться на базовом уровне должны все уважающие себя Java-разработчики.
Ключевые понятия юнит-тестирования
- материал о Code Coverage на JavaRush и на Хабре; .
- Пишем наш тест.
- Запускаем тест, прошел он или нет (видим, что всё красное — не психуем: так и должно быть).
- Добавляем код, который должен удовлетворить данный тест (запускаем тест).
- Выполняем рефакторинг кода.
Этапы тестирования
- Задание тестируемых данных (фикстур).
- Использование тестируемого кода (вызов тестируемого метода).
- Проверка результатов и сверка с ожидаемыми.
Среды тестирования
- assertEquals(Object expecteds, Object actuals) — проверяет, равны ли передаваемые обьекты.
- assertTrue(boolean flag) — проверяет, возвращает ли переданное значение — true.
- assertFalse(boolean flag) — проверяет, возвращает ли переданное значение — false.
- assertNull(Object object) – проверяет, является ли объект нулевым (null).
- assertSame(Object firstObject, Object secondObject) — проверяет, ссылаются ли передаваемые значения на один и тот же обьект.
- assertThat(T t, Matcher<T> matcher) — проверяет, удовлетворяет ли t условию, указанному в matcher.
Практика тестирования
А теперь давайте рассмотрим приведенный выше материал на конкретном примере. Будем тестировать метод для сервиса — update. Рассматривать слой дао не будем, так как он у нас дефолтный. Добавим стартер для тестов: Итак, класс сервиса: 8 — вытягиваем обновляемый обьект из БД 9-14 — создаём объект через билдер, если в приходящем объекте есть поле — задаем его, если нет — оставляем то, что есть в БД И смотрим наш тест: 1 — наш Runner 4 — изолируем сервис от слоя дао, подставляя мок 11 — задаем для класса тестовую сущность (ту, которую мы будем юзать в качестве испытуемого хомячка) 22 — задаём объект сервиса, который мы и будем тестить Здесь мы видим четкое разделение теста на три части: 3-9 — задание фикстур 11 — выполнение тестируемой части 13-17 — проверка результатов Подробнее: 3-4 — задаём поведение для мока дао 5 — задаём экземпляр, который мы будем апдейтить поверх нашего стандартного 11 — используем метод и берём результирующий экземпляр 13 — проверяем, что он не ноль 14 — сверяем айди результата и заданные аргументы метода 15 — проверяем, обновилось ли имя 16 — смотрим результат по cpu 17 – так как в экземпляре для обновления мы не задавали это поле, оно должно остаться прежним, проверяем это. Запускаем: Тест зелёный, можно выдыхать)) Итак, подведём итоги: тестирование улучшает качество кода и делает процесс разработки более гибким и надёжный. Представьте себе, как много сил мы потратим при изменении дизайна программного обеспечения с сотнями файлов классов. Когда у нас есть модульные тесты, написанные для всех этих классов, мы можем уверенно провести рефакторинг. И самое главное — это помогает нам легко находить ошибки во время разработки. Гайз, на этом у меня сегодня всё: сыпем лайки, пишем комменты)))Почему оба подхода до сих пор существуют? Разве тестирование с доступом к коду не эффективнее? Может существует золотая середина? Давайте разбираться.
В терминологии тестирования фразы «тестирование белого ящика» и «тестирование чёрного ящика» относятся к тому, имеет ли тестировщик доступ к исходному коду тестируемого ПО или нет.
Черный ящик
Разработка тестов методом черного ящика (black box test design technique) — процедура создания и/или выбора тестовых сценариев, основанная на анализе функциональной или нефункциональной спецификации компонента или системы без знания внутренней структуры. (ISTQB)
Тестируемая программа для тестировщика – как черный непрозрачный ящик, содержания которого он не видит.
Основной посыл такого тестирования в том, что мы не знаем, как устроена тестируемая система. Имеется ввиду изнутри. При таком тестировании тестировщик очень похож на обычного пользователя: тест анализ и исследование продукта он проводит опираясь на ТЗ (техническое задание), спецификации и прочую документацию, которая описывает этот продукт.
Получается, что идеи для тестирования идут от предполагаемых паттернов (pattern — образец) поведения пользователей. Поэтому такой подход еще называют поведенческим.
Пример. Заходим в приложение вызова такси и видим возможность привязать карту для автоматической оплаты. Начинаем думать как пользователь:
— Что, если привязать заблокированную карту?
— А если забыть/неверно указать срок действия карты?
— Долларовая карта привяжется?
— А может можно после вызова такси и подачи машины быстро отвязать ее до списания оплаты… Что тогда будет? Спишутся средства? Или водителю придет уведомление, что оплата изменилась с безналичной на наличную?
По сути, мы тестируем и строим предположения на основе того, что видим и рисуем себе в голове. То есть мы только предполагаем, что элементы должны работать таким образом и на основании этого подбираем тесты, но точно не уверены, что это именно так.
Либо открываем спецификацию и смотрим, как система должна работать. Потом запускаем продукт и сверяем его с тем, что указано в спецификации.
Таким образом, мы не имеем представления о структуре и внутреннем устройстве системы. Нужно концентрироваться на том, что программа делает, а не на том, как она это делает.
- Тестировщик не обязан обладать (глубокими) знаниями в области программирования.
- Поведение приложения исследуется в контексте реальной среды выполнения и учитывает её влияние.
- Поведение приложения исследуется в контексте реальных пользовательских сценариев.
- Тест-кейсы можно создавать уже на стадии появления стабильных требований.
- Процесс создания тест-кейсов позволяет выявить дефекты в требованиях.
- Допускает создание тест-кейсов, которые можно многократно использовать на разных проектах.
- Возможно повторение части тест-кейсов, уже выполненных разработчиками.
- Высока вероятность того, что часть возможных вариантов поведения приложения останется непротестированной.
- Для разработки высокоэффективных тест-кейсов необходима качественная документация.
- Диагностика обнаруженных дефектов более сложна в сравнении с техниками метода белого ящика.
- В связи с широким выбором техник и подходов затрудняется планирование и оценка трудозатрат.
- В случае автоматизации могут потребоваться сложные дорогостоящие инструментальные средства.
Белый ящик
Разработка тестов методом белого ящика (white-box test design technique): Процедура разработки или выбора тестовых сценариев на основании анализа внутренней структуры компонента или системы. (ISTQB)
Белый ящик является полной противоположностью черному ящику. При тестировании черного ящика, нам необходимо запускать программу и смотреть, что она делает. А в белом этого не требуется. Достаточно смотреть на код программы.
Основной посыл этого типа тестирования — нам известны все детали реализации тестируемой программы.
Тестирование методом белого ящика (прозрачного, открытого, стеклянного ящика, основанное на коде или структурное тестирование) – метод тестирования программного обеспечения, который предполагает, что внутренняя структура/устройство/реализация системы известны тестировщику.
Мы выбираем входные значения, основываясь на знании кода, который будет их обрабатывать. Так же мы знаем, каким должен быть результат этой обработки. Знание всех особенностей тестируемой программы и ее реализации – обязательны для этой техники. Тестирование белого ящика – углубление во внутреннее устройство системы, за пределы ее внешних интерфейсов.
Профессиональные тестировщики, которые тестируют методами белого ящика, имеют большую экспертизу в программировании, так как должны уметь читать код и находить в нем проблемы.
- Показывает скрытые проблемы и упрощает их диагностику.
- Допускает достаточно простую автоматизацию тест-кейсов и их выполнение на самых ранних стадиях развития проекта.
- Обладает развитой системой метрик, сбор и анализ которых легко автоматизируется.
- Стимулирует разработчиков к написанию качественного кода.
- Многие техники этого метода являются проверенными, хорошо себя зарекомендовавшими решениями, базирующимися на строгом техническом подходе.
- Не может выполняться тестировщиками, не обладающими достаточными знаниями в области программирования.
- Тестирование сфокусировано на реализованной функциональности, что повышает вероятность пропуска нереализованных требований.
- Поведение приложения исследуется в отрыве от реальной среды выполнения и не учитывает её влияние.
- Поведение приложения исследуется в отрыве от реальных пользовательских сценариев.
Серый ящик. Отдельный вид или миф?
Его основной посыл в том, что нам известны только некоторые особенности реализации тестируемой системы.
То есть, внутреннее устройство программы нам известно лишь частично. Предполагается, например, доступ к внутренней структуре и алгоритмам работы ПО для написания максимально эффективных тест-кейсов, но само тестирование проводится с помощью техники черного ящика, то есть, с позиции пользователя.
Эту технику тестирования также называют методом полупрозрачного ящика: что-то мы видим, а что-то – нет.
Кто-то говорит, что этот вид тестирования — это симбиоз белого и черного ящика. Кто-то противопоставляет его белому и черному, опираясь на то, что внутренняя структура тестируемого объекта изначально известна частично и выясняется по мере исследования.
ISTQB относит тестирование методами белого и черного ящика к методам проектирования тестов. Поэтому, ни о каком «среднем» или «промежуточном» методе в этом случае конечно и речи быть не может. Мы либо разрабатываем тесты, зная код, либо не зная его. То есть в классификации ISTQB такого вида тестирования не существует.
Думаю, что на собеседовании это явно стоит упомянуть.
Почему все ящики эффективны?
Методы белого и черного ящика не являются конкурирующими или взаимоисключающими. Наоборот, они гармонично дополняют друг друга, компенсируя имеющиеся недостатки.
Параллельное использование черного и белого ящиков увеличивает покрытие возможных сценариев:
То есть у каждого из методов тестирования свои неоспоримые плюсы, которые помогают выпустить качественный продукт.
Читайте также: