testMethod(self):
file = File("foobar"). open()
try:
…âûïîëíèòü òåñò…
finally:
file.close()
Если файл используется в нескольких тестах, вы можете сделать его частью общей фикстуры:
setUp(self):
self.file = File("foobar"). open()
testMethod(self):
try:
…выполнить тест…
finally:
self.file.close()
Во-первых, возникает неприятное дублирование выражений finally – это означает, что мы упустили что-то в дизайне. Во-вторых, при написании подобного метода можно легко допустить ошибку, например забыть добавить ключевое слово finally или вообще забыть о необходимости закрытия файла. Наконец, в этом тесте существует три сбивающих с толку строки – try, finally и сама команда close, – эти выражения не относятся непосредственно к процедуре тестирования.
Инфраструктура xUnit гарантирует вызов метода под названием tearDown() после выполнения тестового метода. Метод tearDown() будет вызван вне зависимости от того, что случится внутри тестового метода (однако следует иметь в виду, что, если сбой произойдет в ходе выполнения метода setUp(), метод tearDown() вызываться не будет). Мы можем преобразовать предыдущий тест следующим образом:
setUp(self):
self.file = File("foobar"). open()
testMethod(self):
…выполнить тест…
tearDown(self):
self.file.close()
Тестовый метод (Test Method)Что такое единичный тест? Это метод, имя которого начинается с префикса test.
В процессе разработки вам придется иметь дело с сотнями, а может быть, и тысячами тестов, как можно уследить за всеми этими тестами?
Языки объектно-ориентированного программирования обеспечивают трехуровневую организацию исходного кода:
• модуль (в языке Java – пакет, по-английски, package);
• класс;
• метод.
Если мы пишем тесты как обычный исходный код, мы должны найти способ организации тестов с использованием элементов этой структуры. Если мы используем классы для представления фикстур, значит, методы этих классов являются естественным местом размещения тестирующего кода. Все тесты, использующие некоторую фикстуру, должны быть методами одного класса. Тесты, работающие с другой фикстурой, должны располагаться в другом классе.
В xUnit используется соглашение, в соответствии с которым имя тестового метода должно начинаться с префикса test. Специальные инструменты могут автоматически производить поиск таких методов и создавать из них наборы тестов (TestSuite). Остальная часть имени теста должна информировать будущего, ни о чем не ведающего читателя, зачем написан данный тест. Например, в наборе тестов, созданных при разработке инфраструктуры JUnit, можно обнаружить тест с именем testAssertPosInfinityNotEqualsNeglnfinity. Я не помню, чтобы я писал этот тест, однако, исходя из имени, могу предположить, что в какой то момент разработки было обнаружено, что код метода assert() инфраструктуры JUnit для чисел с плавающей точкой не делал различия между положительной и отрицательной бесконечностью. Использовав тест, я могу быстро найти код JUnit, осуществляющий сравнение чисел с плавающей точкой, и посмотреть, как осуществляется обработка положительной и отрицательной бесконечности. (На самом деле код выглядит не идеально – для поддержки бесконечности используется условный оператор).
Код тестового метода должен легко читаться и быть максимально прямолинейным. Если вы разрабатываете тест и видите, что его код становится слишком длинным, попробуйте поиграть в «детские шажки». Цель игры – написать самый маленький тестовый метод, который представляет собой реальный прогресс в направлении вашей конечной цели. Размер в три строки, судя по всему, является минимальным размером (если, конечно, вы не хотите делать тест намеренно бессмысленным). И постоянно помните о том, что вы пишете тесты для людей, а не только для компьютера и себя самого.
Патрик Логан (Patrick Logan) рассказал об идее, с которой я намерен поэкспериментировать. Эта идея также описана Макконнеллом (McConnell)[21], а также Кэйном (Caine) и Гордоном (Gordon)[22]:
В последнее время я фактически постоянно применяю методику «основных тезисов» в любой моей работе. Тестирование не является исключением. Когда я пишу тесты, я прежде всего записываю план из нескольких пунктов – тезисов, – которые я хотел бы реализовать в этом тесте. Например:
/* Добавить в пространство кортежей[23] */
/* Извлечь из пространства кортежей */
/* Читать из пространства кортежей */
Это самые основные тезисы, однако я добавляю в каждую из этих категорий конкретные тесты. Когда я добавляю тесты, я добавляю в мой список тезисов еще один уровень комментариев:
/* Добавить в пространство кортежей */
/* Извлечь из пространства кортежей */
/** Извлечение несуществующего элемента **/
/** Извлечение существующего элемента **/
/** Извлечение нескольких элементов **/
/* Читать из пространства кортежей */
Как правило, мне хватает двух-трех уровней комментариев. Я не могу представить ситуацию, в которой мне могло бы потребоваться больше уровней. Список тезисов становится документацией контракта для тестируемого класса. Приведенные здесь примеры, конечно же, сокращены, однако в языках программирования, поддерживающих контракты, тезисы могли бы быть более конкретными. (Я не использую какие-либо добавления к Java, обеспечивающие автоматизацию в стиле Eiffel.)
Сразу же после самого низкого уровня комментариев располагается исходный код теста.
Тест исключения (Exception Test)Как можно протестировать ожидаемое исключение? Перехватите исключение и игнорируйте его, тест должен терпеть неудачу только в случае, если исключение не сгенерировано.
Предположим, что мы пишем код, осуществляющий поиск значения. Если значение не обнаружено, мы хотим сгенерировать исключение. Тестирование механизма поиска выполняется относительно просто:
public void testRate() {
exchange.addRate("USD", "GBP", 2);
int rate = exchange.findRate("USD", "GBP");
assertEquals(2, rate);
}
Тестирование исключения может оказаться неочевидным. Вот как мы это делаем:
public void testMissingRate() {
try {
exchange.findRate("USD", "GBP");
fail();
} catch (IllegalArgumentException expected) {
}
}
Если метод findRate() не генерирует исключения, произойдет обращение к методу fail() – это метод xUnit, который докладывает о том, что тест потерпел неудачу. Обратите внимание, что мы перехватываем только то исключение, которое должно быть сгенерировано методом findRate(). Благодаря этому, если будет сгенерировано какое-либо другое (неожиданное для нас) исключение (включая сбой метода assert), мы узнаем об этом.
Все тесты (All Tests)Как можно запустить все тесты вместе? Создайте тестовый набор, включающий в себя все имеющиеся тестовые наборы, – один для каждого пакета (package) и один, объединяющий в себе все тесты пакетов для