Лекция 7. Контроль качества программного продукта
Предположим, все, о чем мы говорили на мази -- совместно разрабатываем программный продукт, пишем его хорошо, документируем. Осталось выяснить, работает он или нет.
Как бы нам убедиться, что наш продукт нормальный? На прошлой лекции было про то, как убедиться, что код нормальный -- писать в соответствии с полиси и стайлгайдом, и т. п.
Определим места, в которых надо убеждаться в нормальности.
- сборка (собираемость и журнал). Если программа не собирается, то она неработающая, ваш капитан. Предположим, что программа собиралась, но с предупреждениями -- ссылочку не так закастили, инклюдик забыли. Этап сборки можно считать успешным, если программа собралась и журнал сборки (ворнинги) не стал хуже. Если он стал хуже, то может программа стала хуже, может инструменты сборки стали требовательней. Соответственно, нужно смотреть, как и насколько ухудшились. Мы можем, конечно, писать -Wall так, чтобы не было предупреждений. Но далеко не все проекты компилируются с -Werror, и в таких случаях хорошо бы следить за численными показателями. Представьте ситуацию -- идет разработка большого программного продукта. Собирается он долго и не на всякой машине. Сидит программист, коммитит. Как часто надо пересобирать? Надо ли это делать на машине программиста? Вопрос сложный и без однозначного ответа.
- когда пересобирать
- регрессия журналов Решаются эти вопросы полуавтоматически. Но возникают проблемы вида 1000 ворнингов можно, а 1001 нельзя. В любом случае, контроль собираемости это хороший инструмент контроля качества, показываюший, что хоть какое то качество имеется. Проблема -- многие люди на нем останавливаются. Программа собирается, запускается, значит хорошая. Часто этим страдают мантейнеры, которые собирают по привычке, но не пользуются собранным.
- Интеграционное тестирование. программа собралась, предосудительного в логе не нашли. Какие могут быть еще вопросы некачественной сборки, если бинарник получился нормальный? Помимо бинарника есть ещё окружение. Нам же надо чтобы оно задеплоилось в какую-то среду и стало работать. Если так получилось, что собранный нами бинарник требует не таких библиотек, какие есть в той среде, то это негодный бинарник. Соответственно, пункт второй -- интеграция. Получившийся продукт может плохо интегрироваться в среду, для которой предназначен. Палка о двух концах -- можем захотеть библиотеки с другим апи, или, если мы библиотека, то можем изменить апи. Этот кусок называется
- системное тестирование. Если вы что-то запускаете из своего пп, то оно обязано существовать. Чтобы отследить подобного рода ухудщение качества недостаточно запустить программу в тестовом окружении, потому что тестов ограниченное число, оттестировать всю функциональность невозможно. А вот наличие аби можно полностью проверить. Кроме свойств продукта на этом этапе вы должны знать свойства среды деплоймента. Когда вы делаете сборочное тестирование вы худо-бедно взаимодействуете с сборочным окружением, а в системном тестировании уже окружение деплоймента.
- отслеживание зависимостей. Первое с чем вы сталкиваетесь, работая с линуксом -- зависимости. По сборке, по запуску, по наличию файлов, итд.
- отдельный интерес представляет изменение в аби, которое невозможно без специального инструмента. В сизифе введено понятие зависимости на хеш используемых функций. Даже это не помогает нам полностью отследить изменение аби -- может измениться не имя, а параметры. Эти изменения надо как-то проверять. Вообще говоря, встроенных инструментов в трад. яп нету, хотя в виртовских, типа оберона, они были, хотя и немного странное -- версионирование и при несоответствии версий конфликт. Похожий механизм сейчас для модулей ядра. Есть сейчас такие ребята из ИСП, которые занимаются верификацией кода сложных программ, и есть у них 20000 строк кода на перле -- инструмент ABI Compliance Checker -- у них три автора -- Хорошилов, Рубанов и тот, кто всё это написал. Эта удивительная хрень берёт два h файла, и сравнивает ABI. Мощный, интересный инструмент. Достаточно формализуемая задача -- сравнить два списка сишных хедеров. Несколько хуже обстоит дело с ситуацией, когда у вас среда, в которую вы планируете помешать продукт -- большая. Например сизиф. Собираете новый пакет, он не должен мешать остальным 12000.
- глобальное пространство имен, namespace. Для линукс разработчиков такая проблема вполне разрешима, благодаря специальной процедуре помещения туда. Необязательно, чтобы это пространство было непротиворечиво, но вы своим проектом не должны это ухудшить. Идеально это тоже не работает, потому что когда выходит новая версия библиотеки, она вполне может осознанно ломать аби. Пример контроля за пространством имен это любая современная сборочница линукс-дистрибутива. На одну такую (ginar-builder) дана ссылка на странице материалов лекций. Это не очень большая пачка шелл-скриптов, можете почитать.
- системное тестирование. Если вы что-то запускаете из своего пп, то оно обязано существовать. Чтобы отследить подобного рода ухудщение качества недостаточно запустить программу в тестовом окружении, потому что тестов ограниченное число, оттестировать всю функциональность невозможно. А вот наличие аби можно полностью проверить. Кроме свойств продукта на этом этапе вы должны знать свойства среды деплоймента. Когда вы делаете сборочное тестирование вы худо-бедно взаимодействуете с сборочным окружением, а в системном тестировании уже окружение деплоймента.
- Работоспособность.
- приемочное тестирование. Программа запускается, а мы в нее начинаем тыкать. Собрали, теперь запустим и покликаем. Главных проблем две
- написание достаточно глубокой методики тестирования. Программа обладает многими свойствами, свойства вступают друг с другом в суперпозицию и дают факториал новых свойств. Описать все случаи использования невозможно. Когда строим программу испытаний/методику тестирования мы себя ограничиваем, выкидывая примерно 100 процентов возможных ситуаций, оставляя конечное количество типичных юзкейсов. Задача абсолютно неавтоматизируемая. Хотя, если ваш продукт является библиотекой, там хотя бы есть костяк -- пишем программу, которая использует все функции различными спосоами.
- тестовое окружение. Если не принимать дополнительных ограничений автоматическое тестирование или невозможно, или ужасно. Монстры, обещаюшие протестировать полностью приложение ужасны, и как правило накладывают ограничения на расположение и название кнопочек, итп. Приемочное тестирование в общем -- не очень автоматизируемый. Хотя в частных случаях вполне автоматизируем. Например, математическая библиотека. Или установщик альтлинукса -- программа с конечным количеством состояний. Тот же установщик -- несколько формочек с органиченным количеством кнопочек и небольшим количеством вариантов правильного функционирования, любая нештатная ситуация означает нерабочесть. Итак, относительно просто тестируются
- библиотеки с ограниченным количеством функций
- интерфейсно малые приложения
- интерфейс командной строки. С ним можно забавляться как хочешь. expect - специально изобретенный для этого язык программирования, на нем написано много инструментов, например DejaGNU. Экспект это специальный язык программирования, но и самопальных подобных причиндалов. Expect на tcl, Empty на шелле. Отметим, это не просто для пассивного ввода-вывода, но и иммитация терминальной линии, когда пользователь будто бы вводит какие-либо командны.
- Что касается UI. Существует очень много инструментов, нажимаюших на кнопочки в виндоусе. Подобного рода инструменты есть и под линукс. Xautomation, xpresser. Общее свойство фиговин, кликающих на кнопочки в уи. Какая главная проблема? Как узнать, что эта группа пикселей на экране это кнопка. Один способ решения - знаем на чем написано приложение, лезем ему в голову и ищем координаты виджетов. Или ищем на экране заданную картинку. Это хорошо, если шрифт один и тот же, и кнопочки серые. Плохо, если дизайнер перегенировал пнгшки? Тогда xautomation перестает работать. Поэтому придумали кнопку -- отключить темы вообще, совсем.
- Ещё один пример жестко детерминированного интерфеса -- веб-интерфейсы и сетевые приложения. Инструментов много, нормального лектор не нашел. Тема очень горячая. Каждая четная собака хочет веб-магазин, а каждая нечетная предлагает инструменты для этого. Selenium. Приятность этого дела, что хтмл очень простой, красота вся наводится отключаемым цссом.
- библиотеки с ограниченным количеством функций
- попробуем отступить на шаг назад. Не могли мы методику тестирования как либо породить? Порождение тестов. Оказывается, можно. Все тот же товарищ из ИСП написал небольшой скрипт на перле на 20 000 -- API sanity cheking. Если предыдущий скрипт был ещё наука, то тут чистое искуссто. По имени переменной догадаться о ее семанике, по типу структуры о ее содержимом. Но как ни странно эффект массовости дает о себе знать. В библиотеке из 1000 функций на 100 он даст неправильный тест, а на 2 найдет ошибки. Проблема -- иногда дает false negative. Так же, для порождения тестов по исходному коду код должен быть специальным образом оформлен, причем образом, удобным для генератора, а не для программиста.
- Разработка через тестирование, по русски - test driving develoment. Идея -- когда пишем программу, пишем, как она должна работать. Сначала делаем тест, который не работает (ещё до реализации функции). Когда тест не работает -- реализуем функцию, доводим до того, чтобы тест работал. Точно также, как Literate programming -- способ изложения программы на родном языке, макроподстановкой переводящийся в код. Можете зайти на страничку википедии. Там живо описаны бенефиты такого подхода. Недостатки -- люди не любят поступать правильно. Второй -- в таком случае удобно программировать на всяких контрактных языках типа эйфеля. На низкоуровневых языках вам придется перестраивать голову. Третье, скорее свойство -- оверхелм -- сначала вы будете писать заглушки. Хорошо тем, что вы сразу делаете некую архитектуру. Плохо -- что эта архитектура наверняка походу изменится и их придется переписывать.
- Модульное тестирование. Однако есть компромисс, -- юнит тестирование как таковое. Сначала код с юнит тестами, потом независимый системный тест, а потом приемочный. Тест дривен подход будет относиться только к отдельным компонентам, а не ко всему продукту. Резко обрезанный сверху и снизу вариант тест дривен. Про юнит тестирование знают все промыщленно программировавшие люди. Если не знают, то они несчастны. Дошло до того, что образовалась целая идеология модульного тестирования, которая называется xunit. Её сформулировал какой-то человек, программировавщий ещё на смолтолке.
- есть понятие test case, что тестируем (?)
- В неё входит окружение, при котором тест эффективен. Нужно подготовить объекты, с которыми работает функция. Если есть файлы, они должн быть на месте. Fixture
- Test Suit. Внутри одного окружения можно продлеать несклоько тестов.
- Assertion. Замеряете параметры до, после, и смотрите, оправдываются ли ваши ожидания.
- Тирдаун -- свертка окружения. Ничего экстроординароного и сверхумного в этой идеологии нет, но надо было как-то формализовать тестирование отдельных кусков программы. Основные инструменты юнит-тестирования
- CUnit, Check (c)
- boost (c++)
- unittest (python). Хотя даже для питона их сотни, потом что как именно делается тот же тирдаун идеологией не формализовано
- jtest (java) Изобратать 10001-ый инструмент не стоит, лучше взять существуюшее. Итак, юниттесты вклиниваются в тестирование посередине. Тестируется не вся программа, а отдельные компоненты.
- CUnit, Check (c)
- приемочное тестирование. Программа запускается, а мы в нее начинаем тыкать. Собрали, теперь запустим и покликаем. Главных проблем две
Как все это собрать в единую кучу? Много инструменов, все разные, как организовать непрерывный процесс разработки. Перечислим стадии.
- Верификация исходного кода.
- Сборка. В процессе сборки -- модульное тестирование.
- Формирование дистрибутива (чего-то, что будет передано и где-то установлено)
- Деплоймент. Не обязательно это инсталяция, мало ли, как вы помещаете свой код в рабочую среду. Процедура введения кода в строй. Как именно это происходит не важно, важно, что он может не пройти.
- Приемочное тестирование функциональности
- Публикация.
Обратите внимание, что практически каждый из этих пунктов требует специфического, неравного другим окружения. Весь цикл разработки и тестирования программного продукта требует от той среды, в которой происходит разной функциональности и может быть реализован на разных компьютерах.
Рекомендуется поглядеть на buildbot.
Замечание. Этап сборки не должен происходить на компьютере программиста. Черт знает, что программист себе поставил, во-первых. Во-вторых, не дело программиста собирать проект под 8 разных архитектур, например. Это дело сборочника, который должен собирать и доносить до программиста проблемы.
Второе замечание -- мы ничего не поговорили о тестировании живыми людьми. А в случае свободного софта это львиная доля процесса тестирования. В следующий раз поговорим о коммьюнити-ориентед инстурментах -- всякие багзиллы, вот это всё. Это совершенно отдельная тема, она толкует не столько о процессе тестирования, сколько о процессе взаимодействия между людьми.