В первой части этой небольшой серии я показал простой тест, основанный на простой модели данных. Как мы увидели, код для теста был совсем не простым, а довольно уродливым. Мы начали исправлять это, выдвигая все правила обработки 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/