Пролог

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


О статье и общая проблематика

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

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

  • на проекте присутствует БД, данные которой являются точкой применения вышеуказанной “серой логики

  • сам проект уже в production

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

Согласитесь, не так, чтобы эти условия были какой-то редкостью).

Погружаемся глубже в вопрос - “Зачем”?

Перед тем как ответить на вопрос “что же нам с этим делать?”, давайте поглубже копнем в сам вопрос - “Зачем нам вообще что-то с этим делать?”.

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

Например

В БД SQL у нас есть отражение погодных явлений. Данные по разным метрикам о погодном явлении приходят из нескольких источников: источник № 1 передает все что связано с температурой, источник № 2 - с ветром, источник № 3 - давлением.  И каждый передает статус явления: начинается, идет, завершен.

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

Ограничения же на уровне БД не предусматривают того, что например поставщик № 2 может внезапно вести явление в рамках 2х внутренних событий, в которых по отдельности будет передавать, в одном данные по скорости ветра, а во втором по скорости и по силе ветра. Первое событие оператор завел по ошибке, а второе уже - исправление ошибки с добавлением недостающих метрик. На какое из них ориентироваться если нам нужно использовать актуальные данные? Тут то и хорошо это выявить еще на этапе тестирования аналитики, то есть еще до начала разработки (или хотя бы тестирования реализации).

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

Например

У пользователя ценник за покупку ниже ожидаемого или кэшбек выше ожидаемого

Предположим, что при наличии разных поставщиков данных нет полной уверенности (а когда она была в нашей-то профессии:-)) в том, что обработка данных от одного из них не приведет к резкому увеличению затрат ресурсов

Например

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

Что будет, когда где-то запустят крупный международный фестиваль по этому жанру?

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

Как справиться с подобными кейсами на проекте?

Единственное место, где мы можем проверить: “а действительно ли такое когда либо было, а если было, то когда и как часто”, - это БД на production окружении. В связи с этим речь пойдет именно о валидации уже имеющихся на production данных с целью (нашей извечной) найти отклонения между ожиданием и реальностью.

Что нам необходимо понимать прежде чем работать с БД на production окружении? 

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

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

  3. наши действия не должны заафектить производительность БД

Рекомендую, перед применением каких-либо скриптов в production окружении, уточнить безопасность применения этих скриптов у Database administrator (с учетом вышеописанных критериев).

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

  • Тесты ссылочной целостности (Referential Integrity)

  • Тесты уникальности (Uniqueness tests)

  • Струнные узоры (String patterns)

Что же нам стоит взять на вооружение? Из своего опыта, могу выделить те проверки, которые хорошо вписываются в наши задачи (Software QA), но сначала опишем "легенду" БД на вымышленном проекте, чтобы изучая примеры можно было проще погрузиться в тот или иной случай.

Легенда

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

Таблица unified_fest (фестивали на которые будут поставляться продукты питания) с колонками:

id- первичный ключ

name- имя фестиваля 

start_date- дата начала фестиваля

end_date- дата конца фестиваля

Таблица technical_supply (технические поставки продуктов на фестивали) с колонками:

id - первичный ключ

unified_fest_id - внешний ключ принадлежности поставки к фестивалю (таблица unified_fest)

is_active_state - активность технической поставки

contractor - поставщик

start_date - дата начала поставки

end_date - дата конца поставки

Таблица product_sets (категории продуктов, поставляемые в рамках технической поставки) с колонками:

id - первичный ключ

category_type_id - идентификатор категории продукта по внутреннему справочнику

Таблица products (продукты, поставляемые в рамках технической поставки) с колонками:

id - первичный ключ

product_set_id - внешний ключ к таблице product_sets 

product_type_id - тип продукта по внутреннему справочнику

trade_category_id - категория продукта по классификации регулятора (также по справочнику)

name - название продукта/товара 

price - цена продукта/товара 

technical_supply_id - внешний ключ к таблице technical_supply

Проверки данных

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

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

Пример + SQL скрипт

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

WITH valid_product_sets AS (
    SELECT p.product_set_id
    FROM products p
    JOIN technical_supply ts ON p.technical_supply_id = ts.id
    WHERE ts.is_active_state = TRUE
    GROUP BY p.product_set_id
    HAVING COUNT(DISTINCT p.technical_supply_id) > 1
)
SELECT DISTINCT ON (p.product_set_id, p.technical_supply_id) p.id, p.technical_supply_id, ts.contractor, ts.is_active_state, p.product_set_id
FROM products p
JOIN technical_supplies ts ON p.technical_supply_id = ts.id
JOIN valid_product_sets vps ON p.product_set_id = vps.product_set_id
WHERE ts.is_active_state = TRUE
ORDER BY p.product_set_id, p.technical_supply_id, p.id
LIMIT 1000;

Разберем пример скрипта:

В начале опишем CTE valid_product_sets, где соединим продукты с техническими поставщиками по ключам p.technical_supply_id = ts.id после чего отсеем только активные технические поставки WHERE ts.is_active_state = TRUE, сгруппируем по категории продукта  GROUP BY p.product_set_id и оставим только те, где найдено более одной записи HAVING COUNT(DISTINCT p.technical_supply_id) > 1.

Основная работа выполнена, осталось сократить полученные данные, выбрав только то, что нам нужно. В последующем SELECT мы берем продукты, снова соединяем с таблицей тех поставок по ключам, после чего оставляем только те записи, по которым есть совпадение в нашем CTE с помощью JOIN valid_product_sets vps ON p.product_set_id = vps.product_set_id. Оставляем по одной строке для каждой пары значений product_set_id, technical_supply_id с помощью DISTINCT ON (p.product_set_id, p.technical_supply_id). Остальное это набор различных удобств для чтения (поля, сортировка и ограничение на количество вхождений).

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

2. наличие сущностей со связями между таблицами, которые противоречат ожидаемой логике

Пример

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

При формировании скрипта можно использовать конструкции SQL CASE Expression, в которых можно описать правила соответствия между products.product_type_id и product_sets.category_type_id.

3. наличие пустых (""/null) значения в ячейках, где это запрещено только бизнес логикой, и не запрещено ограничениями на уровне БД

Пример + SQL скрипт

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

SELECT *
FROM unified_fest
WHERE name IS NULL
   OR TRIM(name) = '';

4. аномальное количество записей для какой-нибудь объединяющей сущности в со-зависимой таблице

Пример + SQL скрипт

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

SELECT 
    uf.id,
    uf.name,
    ts.contractor,
    COUNT(ts.id) AS supply_count
FROM unified_fest uf
JOIN technical_supply ts 
      ON ts.unified_fest_id = uf.id
GROUP BY 
    uf.id,
    ts.contractor
HAVING 
    COUNT(ts.id) > 100;

5. наличие записей меньше нуля там, где это недопустимо

Пример + SQL скрипт

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

SELECT * FROM products WHERE price <= 0;

6. разсогласованность связанных дат

Пример + SQL скрипт

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

SELECT ts.*
FROM technical_supply ts
JOIN unified_fest uf 
    ON ts.unified_fest_id = uf.id
WHERE ts.start_date > uf.end_date;

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

Заключение

Подобные проверки можно проводить перед реализацией новой логики, которая опирается на сомнительные парадигмы поведения сущностей, подверженных влиянию “серой логики”. Также проверки можно включить в рамки пост релизных мероприятий, выполняемых после деплоя очередного инкремента в production (через какой-то временной период). Особенно полезно это на тех проекта, где идет активная работа с легаси логикой.

Подведем итог, мы имеем выделенный процесс тестирования со строго определенным набором атрибутов:

  • исследуемые объекты - данные из БД

  • окружение источника данных - production

  • ограничения для операций - минимальное влияние на источник данных

  • цель - поиск отклонений в поведении ранее реализованного функционала, при отсутствии уверенного понимания логики работы этого самого функционала

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

Комментарии (0)