Читаем Программирование. Принципы и практика использования C++ Исправленное издание полностью

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

  Обратите внимание на то, что мы считаем неизбежным случайное внесение новых ошибок при исправлении старых. Мы рассчитываем, что новых ошибок меньше, чем старых, которые уже удалены, причем последствия новых ошибок менее серьезные. Однако, по крайней мере пока, мы вынуждены повторять регрессивные тесты и добавлять новые тесты для нового кода, предполагая, что наша система вышла из строя (из-за новых ошибок, внесенных в ходе исправления).

<p id="AutBody_Root523"><strong>26.3.4.1. Зависимости</strong></p>

  Представьте себе, что вы сидите перед экраном, стараясь систематически тестировать программу со сложным графическим пользовательским интерфейсом. Где щелкнуть мышью? В каком порядке? Какие значения я должен ввести? В каком порядке? Для любой сложной программы ответить на все эти вопросы практически невозможно. Существует так много возможностей, что стоило бы рассмотреть предложение использовать стаю голубей, которые клевали бы по экрану в случайном порядке (они работали бы всего лишь за птичий корм!). Нанять большое количество новичков и глядеть, как они “клюют”, — довольно распространенная практика, но ее нельзя назвать систематической стратегией. Любое реальное приложение сопровождается неким повторяющимся набором тестов. Как правило, они связаны с проектированием интерфейса, который заменяет графический пользовательский интерфейс.

Зачем человеку сидеть перед экраном с графическим интерфейсом и “клевать”? Причина заключается в том, что тестировщики не могут предвидеть возможные действия пользователя, которые он может предпринять по ошибке, из-за неаккуратности, по наивности, злонамеренно или в спешке. Даже при самом лучшем и самом систематическом тестировании всегда существует необходимость, чтобы систему испытывали живые люди. Опыт показывает, что реальные пользователи любой значительной системы совершают действия, которые не предвидели даже опытные проектировщики, конструкторы и тестировщики. Как гласит программистская пословица: “Как только ты создашь систему, защищенную от дурака, природа создаст еще большего дурака”.

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

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

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

Приведенный ниже рисунок иллюстрирует два важных аспекта хорошего тестирования.

• Части системы следует (по возможности) тестировать по отдельности. Только модули с четко определенным интерфейсом допускают тестирование по отдельности.

• Тесты (по возможности) должны быть воспроизводимыми. По существу, ни один тест, в котором задействованы люди, невозможно воспроизвести в точности.

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

Перейти на страницу:

Похожие книги

Programming with POSIX® Threads
Programming with POSIX® Threads

With this practical book, you will attain a solid understanding of threads and will discover how to put this powerful mode of programming to work in real-world applications. The primary advantage of threaded programming is that it enables your applications to accomplish more than one task at the same time by using the number-crunching power of multiprocessor parallelism and by automatically exploiting I/O concurrency in your code, even on a single processor machine. The result: applications that are faster, more responsive to users, and often easier to maintain. Threaded programming is particularly well suited to network programming where it helps alleviate the bottleneck of slow network I/O. This book offers an in-depth description of the IEEE operating system interface standard, POSIX (Portable Operating System Interface) threads, commonly called Pthreads. Written for experienced C programmers, but assuming no previous knowledge of threads, the book explains basic concepts such as asynchronous programming, the lifecycle of a thread, and synchronization. You then move to more advanced topics such as attributes objects, thread-specific data, and realtime scheduling. An entire chapter is devoted to "real code," with a look at barriers, read/write locks, the work queue manager, and how to utilize existing libraries. In addition, the book tackles one of the thorniest problems faced by thread programmers-debugging-with valuable suggestions on how to avoid code errors and performance problems from the outset. Numerous annotated examples are used to illustrate real-world concepts. A Pthreads mini-reference and a look at future standardization are also included.

David Butenhof

Программирование, программы, базы данных