Литмир - Электронная Библиотека
A
A

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

 

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

26.3.4.1. Зависимости

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мы уже видели такой пример: классы графического интерфейса из глав 13–16. Они изолируют главную программу (т.е. код, который вы написали) от готовой системы графического пользовательского интерфейса: FLTK, Windows, Linux и т.д. При такой схеме мы можем использовать любую систему ввода-вывода.

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Важно ли это? Мы считаем, что это чрезвычайно важно. Во-первых, это облегчает тестирование, а без систематического тестирования трудно серьезно рассуждать о корректности. Во-вторых, это обеспечивает переносимость программы. Рассмотрим следующий сценарий. Вы организовали небольшую компанию и написали ваше первое приложение для системы Apple, поскольку (так уж случилось) вам нравится именно эта операционная система. В настоящее время дела вашей компании идут успешно, и вы заметили, что большинство ваших потенциальных клиентов выполняют свои программы под управлением операционной систем Windows или Linux. Что делать? При простой организации кода с командами графического интерфейса (Apple Mac), разбросанными по всей программе, вы будете вынуждены переписать всю программу. Эта даже хорошо, потому что она, вероятно, содержит много ошибок, не выявленных в ходе несистематического тестирования. Однако представьте себе альтернативу, при которой главная программа отделена от графического пользовательского интерфейса (для облегчения систематического тестирования). В этом случае вы просто свяжете другой графический пользовательский интерфейс со своими интерфейсными классами (транслятор на диаграмме), а большинство остального кода системы останется нетронутым.

377
{"b":"847443","o":1}