суббота, 23 февраля 2013 г.

Контроль целостности кода функций

В ходе разработки сложной многокомпонентной системы для автоматизированного тестирования сканера безопасности, возникла проблема контроля целостности и проведения ревизий кода отдельных тестовых функций. Функциональных тестов, запускаемых системой, было написано уже около 2 сотен и их число продолжает увеличивается. В нашем случае один функциональный тест - это одна функция. Как правило, после разработки теста и постановки ему статуса "готов" в некотором трекере, о нём забывают и надолго. Однако, в процессе разработки других тестовых функций часто возникает необходимость проведения рефакторинга, которая по невнимательности тестировщика-автоматизатора может затронуть и уже готовые, отлаженные тесты.

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

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

1. Суть идеи.



Для каждой функции может быть определена ревизия, набор из хеша и кода функции:
(func_hash, func_source)

Все критичные функции могут быть добавлены в словарь ревизий:
{"funcName1": (funcName1_hash, funcName1_source),
 "funcName2": (funcName2_hash, funcName2_source), ...}

Например, для нас критичными являются все функции с уже разработанными тестами. Хранить все ревизии можно в файле-ревизий, обычном текстовом файле, содержащем список из даты последней ревизии и словарь с ревизиями:
[revision's last date-n-time, {revisions}]

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

Конечно же, имеются и альтернативные варианты решения проблемы: код-ревью и использование инструментов в код-репозитариях (git, svn etc). Однако, код-ревью бесполезен в случае внесения автоматических изменений в сотнях тестов, а отслеживать изменения в коде, используя инструменты репозитария, после нескольких мержей и трудно и долго. Также  механизм ревизий позволяет решить проблему того, что на функции-тесты обычно не пишут юниттесты, но при этом необходимо контролировать их качество и неизменность.

2. Программный код.

Для реализации описанной идеи, на Python 3.2 был написан небольшой модуль FileRevision.py. Имеющийся в нём класс Revision() можно импортировать в свой проект и добавить ревизии для нужных вам функций. При небольших доработках, можно дополнительно реализовать, например, сжатие файла ревизий, но для небольших по объему проектов это не критично.

Программный код доступен архивом по ссылке:
Код на GitHub:

Реализация модуля:

class Revision():
__init__() # Инициализация параметров.
_ReadFromFile() # Получение ревизий из файла.
_WriteToFile() # Запись ревизий в файл.
_GetOld() # Получение предыдущей ревизии для функции.
_GetNew() # Получение новой ревизии для функции.
_Similar() # Сравнение двух ревизий.
Update() # Обновление ревизии для указанной функции.
DeleteAll() # Удаление всех ревизий из файла.
ShowOld() # Вывод информации о предыдущей ревизии для функции.
ShowNew() # Вывод информации о новой ревизии для функции.
Diff() # Сравнение ревизий и вывод диффа для функции при необходимости.
_testFunction() # Фейковая функция для проверки работы модуля.
if __name__ == '__main__':() # Примеры использования модуля, при его отдельном запуске.

Для того, чтобы посмотреть примеры использования данного модуля, достаточно его запустить:
python FileRevision.py


При первом запуске будет обнаружено, что для фейковой функции нет ревизии, затем будет предложено обновить информацию о ней, очистить файл ревизий, вывести информацию о предыдущей ревизии и о новой ревизии. Файл revision.txt, с ревизиями для примеров, будет создан рядом с .py файлом.

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