Найти тему

Unit тестирование ч.2

В продолжении разговора о unit-тестах мы будем говорить об усложнении тестирования нашего калькулятора и о многом-многом другом.

Начнем!

public class TestCalculator {

private int nbErrors = 0;

public void testAdd() {

Calculator calculator = new Calculator();

double result = calculator.add(10, 50);

if (result != 60) {

throw new RuntimeException("Bad result: " + result);

}

}

public static void main(String[] args) {

TestCalculator test = new TestCalculator();

try {

test.testAdd();

}

catch (Throwable e) {

test.nbErrors++;

e.printStackTrace();

}

if (test.nbErrors > 0) {

throw new RuntimeException("There were " + test.nbErrors + " error(s)");

}

}

}

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

■ Каждый модульный тест должен выполняться независимо от всех других модульных тестов.

■ Ошибки должны быть обнаружены и сообщены тест за тестом.

■ Должно быть легко определить, какие модульные тесты будут выполняться.

Тестовая программа “немного лучше” приближается к соблюдению этих правил, но все равно не

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

Добавление класса также лишь немного лучше. Теперь вы можете добавить новые модульные тесты, добавив новый метод, а затем добавив соответствующий блок try/catch в main.

Определенный шаг вперед, но все еще не дотягивает до того, что вы хотели бы получить в реальном наборе модульных тестов. Самая очевидная проблема заключается в том, что большие блоки try/catch, как известно, являются кошмарами обслуживания. Вы можете легко оставить модульный тест и никогда не узнать, что он не работает!

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

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

JUnit имеет множество функций, которые облегчают написание и запуск тестов.

■ Альтернативные интерфейсы или тестовые раннеры для отображения результатов ваших тестов.

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

■ Стандартные методы инициализации и восстановления ресурсов (настройка и демонтаж).

■ Различные методы assert, позволяющие легко проверить результаты ваших тестов.

■ Интеграция с популярными инструментами, такими как Ant и Maven, и популярными IDE, такими как Eclipse, IntelliJ и JBuilder.

Давайте посмотрим как выглядит простой тест калькулятора, написанный с помощью JUnit.

import junit.framework.TestCase;

public class TestCalculator extends TestCase {

public void testAdd() {

Calculator calculator = new Calculator();

double result = calculator.add(10, 50);

assertEquals(60, result, 0);

}

}

Вы начинаете с расширения тестового класса из стандартного JUnit junit.framework.TestCase. Этот базовый класс включает в себя код фреймворка, который JUnit должен автоматически запускать тесты.

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

Дальше вы запускаете тест, создавая экземпляр класса Calculator,

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

Чтобы проверить результат теста, вы вызываете метод assertEquals, унаследованный от базового тестового набора.

Javadoc для метода assertEquals:

/** * Asserts that two doubles are equal concerning a delta.

If the * expected value is infinity then the delta value is ignored.

*/

static public void assertEquals(double expected, double actual, double delta)

Вы передали assertEquals эти параметры:

■ ожидаемое = 60

■ фактический = результат

■ дельта = 0

Поскольку вы передали калькулятору значения 10 и 50, вы говорите assertEquals ожидать, что сумма будет равна 60. (Вы передаете 0 при добавлении целых чисел, поэтому дельты нет.) Когда вы вызывали объект калькулятора, вы вставляли возвращаемое значение в локальный результат с типом double. Таким образом, вы передаете эту переменную в assertEquals для сравнения с ожидаемым значением 60.

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

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

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

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

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

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