В первой части этой небольшой серии я показал простой тест, основанный на простой модели данных. Как мы увидели, код для теста был совсем не простым, а довольно уродливым. Мы начали исправлять это, выдвигая все правила обработки SessionFactory и Session в правило . Оставив нам с тестовым кодом, как это:
public class SuperHeroRepository3Test { @Rule public final SessionFactoryRule sf = new SessionFactoryRule(); @Test public void returnsHerosWithMatchingType() { Session session = sf.getSession(); SuperPowerType powerType = createSuperPowerType(); session.save(powerType); SuperPower superpower = createSuperPower(powerType); session.save(superpower); SuperHero hero = createSuperHero(superpower); session.save(hero); sf.commit(); SuperHeroRepository heroRepository = new SuperHeroRepository(session); List<SuperHero> heroes = heroRepository.loadBy(superpower); assertNotNull(heroes); assertEquals(1, heroes.size()); } private SuperHero createSuperHero(SuperPower superpower) { SuperHero hero = new SuperHero(); hero.name = "Name"; hero.power = superpower; hero.weakness = "None"; hero.secretIdentity = "Mr. Jones"; return hero; } private SuperPower createSuperPower(SuperPowerType powerType) { SuperPower superpower = new SuperPower(); superpower.name = "SuperPower"; superpower.description = "Description"; superpower.type = powerType; return superpower; } private SuperPowerType createSuperPowerType() { SuperPowerType powerType = new SuperPowerType(); powerType.name = "TheType"; powerType.description = "12345678901234567890aDescription"; return powerType; } }
Поскольку мы не будем удовлетворены ни одним тестом, мы не будем повторно использовать методы createXXX. Начальная идея о том, как это сделать, исходит от Мартина Фаулера. Он описывает ObjectMothers как объекты, которые порождают сложные объекты, необходимые для тестирования. Это именно та информация, в которой мы находимся. Поэтому методы createXXX должны идти в ObjectMothers. Как вы можете видеть в текущей версии теста, ObjectMother должен сделать две вещи:
- Создайте экземпляры класса, у которых все атрибуты заполнены таким образом, чтобы удовлетворить ограничения, присутствующие в базе данных.
- сохраните их в спящем режиме.
Итак, вот очень простая версия такой ObjectMother:
public class SuperPowerTypeMother { private final Session session; public SuperPowerTypeMother(Session s) { session = s; } public SuperPowerType instance() { SuperPowerType powerType = new SuperPowerType(); powerType.name = "TheType"; powerType.description = "12345678901234567890aDescription"; session.save(powerType); return powerType; } }
Это используется ObjectMother для SuperPowers:
public class SuperPowerMother { private final Session session; public SuperPowerMother(Session s) { session = s; } public SuperPower instance() { SuperPower superpower = new SuperPower(); superpower.name = "SuperPower"; superpower.description = "Description"; superpower.type = new SuperPowerTypeMother(session).instance(); session.save(superpower); return superpower; } }
Наконец добавьте похожую SuperHeroMother, и наш тест будет выглядеть так:
public class SuperHeroRepository4Test { @Rule public final SessionFactoryRule sf = new SessionFactoryRule(); @Test public void returnsHerosWithMatchingType() { SuperHero hero = new SuperHeroMother(sf.getSession()).instance(); sf.commit(); SuperHeroRepository heroRepository = new SuperHeroRepository( sf.getSession()); List<SuperHero> heroes = heroRepository.loadBy(hero.power); assertNotNull(heroes); assertEquals(1, heroes.size()); } }
Это мило. Коротко и сосредоточено на том, что мы хотим проверить. Только сеанс намекает на наличие базы данных. Также, когда наши объекты получают новые атрибуты, у нас есть единственное место, чтобы установить значение по умолчанию для этого атрибута для всех наших тестов. Все, что осталось в тесте для создания SuperHero, это один лайнер, подобный этому:
-
SuperHero hero =
новая SuperHeroMother
( sf.
GetSession
(
)
) .
instance
(
)
;
Так мы закончили? Нет, потому что у ObjectMothers есть две серьезные проблемы:
- они создают новые объекты каждый раз, что приведет к уникальным нарушениям ограничений, когда вы будете иметь дело с более чем одним SuperHero
- они каждый раз устанавливают атрибуты одинаково, но рано или поздно вам понадобится герой со специальной сверхдержавой, поэтому мы хотим указать некоторые интересующие нас атрибуты, не беспокоясь о других.
Для решения первой проблемы матери должны посмотреть в базе данных, если экземпляр, как запрошенный, уже существует. Для этого используется альтернативный ключ объекта, и только если выбор с этим альтернативным ключом не возвращает объект, создается новый. Обратите внимание, что поскольку каждая сущность сохраняется в сеансе до того, как она покидает мать, каждый такой выбор поиска вызывает сброс сеанса и, следовательно, видит ранее созданные сущности.
Для установки атрибутов сущностей используется шаблон строителя. У матерей есть метод, названный в честь атрибута, который принимает значение атрибута и возвращает экземпляр матери.
Так что SuperHeroObjectMother теперь выглядит следующим образом.
public class SuperHeroMother { private final Session session; private String secretIdentity = "Mr. Jones"; private String name = "Name"; private String weakness = "None"; private SuperPower power = null; public SuperHeroMother(Session s) { session = s; power = new SuperPowerMother(session).instance(); } public SuperHero instance() { SuperHero hero = loadInstance(); if (hero == null) { hero = createInstance(); } hero.power = power; hero.weakness = weakness; hero.secretIdentity = secretIdentity; session.save(hero); return hero; } private SuperHero loadInstance() { return (SuperHero) session.createCriteria(SuperHero.class) .add(Restrictions.eq("name", name)).uniqueResult(); } private SuperHero createInstance() { SuperHero hero = new SuperHero(); hero.name = name; return hero; } public SuperHeroMother name(String aName) { name = aName; return this; } public SuperHeroMother secretIdentity(String aSecretIdentity) { secretIdentity = aSecretIdentity; return this; } public SuperHeroMother power(SuperPower aPower) { power = aPower; return this; } public SuperHeroMother weaknes(String aWeakness) { weakness = aWeakness; return this; } }
Использовать такой объект mother очень просто: создайте экземпляр mother, установите интересующие вас атрибуты и вызовите instance ().
Но написание «Матери» немного утомительно, и если это сделано, как показано выше, это приводит к большому дублированию кода. Поэтому в следующей части серии мы проведем рефакторинг, чтобы удалить хотя бы часть дублирования кода.
Из http://blog.schauderhaft.de/2011/03/20/testing-databases-with-junit-and-hibernate-part-2-the-mother-of-all-things/