Базы данных являются чрезвычайно важной частью почти каждого корпоративного приложения. Тем не менее, существует очень небольшая поддержка для тестирования вашей базы данных, что приводит к очень небольшому охвату тестов кода, связанного с базой данных. В отчаянной попытке изменить это хотя бы немного в серии статей, начиная с этой статьи, будут описаны некоторые проблемы и возможные частичные решения на основе Hibernate и JUnit. 
В качестве примера я использую следующий небольшой набор классов сущностей Hibernate, и из него будет создана схема базы данных Hibernate:
    @Entity
    public class SuperHero extends AbstractEntity {
     @NotEmpty
     @Column(unique = true)
     public String name;
     @ManyToOne
     @NotNull
     public SuperPower power;
     @NotEmpty
     public String weakness;
     @NotEmpty
     public String secretIdentity;
    }
    @Entity
    public class SuperPower extends AbstractEntity {
     @NotEmpty
     @Column(unique = true)
     public String name;
     @NotEmpty
     public String description;
     @NotNull
     @ManyToOne
     public SuperPowerType type;
    }
    @Entity
    public class SuperPowerType extends AbstractEntity {
     @NotEmpty
     @Column(unique = true)
     public String name;
     @NotNull
     @Length(min = 30)
     public String description;
    }
Как вы можете видеть (и если вы этого не скажете), SuperHero ссылается на SuperPower, а SuperPower ссылается на SuperPowerType. У всех есть некоторые обязательные атрибуты.
Все три объекта имеют общий супертип, предоставляющий идентификатор и версию.
    @MappedSuperclass
    public class AbstractEntity {
     @SuppressWarnings("unused")
     @Id
     @GeneratedValue
     public Long id;
     @SuppressWarnings("unused")
     @Version
     private Long version;
    }Для краткости я пропустил геттеры и сеттеры.
Наконец, есть SuperHeroRepository, который я хочу протестировать. У него есть единственный метод, возвращающий SuperHero на основе SuperPower 
. Простой тест для SuperHeroRepository может работать так:
Создайте SuperHero и убедитесь, что я могу получить его снова, используя SuperHeroRepository.
Легко, не правда ли? Что ж… давайте посмотрим, что вы действительно должны сделать
- создать пустую базу данных
- создайте SuperPowerType, убедившись, что все обязательные поля заполнены
- создать SuperPower этого типа, убедившись, что у вас есть все обязательные поля заполнены
- создайте SuperHero, убедившись, что все обязательные поля заполнены
- использовать хранилище для получения SuperHeros
- утверждаю, что вы получили ожидаемый результат.
Таким образом, мы должны заполнить все обязательные поля и предоставить SuperPowerType (опять же со всеми обязательными полями), хотя ничего в тесте не касается SuperPowerTypes. Если вы все еще не верите, что это приводит к ужасному коду, взгляните на эту наивную реализацию.
    public class SuperHeroRepository1Test {
     private SessionFactory sessionFactory;
     private Session session = null;
     @Before
     public void before() {
      // setup the session factory
      AnnotationConfiguration configuration = new AnnotationConfiguration();
      configuration.addAnnotatedClass(SuperHero.class)
        .addAnnotatedClass(SuperPower.class)
        .addAnnotatedClass(SuperPowerType.class);
      configuration.setProperty("hibernate.dialect",
        "org.hibernate.dialect.H2Dialect");
      configuration.setProperty("hibernate.connection.driver_class",
        "org.h2.Driver");
      configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem");
      configuration.setProperty("hibernate.hbm2ddl.auto", "create");
      sessionFactory = configuration.buildSessionFactory();
      session = sessionFactory.openSession();
     }
     @Test
     public void returnsHerosWithMatchingType() {
      // create the objects needed for testing
      SuperPowerType powerType = new SuperPowerType();
      powerType.name = "TheType";
      powerType.description = "12345678901234567890aDescription";
      SuperPower superpower = new SuperPower();
      superpower.name = "SuperPower";
      superpower.description = "Description";
      superpower.type = powerType;
      SuperHero hero = new SuperHero();
      hero.name = "Name";
      hero.power = superpower;
      hero.weakness = "None";
      hero.secretIdentity = "Mr. Jones";
      // storing the objects for the test in the database
      session.save(powerType);
      session.save(superpower);
      session.save(hero);
      SuperHeroRepository heroRepository = new SuperHeroRepository(session);
      List<SuperHero> heroes = heroRepository.loadBy(superpower);
      assertNotNull(heroes);
      assertEquals(1, heroes.size());
     }
     @After
     public void after() {
      session.close();
      sessionFactory.close();
     }
    }В этом тесте я могу сказать только одно: он использует H2 в режиме In-Memory, поэтому он достаточно быстрый. Есть первый урок: * Используйте базу данных в памяти для тестирования, если это возможно. Это легко на 100 * быстрее, чем база данных на диске *
Итак, очевидно, что мы хотим реорганизовать этого монстра, который после некоторого метода может извлечь результат примерно так:
    public class SuperHeroRepository2Test {
     private SessionFactory sessionFactory;
     private Session session;
     @Before
     public void before() {
      sessionFactory = createSessionFactory();
      session = sessionFactory.openSession();
      Transaction transaction = session.beginTransaction();
     }
     @Test
     public void returnsHerosWithMatchingType() {
      SuperPowerType powerType = createSuperPowerType();
      session.save(powerType);
      SuperPower superpower = createSuperPower(powerType);
      session.save(superpower);
      SuperHero hero = createSuperHero(superpower);
      session.save(hero);
      SuperHeroRepository heroRepository = new SuperHeroRepository(session);
      List<SuperHero> heroes = heroRepository.loadBy(superpower);
      assertNotNull(heroes);
      assertEquals(1, heroes.size());
     }
     private SessionFactory createSessionFactory() {
      AnnotationConfiguration configuration = new AnnotationConfiguration();
      configuration.addAnnotatedClass(SuperHero.class)
        .addAnnotatedClass(SuperPower.class)
        .addAnnotatedClass(SuperPowerType.class);
      configuration.setProperty("hibernate.dialect",
        "org.hibernate.dialect.H2Dialect");
      configuration.setProperty("hibernate.connection.driver_class",
        "org.h2.Driver");
      configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem");
      configuration.setProperty("hibernate.hbm2ddl.auto", "create");
      SessionFactory sessionFactory = configuration.buildSessionFactory();
      return sessionFactory;
     }
     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;
     }
    }Это было бы хорошо, если бы это было единственное испытание такого рода. Но для другого подобного теста также необходим SessionFactory, поэтому я перенесу все элементы SessionFactory, Session и Transaction в правило
    public class SessionFactoryRule implements MethodRule {
     private SessionFactory sessionFactory;
     private Transaction transaction;
     private Session session;
     @Override
     public Statement apply(final Statement statement, FrameworkMethod method,
       Object test) {
      return new Statement() {
       @Override
       public void evaluate() throws Throwable {
        sessionFactory = createSessionFactory();
        createSession();
        beginTransaction();
        try {
         statement.evaluate();
        } finally {
         shutdown();
        }
       }
      };
     }
     private void shutdown() {
      try {
       try {
        try {
         transaction.rollback();
        } catch (Exception ex) {
         ex.printStackTrace();
        }
        session.close();
       } catch (Exception ex) {
        ex.printStackTrace();
       }
       sessionFactory.close();
      } catch (Exception ex) {
       ex.printStackTrace();
      }
     }
     private SessionFactory createSessionFactory() {
      AnnotationConfiguration configuration = new AnnotationConfiguration();
      configuration.addAnnotatedClass(SuperHero.class)
        .addAnnotatedClass(SuperPower.class)
        .addAnnotatedClass(SuperPowerType.class);
      configuration.setProperty("hibernate.dialect",
        "org.hibernate.dialect.H2Dialect");
      configuration.setProperty("hibernate.connection.driver_class",
        "org.h2.Driver");
      configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem");
      configuration.setProperty("hibernate.hbm2ddl.auto", "create");
      SessionFactory sessionFactory = configuration.buildSessionFactory();
      return sessionFactory;
     }
     public Session createSession() {
      session = sessionFactory.openSession();
      return session;
     }
     public void commit() {
      transaction.commit();
     }
     public void beginTransaction() {
      transaction = session.beginTransaction();
     }
     public Session getSession() {
      return 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 methods more or less as before
    }Все создание SessionFactory теперь перенесено в правило и поэтому идеально подходит для повторного использования. Теперь вы можете изменить способ создания вашей SessionFactory в одной точке для всех тестов, нуждающихся в базе данных. Это хорошо, и этого будет достаточно на сегодня. На следующей неделе мы позаботимся о нашем SuperHero и ее зависимостях.