Выбрать главу

Возьмём относительно простой пример: компонент, конвертирующий сумму из одной валюты в другую.

Из элементов стандартизации точно присутствуют коды валют по ISO 4217[18] и, частично, список служб, к которым компонент может обращаться (см., например, каталог служб Financial API). Интерфейс самого компонента не стандартизован, для возможной его замены в будущем без последующей структурной перекройки вашего приложения потребуется обернуть компонент в адаптер (привет, шаблоны!). Это поможет избежать реструктуризации при замене, но не гарантирует работоспособность на том же входном наборе.

Теперь оценим количество состояний, которые необходимо охватить для полноты модульного тестирования, раз уж мы следуем логике разработки «железа». ISO 4217 даёт список из 164 валют. Предположим, что наши входные данные:

• имеют только два знака после запятой;

• значения положительные;

• максимальная величина – 1 миллион;

• дата конвертации всегда текущая;

• мы используем только 10 валют из 164.

Несложный комбинаторный подсчёт показывает, что даже такой сильно урезанный входной набор характеризуется количеством размещений из 10 по 2, помноженным на 100 миллионов входных значений (1 миллион с шагом 0,01):

102 × 100 000 000 = 10 000 000 000.

То есть для обеспечения полноты тестирования нашего входного набора потребуется 10 миллиардов проверок! Сравните, например, с микросхемой дешифратора, преобразующего входное 4-разрядное двоичное значение в сигнал на одном из 16 выходов. Входных наборов будет всего 16, а таблица истинности состоит из 162 = 256 значений.

На практике программист применит допустимую эвристику и будет тестировать, например, только несколько значений (один миллион, ноль, случайная величина из диапазона) для нескольких типовых конвертаций из 100 возможных, дополнительно проверяя допустимую точность значений на входе. При этом формальный показатель покрытия модульными тестами по-прежнему будет 100 %…

Но это ещё не всё. Микросхема работает с заданной тактовой частотой. Если, например, частота равна 1 МГц, то подав на вход набор значений, вы гарантированно через одну микросекунду получите результат на выходе.

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

Может быть, зависнет навечно, если не предусмотрен тайм-аут. А если несколько параллельных запросов?

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

Таким образом, собрав из кучи микросхем устройство, мы уверены, что оно будет работать:

• согласно таблицам истинности;

• с заданной тактовой частотой.

Собрав же из компонентов программу, мы можем только:

• приблизительно и с некоторой вероятностью оценивать время отклика на выходе;

• в большинстве случаев ограничиться выборочным тестированием, забыв о полноте.

Если вам говорят: «Пришло время собранных из кубиков программ», будьте в курсе ограничений технологии. Очень уж далеки программные компоненты от электронных кубиков.

Безысходное программирование

Любая программа, даже созданная визуально, имеет в своей основе исходный код на каком-либо языке программирования.

Безысходное программирование – это программирование без «исходников». То есть мы пишем свой код, не имея исходных текстов используемой подпрограммы, класса, компонента и т. п.

Когда необходимо обеспечить гарантированную работу приложения, включающего в себя сторонние библиотеки или компоненты, то, не имея доступа к их исходному коду, вы остаётесь один на один с «чёрным ящиком». Даже покрыв их тестами, близкими к параноидальным, вы не сможете понять всю внутреннюю логику работы и предусмотреть адекватную реакцию системы на нестандартные ситуации. Поэтому программирование без исходников в таком сценарии превращается в настоящую безысходность и безнадёгу.

Пока цена ошибки в приложении – потеря нескольких строк введённой пользователем информации, дело может ограничиться долгоживущей записью в базе данных ошибок, закрываемой не её исправлением, а описанием обхода «граблей»[19]. Но ситуация кардинально поменяется, если цена будет исчисляться многими нулями потерь от упущенной сделки в торговой системе, сотнями исков клиентов, получивших неверные счета, или того хуже – аварией на производстве. Ответственность с разработчиков никто не снимал.

вернуться

18

Международный стандарт, регламентирующий кодификацию валют.

вернуться

19

Грабли – синоним скрытой ошибки в программе. «Наступить на грабли» в программистском фольклоре означает выявить скрытую проблему за собственный счёт.