Книга: The Programmers

Проектируй для тестирования

Проектируй для тестирования

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

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

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

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

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

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

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

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

У разработчиков объектных систем есть особенно простая стратегия автоматизации тестирования. Каждый класс (или ключевые классы, по решению архитектора) может иметь соответствующий класс, заданный так, что подменяет (эмулирует) методы системного класса. Это так хорошо работает потому, что описание этого класса стимулирует тестирование внешнего интерфейса класса в стандартизованном и хорошо определенным формате (в чем заключается суть объектов). Поэтому каждый класс может сопровождаться собственным тестовым кодом, который просто нужно заключить в небольшую обрамляющую программку для автоматизации тестирования. Эти тестовые классы иногда называют классами «янь» («yang»), а поставляемые классы называют соответственно классами «инь» («yin»).

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

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

Контрольные проверки и прогон автоматизированных тестов снизу доверху не следует рассматривать как помеху в работе — это очень дешевый путь получения подтверждения прочности фундамента. Как дополнительное преимущество, такие события становятся праздниками команды, по мере того как модуль за модулем, уровень за уровнем говорят об успешной компиляции и прохождении теста на рабочей станции менеджера. На таких праздниках команда может естественным образом взглянуть на все, чего они достигли к этому моменту, поскольку самый первый праздник может состоять просто в компиляции и запуске программы `Hello, world!' и доказательстве, что компилятор работает правильно, а последний дает в результате работающий продукт, который поставляется заказчику и в котором достигнуты все цели.

Оглавление книги


Генерация: 1.259. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз