Введение в JUnit

Часть вторая: продолжаем знакомство

В прошлом номере "Компьютерных вестей" мы с вами начали говорить о замечательном фреймворке для unit-тестирования Java-приложений, который называется JUnit. Сегодня мы этот разговор продолжим (а если повезёт, то даже и закончим).

Напомню, что ранее мы уже успели обсудить, как именно тестируются программы на разных этапах их разработки, а также в чём именно состоят особенности такого этапа, как модульное тестирование (оно же unit-тестирование). Ну а в прошлом номере познакомились с фреймворком JUnit, узнали, как подключать его к проектам, разрабатываемым в среде Eclipse, а также с основными функциями, применяемыми для сравнения экземпляров объектов с некоторыми эталонными значениями (поскольку, собственно говоря, именно в таком сравнении и заключается тестирование модулей программного кода). Сегодня пойдём дальше в нашем знакомстве с фреймворком JUnit и начнём с того, что рассмотрим пример теста, написанного с использованием тех функций, о которых говорили в прошлый раз. Думаю, что пример будет полезен, потому что одно дело - читать, как работает та или иная функция, и совсем другое - увидеть своими глазами программный код, в котором она применяется, что называется, "живьём". Вот этот код мы и рассмотрим для начала.


Наш первый тест

В первом листинге вы можете увидеть приведённый полностью текст файла, который занимается тестированием некоторого класса, в качестве которого для определённости выбран класс, реализующий функции калькулятора. Называться он будет Counter (от английского count - считать). Понятное дело, что в реальных приложениях подобные простые классы в реальном тестировании как бы и не особо нуждаются, зато они чрезвычайно удобны для того, чтобы наглядно продемонстрировать работу с тестами в JUnit.

import junit.framework.TestCase;
public class TestCounter extends TestCase {
 public void testAdd() {
  Counter counter = new Counter ();
  double sixty = counter.add(10, 50);
  assertEquals(60, sixty, 0);
 }
}

Итак, давайте разберём, что именно мы можем увидеть в нашем листинге. С первой строчкой всё понятно - с её помощью мы добавляем в наш файл возможность вызывать методы класса TestCase из состава JUnit, который служит основой для реализуемого теста. В следующей строчке объявляем сам класс для тестирования - он называется TestCounter и является потомком класса TestCase. Ещё одной строчкой ниже объявляем единственный метод нашего тестового класса, который должен, как легко догадаться по его названию, тестировать функцию сложения чисел. Далее создаём экземпляр тестируемого класса-калькулятора, который, напомню, для конспирации называется не Calculator, а Counter. Дальше вызываем тестируемую функцию, складывающую числа 10 и 50, и записываем результат их сложения в переменную, имеющую название sixty. И последним, так сказать, аккордом уже сравниваем значение этой переменной и числа 60 с помощью метода assertEquals, о котором я вам уже рассказывал.


Ещё немного об equals

В прошлый раз мы уже останавливались на различиях между методами assertEquals и assertSame. Сейчас же хочу рассказать о другом моменте, связанном с применением первого из этих двух методов.

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

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

public boolean equals(Object obj){
 if (obj instanceof DummyClass){
  DummyClass dummy = (DummyClass) obj;
  return (dummy.getField1().equals(getFiled1()) &&
   (dummy.getField2().equals(getField2()));
 }
 return false;
}

Итак, давайте, как обычно, пройдёмся последовательно по всем строчкам нашего второго по счёту листинга, чтобы не осталось вопросов о том, что именно в нём происходит и в каком порядке. В первой строчке можем увидеть объявление нашего с вами метода equals. Обратите внимание на то, что в качестве аргумента должен использоваться не объект того же класса, а вообще любого, для чего нужно указывать класс, являющийся базовым для всех объектов в Java. Дальше мы как раз и проверяем принадлежность переданного методу экземпляра объекта к нашему классу DummyClass. В случае, если экземпляр действительно принадлежит к данному классу, мы создаем для простоты и удобства переменную dummy типа DummyClass, которая будет ссылаться на переданный методу в качестве аргумента объект. Ну а в следующей строчке уже смотрим непосредственно на соответствие полей Field1 и Field2, получая их значения с помощью методов-"геттеров". Если значения этих полей у экземпляра объекта, для которого вызывается данный метод, и у того экземпляра, который передаётся ему в качестве параметра, совпадают, то возвращаемое методом значение будет истинным. В случае, если эти значения не совпадают, а также если переданный в аргументе объект вообще "не того поля ягода", то есть, относится не к тому классу, возвращаемое функцией значение будет, само собой, ложным, что очень легко можно увидеть по коду листинга.


"Прогон" тестов: первоначальная подготовка

Итак, будем считать, что тесты у нас написаны и теперь нам их нужно запустить. Причём желательно все сразу и таким образом, чтобы можно было сказать, какой из них выполнен успешно, а какой, как говорится, "профейлился". Что ж, могу вас обрадовать: вручную запускать последовательно каждый тест вам не придётся, потому что в арсенале JUnit'а есть инструменты, позволяющие автоматизировать такое скучное дело, как unit-тестирование.

Однако прежде чем начинать писать код, который будет заниматься непосредственно "прогоном" всех тестов, нужно провести некоторую предварительную подготовку. В чём именно заключается данная подготовка? Для того, чтобы понять это, нужно вспомнить, в чем именно заключается методология unit-тестирования. Ведь в рамках данного типа тестирования проверяется отдельно функциональность каждого класса (и даже каждого метода). Что означает атомарную сущность модульного тестирования: каждый тест является независимым от всех остальных, и в ходе тестирования нам очень важно обеспечить эту независимость для того, чтобы не возникло случайно ошибок, связанных с воздействием тестов на тестируемые классы. Впрочем, возникнувшие новые ошибки - это ещё полбеды; гораздо хуже будет, если ошибки окажутся пропущенными. Каким именно образом обеспечивается атомарность тестов? Очевидно, что перед запуском каждого из них необходимо привести всё в изначальный вид, то есть обнулить все глобальные переменные (или задать им какие-то необходимые значения), инициализировать какие-то вспомогательные объекты и т. п. Само собой, такой мощный и продвинутый фреймворк, как JUnit, позволяет делать все эти вещи. Для этого нужно будет всего лишь реализовать в тестовых классах кое-какие дополнительные методы.

Первый из данных методов называется setup. Аргументов у него никаких нет, а вызывается он, как несложно догадаться по названию, перед выполнением теста. Если же вы хотите что-то почистить или изменить уже после того, как тест был выполнен, вам нужно вызывать метод tearDown. У него, как и у первого метода, нет никаких параметров. Собственно, именно эти два метода помогут нам защитить тесты, которые будут в дальнейшем увязаны в связку, друг от друга. В принципе, для большинства тестов можно обойтись и без применения данных методов, однако полезно иметь в виду, что они всё-таки существуют и могут быть использованы программистом в случае возникновения подобной необходимости. Вряд ли вам понадобится, впрочем, в таком случае одновременно реализовывать в своём тестовом классе и setUp, и tearDown. Хотя, конечно, случаи бывают разные.


Резюме

Мы с вами продолжили разговор о таком замечательном средстве модульного тестирования приложений, как JUnit. Закончить, к сожалению (хотя, может, и наоборот - к счастью) данный разговор в этот раз не получилось, потому что мы ещё не обсудили такой важный момент, как создание серии тестов и, конечно же, её запуск. Так что нам с вами предстоит ещё раз вернуться к теме unit-тестирования на страницах "Компьютерных вестей". Буду искренне надеяться, что тема тестирования приложений вам ещё не совсем наскучила, и вы дочитаете до конца серию статей о фреймворке JUnit.

Вадим СТАНКЕВИЧ,
dreamdrusch@tut.by

Версия для печатиВерсия для печати

Номер: 

45 за 2009 год

Рубрика: 

Software
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!

Комментарии

Аватар пользователя Логик
Какая версия JUnit описывается в cтатьях?
Аватар пользователя Вадим Станкевич
У меня стоит 4.4. Но, в принципе, это ведь не именно по этой версии статьи.
Аватар пользователя Al
Такие статьи хорошо бы препровождать абзацем на тему для чего это сделано и кому это нужно. Так читателю проще определиться, читать или нет. Да и автор самодисциплинируется и начинает понимать, что же он пишет. ))
Аватар пользователя Логик
Вадим Станкевич > У меня стоит 4.4. Но, в принципе, это ведь не именно по этой версии статьи.

Дело в том, что JUnit3 отличается довольно сильно от JUnit4.

Аватар пользователя Вадим Станкевич
Я в курсе, но я думаю, что сейчас вряд ли кто-то, кто изучает JUnit с нуля, будет пользоваться 3-й версией.
Аватар пользователя Логик
Вадим Станкевич > У меня стоит 4.4.

Тогда почему вы пишите в стиле JUnit3?

Например:

public class TestCounter extends TestCase

Не требуется и НЕ надо в JUnit4 наследоваться (extends) от класса TestCase

Да и ничего про setUp() и tearDown() писать НЕ надо уже - в JUnit4 все делается с помощью аннотаций методов и классов. - Но об этом вы почему то(?) ни слова!

Аватар пользователя Вадим Станкевич
Ну а что, разве это не работает?
Аватар пользователя Логик
Вадим Станкевич > Ну а что, разве это не работает?

Типа, опишу-ка я новый Bluray дисковод - DVD диски читает!

;-)

Аватар пользователя Вадим Станкевич
Ну примерно так :)