Статьи

MagicTest — автоматизированный визуальный подход к тестированию

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

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

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

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

Новый подход предлагает следующие преимущества:

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

  • Ведение тестов проще, потому что вся необходимая информация видна с первого взгляда, и изменения могут быть легко внесены без необходимости изменения кода Java

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

Вы думаете, это звучит хорошо, чтобы быть правдой? Давайте посмотрим на первый пример.

Первый пример

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

@Test
public static void concat() {
StaticMethod.concat("s1", "s2");
StaticMethod.concat(null, "s2");
StaticMethod.concat("s1", null);
}

Если мы запустим этот тест с использованием MagicTest, мы получим следующий вывод:

Это выглядит многообещающе, но как это работает?

Визуальный подход

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

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

Если мы хотим протестировать наш метод конкатенации строк с традиционным подходом, мы получим в итоге такой код:

assertEquals(StaticMethod.concat("s1", "s2"), "s1s2");

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

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

Таким образом, расширенное определение следующее:

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

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

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

Фактический и справочный вывод

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

Но как появляется фактический и эталонный результат? Давайте посмотрим на типичный жизненный цикл:

  • Новый тест запускается в первый раз. Поскольку теперь есть эталонный вывод, сравнение вывода и, следовательно, теста не пройдено.

  • Разработчик теперь изучит собранный фактический результат.

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

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

  • Если тест будет запущен снова, сравнение результатов будет успешным, и тест считается успешным.

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

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

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

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

Реализация

Теперь должно быть понятно, как MagicTests работает концептуально. Но все еще может быть неясно, как простой вызов concat («s1», «s2») в нашей тестовой программе создает необходимые выходные данные для сравнения.

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

Если посмотреть на уровень байтового кода, вызов тестируемого метода будет выглядеть примерно так:

пытаться {

   printParameters("s1, s2");
String result = StaticMethod.concat("s1", "s2");
printResult(result);
} catch (Throwable t) {
printError(t);
}

Данные, отслеживаемые методами печати, затем собираются и визуализируются в виде отчета HTML.

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

Условия ошибки тестирования

Псевдобайт-код показал, что каждый вызов тестируемого метода окружен блоком try-catch. Это делает условия ошибок тестирования быстрым.

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

@Test
public static void concatErr1() {
try {
StaticMethod.concat(null, "s2");
fail("should fail");
}
catch (IllegalArgumentException e) {
// expected result
}
}

@Test(expectedExceptions = { IllegalArgumentException.class } )
public static void concatErr2() {
StaticMethod.concat(null, "s2");
}

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

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

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

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

Создание тестов

Услышав о фактических и эталонных выходных данных, становится ясно, что отчет о тестировании, показанный в первом примере, поступает из прогона с уже сохраненными эталонными выходными данными.

Если мы запустим этот тест в первый раз, отчет будет выглядеть так:

 

Как видите, отчет показывает фактический («[act]») рядом с ожидаемым эталонным («[ref]») выводом. Поскольку у нас еще нет справочного вывода, эти строки остаются пустыми, и тест не пройден.

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

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

Ведение тестов

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

Давайте предположим, что у нас есть функция для создания путей к файлам. Поскольку есть много разных случаев для рассмотрения, у нас есть довольно много тестов. Затем было решено, что мы должны использовать файловые URI вместо локальных путей. Таким образом, каждый возвращаемый путь к файлу теперь должен иметь «file: //» в начале.

При традиционном подходе мы должны теперь включить это изменение в каждый вызов теста.

При визуальном подходе отчет о тестировании покажет, что все методы тестирования были неудачными, но он также покажет, что сбой произошел только из-за отсутствующего «file: //». Таким образом, мы можем адаптировать все методы тестирования одним щелчком мыши по ссылке сохранения — без необходимости изменять исходный код теста.

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

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

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

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