Статьи

Тестирование баз данных с помощью JUnit и Hibernate. Часть 2. Мать всего

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


  1. 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/