Как стать автором
Обновить

Тестирование БД в легаси-проекте: повышение качества кода и стабильности системы в «Цифровой карте магазина»

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров892

Добрый день, Хабр!

Меня зовут Владимир Чирков, я старший разработчик баз данных Oracle в компании SM Lab. В этой статье я хотел бы поделиться с вами моим опытом написания юнит-тестов для базы данных (БД) в рамках легаси-проекта. Я выбрал формат ретроспективы, чтобы показать, как все начиналось, к чему мы пришли и какой путь был пройден в разработке юнит-тестов для проекта «Цифровая карта магазина». Возможно, статья будет полезна начинающим разработчикам баз данных или тем, кто хочет начать писать юнит-тесты для Oracle, но не знает, с чего начать.

 Описание продукта

«Цифровая карта магазина» – это решение для ведения в цифровом виде схемы магазина, к которой привязаны различные объекты:

  • архитектурный слой – схематичное изображение магазина с колоннами, лестницами, кассами, примерочными и другими элементами;

  • торговое оборудование (ТО) – оборудование, на котором размещаются товары (панели, гондолы, столы);

  • презентационные поверхности (ПП) – поверхности на торговом оборудовании, где непосредственно размещаются товары;

  • участки зон – очерченный геометрический участок, где располагается ТО (зона «Велосипеды», «Аксессуары»);

  • плановое и фактическое размещение товаров и элементов оформления.

Упрощенно бизнес-процесс продукта выглядит примерно так:

  • архитектор подготавливает DWG-файл с архитектурным слоем для загрузки в систему;

  • менеджер по зонированию пространства дорабатывает файл, расставляет ТО и передает результат в программу загрузки;

  • программа, написанная на Delphi с использование компонентов CadSoftTools, парсит DWG-файл на объекты: архитектурный слой, зоны, ТО, ПП, и сохраняет всё в Oracle

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

Почему не было тестов на БД

Итак, вернемся в 2016 г.

На тот момент команда состояла из разработчика БД, веб-разработчика и аналитика, и проект был похож на один большой эксперимент в ускоренном формате:

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

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

Отдельно стоит отметить сложности, связанные с разработкой юнит-тестов для БД:

  • сложность данных: в БД может быть множество взаимосвязанных таблиц, которое необходимо учитывать при тестировании. Например, для проверки новой функциональности объекта ПП требуется создание записей в нескольких справочниках, формирование магазина, его этажей, архитектурных слоев, ТО и участков зон. Это требует либо подготовки уникальных тестовых данных, либо использования данных из системы;

  • жизненный цикл тестовых данных: необходимо определить, как управлять тестовыми данными. В зависимости от типа реализации тестов нужно решить, можно ли выполнять их последовательно или это возможно только параллельно; удалять ли данные между запусками или откатывать транзакции;

  • сходство с интеграционными тестами: юнит-тесты для БД часто напоминают интеграционные, т.к. проверяют не одну единицу поведения, а несколько. Делается это для оптимизации скорости выполнения тестов, особенно когда требуется проверить несколько похожих действий, использующих один и тот же набор справочников, или объектов данных;

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

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

Совершим тайм-джамп: на дворе 2019 г.

Проект развивался, команда росла, а тестов всё еще не было. За это время я начал активно изучать лучшие практики разработки: паттерны проектирования, SOLID, абстракции, экстремальное программирование, TDD (разработку через тестирование). В этом мне помогли книги «Гибкая разработка программ» Роберта Мартина и «Рефакторинг кода» Мартина Фаулера. Чтение было увлекательным, особенно мне понравился пример из книги Мартина, где он демонстрировал TDD на примере простой программы для подсчета очков в игре в боулинг. 

Тогда я задумался: а можно ли создать юнит-тесты в базе данных нашего продукта? Оказалось, что можно. Более того, в нашей компании уже были команды, которые успешно писали такие тесты. Тогда для меня это стало открытием. 

Покрытие юнит-тестами на БД? Это звучало невероятно!

Руководитель посоветовал обратиться за советом к Максиму Пономаренко, опытному коллеге-тестировщику из другого проекта. К тому времени в его проекте уже использовался мощный движок для тестирования БД, основанный на утилитах utPLSQL Стивена Фейрштейна, автора книги «Oracle PL/SQL. Для профессионалов» (у Максима, кстати, есть хорошая статья на Хабре про utPLSQL). Этот движок помогал создавать макеты тестов и запускать их. Однако, будучи разработчиком БД, а не тестировщиком, я с трудом представлял, как внедрить подобное в наш уже существующий проект. 

Первые тесты, или хождение по мукам

Я попробовал написать несколько простых тестов на ключевой функционал с использованием utPLSQL и понял, что это не так просто. Написание юнит-тестов для БД требует значительных временных затрат, особенно на начальном этапе, когда еще не знаешь, как грамотно их структурировать. Необходимо подготовить данные, связать их между собой, продумать условия проверки: тестируемый запрос в базе может занимать всего несколько строк, а тесты для его проверки – сотни.

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

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

Однако появление первых тестов помогло решить следующие задачи:

  • даже небольшое количество тестов иногда находило ошибки при доработках кода;

  • разработка сложного функционала становилась проще. Например, при создании отчета на 1500 строк кода с множеством условий и комбинаций, юнит-тесты значительно упрощали отладку: без них приходилось вручную проверять каждое изменение, что часто приводило к новым ошибкам;

  • наличие минимального теста на функционал упрощало рефакторинг. Мы могли быть уверены, что отрефакторенный код будет работать – это не раз нас выручало. Например, когда мы переписывали логику сохранения карты магазина с множеством объектов, связей и зависимостей, мы сначала написали качественные тесты, а затем провели рефакторинг. Тесты были настолько хорошо написаны и покрывали код, что задача прошла приемку у тестировщиков без единого возврата (и это при том, что был переписан огромный пакет в несколько тысяч строк).

Но, несмотря на все плюсы, первые тесты использовали данные из системы, запускались вручную, были зависимы друг от друга и не могли выполняться параллельно. Кроме того, у нас были статичные серверы (dev, test, uat, etalon), а не Oracle PDB-контейнеры для каждого разработчика. Нужно было придумать, как установить тесты на серверы и организовать работу с тестами так, чтобы тесты разных разработчиков не мешали друг другу.

На тот момент я не представлял, как это можно было организовать.

Автоматизация запуска тестов и использование контейнеров

Примерно через год, в 2020 г., мы переняли опыт других команд и внедрили в наш проект автоматизированную установку изменений в базу данных с использованием bat-файлов командной строки. Этот процесс включал следующие шаги:

  • установка на тестовый сервер изменений, связанные с задачей;

  • накатывание тестовых пакетов и mock-таблицы;

  • запуск тестов на выполнение.

Еще через год в проекте появились Oracle PDB-контейнеры, которые можно было клонировать с продакшн-окружения для разработки под каждую задачу – это значительно упростило изоляцию тестовых сред. К 2022 г. мы внедрили в проект более продвинутые CI/CD-решения с использованием Bamboo и Python-скриптов, что позволило полностью автоматизировать процесс запуска тестов, и разработчики получили возможность запускать их независимо друг от друга в своих PDB-контейнерах.

Сдвиг с мертвой точки: возврат к тестам

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

У команды возник закономерный вопрос: «Стоит ли тратить время на написание юнит-тестов для базы данных в легаси-системе, которую, вероятно, придется полностью перерабатывать?» После обсуждения было принято решение не тратить ресурсы на тесты в текущей системе, а сделать их уже в новой системе в процессе реинжиниринга.

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

В итоге команда решила возобновить активность по написанию тестов. При разработке нового функционала мы стали выделять время для покрытия кода БД тестами. Постепенно, шаг за шагом, количество юнит-тестов росло. В процессе работы мы также пришли к выводу, что использование реальных данных системы для подготовки тестов – не самое лучшее решение. Это снижало надежность тестов, т.к. данные могли изменяться или удаляться. Вместо этого мы начали активно использовать mock-данные и более тщательно подходить к подготовке тестовых сценариев.

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

Вопрос об источниках тестовых данных  

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

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

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

Позже я наткнулся на похожую идею у Владимира Хорикова в «Принципах юнит-тестирования» (о книге я узнал от нашего QA-департамента). «Принципы», на мой взгляд, являются отличным руководством по юнит-тестам. В них подробно рассматриваются такие ключевые аспекты, как:  

  • стили тестирования и школы юнит-тестов;  

  • что делает юнит-тест хорошим;  

  • интеграционное тестирование и моки;  

  • тестирование базы данных.  

Реализация метода фабрик данных в легаси-системе

Итак, почти каждый тест, где нужно было создать объекты для работы с магазином, у меня предподготавливал данные, вызывая методы вставки магазина, добавления к нему этажа, ТО, ПП, участков зон и несколько справочников при необходимости. Вызывая одни и те же методы фабрики, я понял, что их можно успешно сгруппировать в общие методы, которые лаконично смотрятся в коде теста и выглядят примерно так:  

  • создай магазин и связанные объекты (с параметрами);  

  • подготовь данные для тест-кейса;  

  • выполни метод;  

  • проверь результат;  

  • подготовь данные для следующего тест-кейса;  

  • выполни метод;  

  • проверь результат.  

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

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

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

Эффективность фабрик данных

Когда я только начал писать фабрики, мне казалось, что их потребуется огромное количество. Однако, как оказалось, для покрытия тестами 60% системы достаточно не более 100 небольших методов для создания, связки и взаимодействия объектов. Если разрабатывать их постепенно, в процессе работы над проектом, то это не требует чрезмерных усилий.  

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

Итоги  

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

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

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

 

Теги:
Хабы:
Всего голосов 5: ↑5 и ↓0+5
Комментарии0

Публикации

Информация

Сайт
см-лаб.рф
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия
Представитель
Алина Айсина