Search
Write a publication
Pull to refresh
2
0

User

Send message

С одной стороны, Confluence приведен в качестве примера:

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

Можно использовать и другие Wiki-системы для расширения системы тестовой документации.

С другой стороны, да, если произойдет миграция только документов из Wiki-системы, какой бы она ни была, но не тест-кейсов из TMS, то нужно будет переделывать все связи. Гипотетически, этого можно избежать, если налаживать связи исключительно внутри TMS, но, мне кажется, едва ли найдется такой инструмент, где можно будет удобно и без костылей это сделать. Так или иначе, хочется иметь возможность распределять информацию по разным объектам (артефактам), чтобы переиспользовать их.

в Аллюр

Имеете в виду, Allure Testops? Если да, не работал с ним, к сожалению. Можно, получается, прямо в интерфейсе отобразить, например, содержимое файла .sql и скопировать его?

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

Спасибо вам! :)

у меня это одна из задач немаленького приложения

А вы как проверяете?)

В комменте выше описал, почему в моем случае этот запрос оказался полезен. Возможно, эта информация подойдет в качестве ответа.

А где гарантия, что если вы забыли колонку при создании таблицы, вы не забудете её в тестах

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

Это правда, DDL написать проще. В моем случае удобство использования такого запроса связано с тем, что:

  1. Требования к таблицам описываются аналитиком в виде отдельной таблицы. DDL при этом отсутствует;

  2. Новые таблицы, описанные в требованиях, добавляются по согласованию либо аналитиком, либо разработчиком;

  3. Бывает так, что добавленные новые таблицы правят в БД, но вместе с тем не обновляют таблицу с требованиями в Wiki. Отсюда получается расхождение;

  4. Со своей стороны я, будучи QA, не завожу новые таблицы, но могу проверить их на соответствие требованиям. Это можно сделать глазами, открыв два окна и сравнивая значения, либо через описанный запрос. С учетом того, что после внесения обновлений в требования могут быть опять, к примеру, изменения в БД, то в этом случае прогнать запрос значительно быстрее, чем проводить сравнение глазами. У нас еще и алгоритмы, являющиеся частью функциональностей проекта, могут падать из-за измененных метаданных таблиц. И проверочные запросы помогают локализовать дефект, когда в логах появляется название одной из таких таблиц

Кажется, что последний поудобнее будет.

Так?

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

Ага, есть такое. У меня по задачам не возникало такой ситуации, поэтому не думал об этом. Весомое и дельное замечание. Название таблиц, на которое направлено ограничение, а также схемы, в которых хранятся эти таблицы, можно вытащить по запросу в CTE constraints_query. Однако этого недостаточно для корректной работы запроса, нужно также доработать логику сравнения ограничений. Оставлю здесь исправленный вариант для описанного в статье примера:

Исправленный код
with
    -- Настраиваем запрос: задаем название таблицы и название схемы
    params(table_name, table_schema) as (
        values (
            'price',
            'public'
        )
    ),
    -- Описываем ожидаемые столбцы таблицы
    required_columns(column_name, data_type, is_nullable, character_maximum_length) as (
        values            
            ('id',          'bigint',               'NO', 	null),
            ('price_value', 'numeric',              'NO', 	18.6),
            ('model_id',    'integer',  		    'NO',	null),
            ('comment',     'character varying',    'YES',  255)
    ),
    -- Описываем ожидаемые ограничения
    required_constraints(column_name, constraint_type, constraint_table, table_schema) as (
        values            
            ('id',          'PRIMARY KEY',	'price',	'public'),
            ('id',    		'FOREIGN KEY',	'models',	'public'),
            ('model_id',    'FOREIGN KEY',	'price',	'public'),
            ('price_value', 'UNIQUE',		'price',	'public'),
            ('model_id',    'UNIQUE',		'price',	'public')
    ),
    -- Находим информацию о столбцах тестируемой таблицы и добавляем обработку null'ов
    columns_info as (
        select
            column_name, data_type, is_nullable,
            coalesce(numeric_precision, 0)          as numeric_precision,
            coalesce(numeric_scale, 0)              as numeric_scale,
            coalesce(character_maximum_length, 0)   as character_maximum_length
        from information_schema.columns
        where
            table_name       = (select table_name   from params)
            and table_schema = (select table_schema from params)
    ),
    -- Проверяем существование таблицы и подсчитываем количество столбцов в ней
    check_table_exist as (
        select
            case when count_all_fields < 1 then false else true end table_exists,
            case when count_all_fields < 1 then 1 else 0 end table_exists_error,
            count_all_fields
        from (
            select count (*) as count_all_fields
            from columns_info
        ) sq
    ),
    -- Сравниваем ожидаемый и текущий наборы атрибутов таблицы
    fields_comparison as (
        select t.*
        from columns_info t
        inner join required_columns r
            on  t.column_name   = r.column_name
            and t.data_type     = r.data_type
            and t.is_nullable   = r.is_nullable
            and (
                -- Сравниваем целую часть десятичных значений
                case
                    when t.data_type = 'numeric'
                    then t.numeric_precision = trunc(r.character_maximum_length::numeric)
                end
                and
                -- Сравниваем дробную часть десятичных значений
                case
                    when t.data_type = 'numeric'
                    then t.numeric_scale = (r.character_maximum_length::numeric - trunc(r.character_maximum_length::numeric))
                    * power(10, length(split_part(r.character_maximum_length::text, '.', 2)))
                end
                or t.character_maximum_length = coalesce(r.character_maximum_length::numeric, 0)
           )
    ),
    -- Ищем лишние столбцы и считаем их количество
    check_unexpected_fields as (
        select
            count (column_name) as count_unexpected_fields,
            string_agg(column_name, ', ') as unexpected_fields
        from (
            select column_name
            from columns_info
            except
            select column_name
            from required_columns
        ) sq
    ),
    -- Ищем недостающие столбцы и считаем их количество
    check_missing_fields as (
        select
            count (column_name) as count_missing_fields,
            string_agg(column_name, ', ') as missing_fields
        from (
            select column_name
            from required_columns
            except
            select column_name
            from columns_info
        ) sq
    ),
    -- Ищем невалидные столбцы и считаем их количество
    check_invalid_fields as (
        select
            count (column_name) as count_invalid_fields,
            string_agg(column_name, ', ') as invalid_fields
        from (
            select column_name
            from required_columns
            except
            select column_name
            from fields_comparison
            except
            select string_to_table(missing_fields, ', ')
            from check_missing_fields
        ) sq
    ),
    -- Ищем все ограничения для таблицы
    constraints_query as(
        select
            t1.constraint_type,
            t1.table_name 	as foreign_table_name,
            t1.table_schema	as foreign_table_schema,
       		t2.table_schema,
            t2.table_name,
            t2.column_name,
            t3.column_name	as foreign_column
        from information_schema.table_constraints t1
        left join information_schema.constraint_column_usage t2
            on t1.constraint_name = t2.constraint_name
        left join information_schema.key_column_usage as t3
            on t1.constraint_name = t3.constraint_name
            and t1.table_schema = t3.table_schema
        where
            t1.table_name            = (select table_name   from params)
            and t1.constraint_schema = (select table_schema from params)
            and t2.column_name is not null
    ),
    -- Включаем значения зависимых ключей (foreign_column) в список ограничений (column_name)
    union_foreign_ref_columns as (
        select distinct on (constraint_type, column_name) column_name, constraint_type, table_name, table_schema
        from constraints_query
        union all
        select foreign_column, constraint_type, foreign_table_name, foreign_table_schema
        from constraints_query
        where constraint_type = 'FOREIGN KEY'
    ),    
    -- Ищем лишние ограничения и считаем их количество
    check_unexpected_constraints as (
        select
            count (column_name) as count_unexpected_constraints,
            string_agg(concat_ws(', ', column_name, constraint_type, table_name, table_schema), E'\n') as unexpected_constraints
        from (
            select *
            from union_foreign_ref_columns
            except
            select column_name, constraint_type, constraint_table, table_schema
            from required_constraints
        ) sq
    ),
    -- Ищем недостающие ограничения и считаем их количество
    check_missing_constraints as (
        select
            count (column_name) as count_missing_constraints,
            string_agg(concat_ws(', ', column_name, constraint_type, constraint_table, table_schema), E'\n') as missing_constraints
        from (
            select column_name, constraint_type, constraint_table, table_schema
            from required_constraints
            except
            select *
            from union_foreign_ref_columns
        ) sq
    ),
    -- Собираем полученные данные
    checks as (
        select
            -- Выводим всю информацию об ошибках и суммируем их количество
            table_exists_error + count_unexpected_fields + count_missing_fields + count_invalid_fields +
            count_unexpected_constraints + count_missing_constraints as errors,
            *
        from        check_table_exist
        cross join  check_unexpected_fields
        cross join  check_missing_fields
        cross join  check_invalid_fields
        cross join  check_unexpected_constraints
        cross join  check_missing_constraints
)
select *
from checks

Спасибо вам!

Случаем, не осталось примера под рукой? Интересно было бы посмотреть)

Information

Rating
Does not participate
Location
Москва и Московская обл., Россия
Works in
Registered
Activity

Specialization

Quality Assurance Engineer
Senior
SQL
API Testing
Manual testing
Functional testing