Вы, модульное тестирование и базы данных
Одна из самых часто встречающихся жалоб от разработчиков и конечных пользователей касаются багов. Когда кто-то размышляет об этих ошибках, одна из главных причин их сводится к «неизвестному». Вы можете указать пальцем на требования, конфигурацию, дизайн и т. д., но очень часто все эти элементы сводятся к…
- Я не знал, что это должно быть требованием
- Почему никто не подумать поговорить с администратором базы данных о том, как эта база данных была настроена?
- Почему мы построили такие отношения в таблице?
- Кто бы мог подумать, что Карл выиграет в лотерею и покинет компанию?
Эти проблемы и вопросы не могут быть решены с помощью инструмента, который обычно является первым, за которым идут люди. Мы хотели бы поговорить о том, как модульное тестирование может заставить вас задуматься таким образом, чтобы это стимулировало больше дискуссий и, надеюсь, ранее выявляло причину некоторых из этих ошибок.
Мы не собираемся утомлять вас определением того, что такое юнит-тестирование. (Вот ссылка на модульное тестирование Википедии, если вы хотите узнать об этом больше). К чему сводится этот вопрос: делает ли мой код то, что, он должен делать по задумке? Модульное тестирование разбивает требования, явно определенные или подразумеваемые тем, как определен ваш код. Вы можете вступить в дискуссию по различным методологиям тестирования (Test Driven, Behavior Driven и т. д.), Но мы собираемся обсудить это в следующий раз. Основное внимание здесь уделяется точности кода. Если требуется написать код для расчета возраста человека, будет ли он возвращать возраст человека? На первый взгляд, это довольно простой запрос, особенно в SQL. Однако как насчет причуд, которые живут глубже в этом запросе? Будет ли работать код, если кто-то введет неверный формат даты? Будет ли работать код, если кто-то введет дату в будущем? Как насчет того, если человек родился в 1800-х годах? Это типы вопросов, на которые можно ответить с помощью модульного тестирования. Другой аспект заключается в том, что он гарантирует, что, если кто-то изменит ваш код, он все равно будет работать так, как задумано. Сколько раз вы слышали, как кто-то говорил: «О, это легкая перемена», и как только произошла перемена, наступает хаос? В то время как модульные тесты не могут обеспечить абсолютную уверенность, они могут значительно помочь вам убедиться, что когда что-то изменяется, оно выдаст ошибку, если оно не будет работать так, как изначально было разработано.
Как работает модульное тестирование
Итак, как это сделать? Модульное тестирование - это способ ответить на эти вопросы и представить их в небольших, быстрых и понятных тестах, которые будут отвечать на каждый из этих вопросов независимо друг от друга и независимо от чего-либо, кроме написанного вами кода. Прежде чем перейти к сложностям модульного тестирования, вот несколько основных принципов:
- Скорость - модульные тесты не могут занимать много времени. Чем дольше все выполняется, тем больше времени требуется для написания кода, поскольку разработчику придется ждать, чтобы узнать, нужно ли исправление.
- Повторяемость - модульные тесты должны быть выполнены несколько раз с легкостью, чтобы вы могли внести коррективы в код и повторно запустить тест, чтобы определить, было ли выполнено требование
- Самоконтроль - модульные тесты должны приводить к единственному двоичному результату. Модульные тесты пройдены или не пройдены; нет серой зоны
- Своевременность - модульные тесты не могут занимать непропорционально много времени для написания по сравнению с тестируемым кодом
- Изоляция - модульные тесты не могут полагаться на внешние данные, объекты, людей или процессы
Почему эти принципы важны? Уточним, что они не просто важны, они важны для модульного тестирования базы данных. Задайте себе эти вопросы:
- Если для выполнения модульных тестов требуются часы, действительно ли разработчики найдут смысл в их запуске, если они сидят и ждут завершения теста, а не кодирования?
- Если модульному тесту требуются часы для очистки данных и повторного запуска данных для выполнения теста, действительно ли разработчики приложат усилия, чтобы определить, как это сделать, просто чтобы пройти некоторые тесты?
- Если результат модульного теста нужно интерпретировать по сложным сценариям или требовать каждый раз получать интерпретации для конечных пользователей, захотят ли разработчики тратить время на это вместо кодирования?
- Если для написания юнит-теста требуются дни, чтобы подтвердить, что число не является буквой, увидят ли разработчики какое-либо значение для написания юнит-теста?
- Если юнит-тест должен полагаться на доступность кого-то другого или ждать, пока поставщик предоставит данные, которые не будут готовы в течение нескольких недель, захочет ли разработчик попросить его набраться терпения и найти что-то еще, что можно сделать?
Ответы на все эти вопросы должны быть «нет». Разработчики должны быть в состоянии выполнять свою работу и не подвергаться влиянию таких ситуаций. Модульное тестирование должно учитывать эти сценарии.
Способ, которым модульное тестирование достигает целей этих основ, - использование фреймворков. Как и любая другая технология, фреймворк предоставляет функциональные возможности и шаблоны для решения проблем. В частности, для баз данных существует несколько платформ на выбор. Существуют последствия, которые зависят от выбранной вами структуры. Одним из последствий является язык, на котором написаны тесты. Тесты в некоторых средах написаны на SQL или C #, в то время как другие могут использовать свой собственный язык. Это повлияет на кривую обучения, необходимую для начала написания тестов, и должно основываться на наборе навыков людей, которые будут отвечать за написание и ведение тестов.
Вы также должны посмотреть, как фреймворк взаимодействует с другими технологиями, в частности, как он интегрируется с такими целями, как постоянное улучшение / непрерывное развертывание и DevOps. Если инструмент не имеет возможности интегрироваться в ваш инструмент, вам может потребоваться разработка собственной интеграции между системами, чтобы эта работа работала. Принимая во внимание, что другая структура может работать с такими стандартами, как MS Unit или NUnit, которые широко используются многими платформами CI / CD.
Некоторые из общих функций, которые должны быть включены в платформу, - это функциональности заглушки/макета/фейка, утверждений и тестовых наборов. Первая функциональность относится к способности инфраструктуры тестирования создавать объекты базы данных, которые напоминают объект, который тестируется. Например, подделка таблицы базы данных позволила бы существовать таблице, которая напоминает всю или хотя бы часть схемы исходной таблицы. Это позволяет писать модульные тесты изолированно от исходного объекта и изолировать от существующих данных, которые могут существовать в таблице. Утверждения - это предопределенные методы, которые используются для предоставления двоичных результатов для условий тестирования. Это методы, которые обеспечивают сравнение объектов, сравнение данных или сравнение ожидаемых результатов.
Последним элементом будет функциональность наборов тестов, то есть возможность группировать тесты, которые оценивают одни и те же объекты вместе, так что тест может быть выполнен быстро и предоставить двоичный результат, чтобы указать, что объектные модульные тесты все пройдены или если произошел сбой, который указывает на то, что объект не подходит для развертывания. Эти функции необходимы для разработки модульных тестов, что станет очевидным, когда вы начнете писать модульные тесты.
Модульное тестирование и базы данных
Пока что обсуждение было сосредоточено вокруг некоторых основ о модульном тестировании и некоторых последствиях, которые они имеют для баз данных. Это на самом деле не дало никакого представления о том, почему это важно для развития баз данных. Мы не будем долго останавливаться на этом, полагая, что все понимают ценность модульного тестирования. Итак, вспомните время, когда вы написали свою последнюю хранимую процедуру. Вам, вероятно, дали какое-то требование, или, по крайней мере, кто-то дал вам неопределенную просьбу о чем-то. Хранимая процедура, вероятно, использовала таблицы с внешними ключами между таблицами, которые были задействованы и не связаны с кодом, который вы должны были написать, чтобы получить нужные вам результаты. Кроме того, данные в таблицах не содержат всех данных, чтобы поддерживать возможность просмотра результатов запроса, чтобы проверить, работает ли ваша хранимая процедура. Имея это в виду, подумайте, как вы могли бы подойти к разработке этой хранимой процедуры. У вас обычно есть несколько вариантов:
- Вы можете использовать свой опыт и построить запрос как можно лучше, не имея никаких вспомогательных данных или только данных, которые вы на данный момент располагаете
- Вы можете потратить кучу времени, пытаясь обновить или вставить данные в таблицы, чтобы поддержать ваш сценарий
- Вы можете попытаться построить таблицы в экспериментальной среде, которая очень похожа на исходные таблицы с данными, которые вам нужны
- Вы можете попросить кого-нибудь загрузить данные в базы данных, которые отвечают всем требованиям сценария, которые должна использовать ваша хранимая процедура.
У каждого из этих подходов есть риски и последствия. В варианте №1, если вы строите процедуру без данных, вы не будете уверены, что в вашем коде нет ошибок или опечаток. В варианте № 2 вы потратите время на изучение и копирование данных, которые могут привести к появлению вспомогательных таблиц, которые необходимо обновить, чтобы удовлетворить ограничения. Вы также рискуете создать данные в базе данных, которые не соответствуют другим стандартам или состояниям данных, что приводит к ошибкам или ложным ошибкам и потенциальной нестабильности. В варианте № 3, если вы строите что-то в экспериментальной среде, существует риск того, что вы не создадите объекты таким образом, чтобы он достаточно близко напоминал окружение и ваш дизайн мог быть испорчен. У вас также могут возникнуть проблемы, когда перемещение кода из экспериментальной среды в область разработки может привести к конфликтам или опечаткам. Наконец, в варианте № 4 это может занять много времени, так как вам может потребоваться подождать, пока пользователи создадут данные, им может потребоваться больше информации, чтобы собрать все необходимые данные для удовлетворения системных требований, или у них могут быть конкурирующие запросы, которые задержат ваше развитие. Другая проблема, которую имеют все четыре из этих сценариев, заключается в том, что их нелегко повторить и они требуют отдельной документации, чтобы другие разработчики или тестировщики могли их поддерживать и выполнять.
Так как же это касается модульного тестирования? Один из наиболее эффективных способов решения этой проблемы в модульных тестах - использование поддельных/фиктивных/ложных объектов. Поскольку данные оказывают непосредственное влияние на наши разработки и управляют ими, способность создавать собственные тестовые данные по желанию позволяет нам писать тесты, которые обеспечивают правильную работу нашего кода. В сценарии, описанном ранее, таблицы, которые участвуют в хранимой процедуре, могут быть поддельными. Это означает, что во время выполнения теста и только во время выполнения теста создается таблица с тем же именем, которое структурно идентично таблице, используемой в хранимой процедуре. Важным отличием является то, что таблицы, созданные в ходе этого процесса, не содержат никаких данных и не имеют внешних ключей или каких-либо других ограничений. Это предоставляет все виды возможностей, потому что вы можете заполнить поддельную таблицу только теми данными, которые необходимы для проверки условий процедуры. Вам не нужно заполнять ненужные внешние ключи, вам не нужно заполнять ненужные поля в таблице, которые не используются хранимой процедурой, и, как упоминалось ранее, она полностью автоматизирована. Это означает, что существующие данные и ограничения будут быстро перемещены, заменены данными, необходимыми для теста, а затем, когда тест завершен, предыдущие данные и ограничения будут перемещены обратно в таблицу, и с таблицей совсем ничего не случится. Это делает весь процесс разработки более простым и бесконечно легким для проверки того, что происходит то, что нужно. Это очень простой пример того, как фейковая функциональность может стать огромным преимуществом для модульного тестирования. Другими сценариями может быть подделка функции, используемой в хранимой процедуре, хранимой процедуры в хранимой процедуре или представления базы данных, используемого в хранимой процедуре. Одна только эта возможность сэкономит ваше время и сделает написание тестов очень простым!
Вы, вероятно, говорите себе: «Почему бы не всем писать модульные тесты, если бы это было так просто»? Ответы на эти вопросы могут быть разными, но скорее всего, в основном это связано с отсутствием инструментов для включения базы данных в жизненный цикл разработки. Эта нехватка инструментов определенно меняется из-за появления DevOps, Agile, CI/CD и других методологий, которые стремятся к автоматизации, итеративной разработке и постоянному улучшению. Если вещи часто меняются, вы не можете тратить часы или дни на регрессионное тестирование. Это отсутствие инструментов создало культуру исключения. Я слышал такие вещи, как «о, мы не можем этого сделать, потому что это в таблице» или «что функция, которую мы вызываем в базе данных, она просто работает». Такие заявления почему-то давали возможность разработки базы данных долгое время, но теперь, когда данные лежат в основе такого большого количества решений, необходимо иметь уверенность в том, что спроектировано и разработано.
Кроме того, можно услышать много таких же оправданий:
Оправдание |
Решение |
Код хранимой процедуры слишком сложен для проверки. |
Если он так сложен, возможно, его следует сделать приемлемого размера. Никому не нравится гигантская хранимая процедура, которая занимает часы, чтобы прочитать и понять, что происходит. |
Тест, который нужно написать для этого, слишком сложен. |
Если это так сложно, тогда то, что вы написали, вероятно, также слишком сложно и должно быть упрощено. |
Я не знаю, как написать тест. |
Разработка программного обеспечения это изучение нового. Когда появляются новые вещи, нам нужно учиться изучать их и использовать их в наших процессах, не игнорируя их и надеясь, что они исчезнут. |
У меня нет времени, чтобы написать тест. |
Так или иначе, вы найдете время. Либо что-то сломается в какой-то момент, и вам нужно будет найти время, чтобы определить, что сломано, и исправить это, когда все будут наблюдать за вами (включая генерального директора / вице-президента / директора), либо вас уволят, а затем у вас будет куча свободного времени. |
Я не знаю, какие тесты написать. |
Это отдельная тема, так как есть несколько различных методов для определения того, какие тесты писать. Вы всегда можете начать с обсуждения требований с бизнесом, тестировщиками или аналитиками, чтобы определить, какие тесты необходимы. |
Заключение
Из этого важно извлечь уроки из того, что модульное тестирование важно, и в некотором роде его форма или форма должны быть включены в практику разработки. Неважно, являетесь ли вы единственным сотрудником базы данных в вашей компании или если в вашей компании 5000 человек. Неважно, если у вас есть одна база данных или 1000 баз данных. Применение модульного тестирования позволяет вам убедиться, что после того, как вы что-то напишете, его можно будет проверить, а затем, когда потребуется изменить, вы сможете проверить, что все ранее пройденные тесты будут продолжать проходить. Это даст вам уверенность в том, что ваши изменения будут более точными и с меньшей вероятностью сломают другие вещи, которые зависят от вашего кода. Это неизбежно поможет вам спать по ночам, больше наслаждаться отдыхом и быть более уверенным в том, что вы развиваете.