Программист-прагматик. Путь от подмастерья к мастеру
Шрифт:
При работе с языком С++ вы можете добиться того же эффекта (во время компиляции) используя конструкцию #ifdef для выборочной компиляции программы модульного теста. Ниже представлен очень простой модульный тест на языке С++, внедренный в наш модуль и проверяющий работу функции извлечения квадратного корня с помощью подпрограммы testValue, подобной программе на языке Java, реализованной ранее:
#ifdef _TEST_
int main(int argc, char **argv) {
argc-; argv++; // пропускаем имя программы
if (argc<2) { // стандартные тесты, если аргументы не указаны
TestValue(-4.0, 0.0);
TestValue(0.0, 0.0);
TestValue(2.0, 1.4142135624);
TestValue(64.0, 8.0);
TestValue(1.0e7, 3162.2776602);
}
else { //
double num, expected;
while (argc>= 2) {
num = atof(argv[0]);
expected = atof(argv[1]);
testValue(num.expected);
argc – = 2;
argv += 2;
}
}
return 0;
}
#endif
Данный модульный тест запускает минимальный набор тестов или же (при наличии аргументов) позволяет использовать внешние данные. Эта возможность могла быть задействована в сценарии запуска более полного набора тестов.
Как поступить, если корректным откликом на модульный тест является выход из программы или ее аварийное завершение? В этом случае вам необходимо выбирать запускаемый тест, указывая аргумент в командной строке. Вам также придется передать некие параметры, чтобы указать различные начальные условия для ваших тестов.
Но разработки одних модульных тестов недостаточно. Вы обязаны выполнять их и выполнять часто. Это также полезно, если класс время от времени проходит процедуру тестирования.
Применение тестовых стендов
Поскольку обычно мы пишем большое количество тестирующих программ и проводим большое количество процедур тестирования, есть смысл облегчить себе жизнь и разработать стандартный тестовый стенд для конкретного проекта. Программа main, представленная в предыдущем разделе, является весьма простым тестовым стендом, но обычно нам нужно больше функциональных возможностей.
Тестовый стенд может осуществлять универсальные операции, такие как регистрация состояния системы, анализ выходных данных на наличие ожидаемых результатов, а также выбор и запуск конкретных процедур тестирования. Стенды могут управляться при помощи графического интерфейса, могут быть написаны на том же целевом языке, что и весь проект, или реализованы в виде сочетания сборочных файлов и сценариев на языке Perl. Простой тестовый стенд описан в ответе к упражнению 41 (см. Приложение В).
При работе с объектно-ориентированными языками и средами можно создать базовый класс, содержащий универсальные операции. Отдельные тесты могут создать подкласс и добавить специфические процедуры тестирования. Можно использовать стандартное соглашение об именовании и отражение на языке Java для формирования списка процедур тестирования в автоматическом режиме. Эта методика является прекрасным способом соблюдать принцип DRY – вам не приходится следить за списком доступных тестов. Но перед тем как взлететь и начать писать свой собственный стенд, есть смысл изучить методику xUnit Кента Бека и Эриха Гаммы [URL 22]. Они уже проделали всю сложную подготовительную работу.
Вне зависимости от выбранной вами технологии тестовый стенд обязан предоставлять следующие возможности:
• Стандартный способ определения установочной процедуры и завершения работы
• Метод выбора отдельных тестов или всех доступных тестов
• Средства анализа выходных данных на наличие ожидаемых (или неожиданных) результатов
• Стандартизированная форма отчета об обнаруженных неисправностях
Процедуры тестирования должны быть составными; другими словами, процедура тестирования может состоять из различающихся степенью детализации субтестов, которые направлены на подкомпоненты. Мы можем воспользоваться этой особенностью для тестирования отдельных компонентов или системы в целом, используя те же самые инструменты.
Во время отладки можно прекратить создание определенных тестов "на лету". Это может быть таким же простым делом, как оператор print или ввод фрагмента программы в интерактивной оболочке отладчика или ИСР.
В конце сеанса отладки необходимо формализовать процедуру специального тестирования. Если программа прервалась один раз, скорее всего она прервется снова. Не стоит просто отбрасывать в сторону созданную процедуру тестирования; добавьте ее к существующему модульному тесту.
Например, при помощи JUnit (элемент Java из семейства xUnit) можно записать процедуру проверки извлечения квадратного корня следующим образом:
public class JUnitExample extends TestCase {
public JUnitExampleffinal String name) {
super(name);
}
protected void setUpQ {
// Load up test data…
testData.addElement(new dblPair(-4.0,0.0));
testData.addElement(new dblPair(0.0,0.0));
testData.addElement(new dblPair(64.0,8.0));
testData.addElement(new dblPair(Double.MAX_VALUE, 1.3407807929942597E154));
}
public void testMySqrt {
double num, expected, result = 0.0;
Enumeration enum = testData.elements;
while (enum.hasMoreElements) {
dblPair p = (dblPair)enum.nextElement;
num = p.getNum;
expected = p.getExpected;
testValue(num, expected);
}
}
public static Test suite {
TestSuite suite= new TestSuite;
suite.addTest(new JUnitExample("testMySqrt"));
return suite;
}
}
Пакет JUnit разработан по модульному принципу: к нему можно добавлять сколько угодно тестов, и каждый из них может, в свою очередь, являться пакетом. В дополнение к этому для управления процедурой тестирования вы можете выбрать графический или текстовый интерфейс.
Построение тестового окна
Даже самые лучшие наборы тестов скорее всего не смогут обнаружить всех «жучков»: во влажных и жарких условиях реальной эксплуатации возникает нечто, что заставляет их вылезать из деревянных изделий.
Это означает, что зачастую приходится тестировать фрагмент программного обеспечения сразу после его развертывания – с реальными данными, текущими в его жилах. В отличие от печатной платы или чипа, в программном обеспечении нет тестовых контактов, но можно по-разному взглянуть на внутреннее состояние модуля, не прибегая к помощи отладчика (в производственных условиях его применение либо неудобно, либо просто невозможно).