Статьи

Решить проблемы внешнего ключа в тестовых данных DBUnit

Если вы создаете небольшие наборы данных для каждого теста, как советует DBUnit , вы будете периодически получать сбои при сборке из-за нарушений внешнего ключа. В этом посте объясняется (1), почему это происходит, (2) почему небольшие наборы данных для каждого теста все еще являются хорошей идеей, и (3) один простой способ обойти проблему.

NB Когда я искал решения этой проблемы, я обнаружил, что другие виды проблем с внешним ключом приходят с DBUnit. Некоторые люди имеют циклические зависимости в своих схемах реляционных баз данных, что мешает DBUnit загружать тестовые данные. Если это ваш случай, мне жаль говорить, что этот пост не поможет вам в этом, и ваш лучший вариант — просто выйти на улицу и застрелиться. (Хотя некоторые люди предпочитают отключать проверку внешнего ключа во время тестовых прогонов.)

Что вызывает нарушения внешнего ключа

Причина проблемы проста и проиллюстрирована тривиальным примером. Предположим, у вас есть два класса сущностей, HitchHiker и SpaceShip. Таблица HitchHiker имеет внешний ключ, который ссылается на SpaceShip. Тестовые данные для HitchHikerDaoTest содержат строки из обеих таблиц, тогда как тестовые данные для SpaceShipDaoTest содержат только строки из SpaceShip.

Операция установки по умолчанию DBUnit, CLEAN_INSERT, стирает данные из каждой таблицы в наборе тестовых данных и затем вставляет строки, перечисленные в этом наборе данных. Когда запускается SpaceShipDaoTest, DBUnit запускается путем удаления всего из таблицы SpaceShip. Если какие-либо автостопщики в настоящее время едут на космических кораблях, которые собираются удалить, база данных будет возражать против их несвоевременного выселения (хотя я не уверен, что сообщение об ошибке будет читаться как стихи Вогона).

Если вы начнете с пустой базы данных и выполните SpaceShipDaoTest, а затем HitchHikerDaoTest, все будет в порядке; но если вы сделаете это в другом порядке, ваша сборка не удастся. Это ошибка второго рода, непредсказуемая, поскольку вы (обычно) не указываете порядок выполнения тестов. В конце концов, они должны быть независимыми! Таким образом, вы вполне можете обнаружить, что у вас нет проблем в течение нескольких месяцев подряд, пока однажды вы не получите ошибку при выполнении отдельных тестов в определенной последовательности или пока Maven не изменит порядок запуска ваших тестов на сервере CI, а BOOM!

Почему вы все еще должны использовать небольшие независимые наборы данных

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

Как избежать проблемы внешнего ключа с небольшими независимыми наборами данных

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

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

private IDataSet loadTestDataWithDtdTableList(String dtdFilename) throws IOException, DataSetException, SQLException {     Reader dtdReader = new FileReader(new ClassPathResource(dtdFilename).getFile());    IDataSet dtdDataset = new FlatDtdDataSet(dtdReader);    FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();    builder.setMetaDataSet(new DatabaseDataSet(dbUnitConnection, false));    IDataSet xmlDataset = builder.build(asFile(xmlFilename));    return new CompositeDataSet(dtdDataset, xmlDataset);}

Как это работает: DBUnit предоставляет средство для загрузки набора данных из DTD. Этот набор данных содержит все таблицы, перечисленные в DTD, но, конечно, пустые данные. Набор данных DTD затем объединяется с FlatXmlDataSet, представляющим ваши тестовые данные. На рисунке ниже показан составной набор данных, который будет создан для примера SpaceShip.

Схема составного набора данных из XML и DTD

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

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

NB. Более подробный пример кода приведен в следующем фрагменте Gist базового класса для тестов TestNG + Spring + DBUnit , в котором описанная выше операция установки DBUnit добавляет к вспомогательному классу SpringN TestNG.

Удачного тестирования базы данных!

С http://www.andrewspencer.net/2011/solve-foreign-key-problems-in-dbunit-test-data/