Нужно объяснение необходимости предварительной очистки, чтобы избежать ложных срабатываний при тестировании с помощью Spring?

В документации Spring относительно тестирование, в нем говорится:

Избегайте ложных срабатываний при тестировании кода ORM

Когда вы тестируете код с использованием инфраструктуры ORM, такой как JPA или Hibernate, очищайте базовый сеанс в методах тестирования, которые обновляют состояние сеанса. Если не сбросить базовый сеанс платформы ORM, это может привести к ложным срабатываниям: ваш тест может пройти, но тот же код выдает исключение в реальной производственной среде. В следующем тестовом примере на основе Hibernate один метод демонстрирует ложное срабатывание, а другой метод правильно предоставляет результаты сброса сеанса.

Может кто-нибудь объяснить, почему мне нужно вызвать флеш?


person JavaRocky    schedule 25.08.2010    source источник


Ответы (4)


Ну, вы действительно пропустили самое интересное, пример :) Вот он:

// ...

@Autowired
private SessionFactory sessionFactory;

@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the session is
    // finally flushed (i.e., in production code)
}

@Test(expected = GenericJDBCException.class)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

// ...

Этот пример пытается проиллюстрировать, что если вы на самом деле не flush сессию (иначе говоря, кеш первого уровня) для синхронизации изменений в памяти с базой данных, вы на самом деле не тестируете интеграцию с базой данных и можете не проверить реальное ожидаемое поведение или пропустить проблема.

Например, база данных может вернуть ошибку из-за, скажем, нарушения ограничения, и если вы не попадете в базу данных, вы не продемонстрируете правильное поведение, как в тестовом методе falsePositive() выше. Этот метод тестирования должен завершиться ошибкой или ожидать исключения, но он просто пройдет. С другой стороны, другой тестовый метод с флешем проверяет реальное поведение. Отсюда необходимость flush.

person Pascal Thivent    schedule 25.08.2010
comment
Паскаль, спасибо. Какая тут рекомендация? flush() после любого сохранения и избегать кеша первого уровня, когда производительность не является большой проблемой? И должны ли мы вызывать flush после вызова тестируемого метода вне вызова метода или даже внутри? Как запутанно! - person JavaRocky; 25.08.2010

Документация Spring использует неправильную концепцию. Это было ясно

но тот же код выдает исключение в реальной производственной среде

Вот википедия

Ошибка типа II, также известная как «ошибка второго рода», β-ошибка, или «ложноотрицательный результат»: ошибка, связанная с невозможностью отвергнуть нулевую гипотезу, когда она на самом деле неверна. . Примером этого может быть случай, когда тест показывает, что женщина не беременна, хотя на самом деле она беременна.

Если вы видите образец, предоставленный Spring, производственная среда выдает исключение (A GenericJDBCException), но оно не обнаружено. Чтобы увидеть, вы должны вызвать базовую фиксацию при использовании какого-либо поставщика ORM.

Определение тестовых шаблонов XUnit

Ситуация, в которой тест проходит, хотя тестируемая система не работает должным образом.

Таким образом, правильная концепция — falseNegative

@Test // no expected exception!
public void falseNegative() {
person Arthur Ronald    schedule 25.08.2010

Аннотировать тесты Spring с помощью @Transactional удобно, но это не то, как будет выполняться ваш производственный код. Аннотация @Transactional запустит транзакцию до запуска вашего тестового метода и откатит ее, когда тестовый метод завершится.

В то время как фиксации предшествует сброс, откату — нет, поэтому сброс вручную — это механизм безопасности, гарантирующий, что все изменения Entity будут переведены в операторы SQL.

Более подходящим дизайном было бы явное отображение границ транзакций следующим образом:

@Test
public void testRootObjects() {

    final Company newCompany = new Company();
    newCompany.setName("TV Company");

    final Long companyId = transactionTemplate.execute(new TransactionCallback<Long>() {
        @Override
        public Long doInTransaction(TransactionStatus transactionStatus) {
            entityManager.persist(newCompany);
            return newCompany.getId();
        }
    });
    Company detachedCompany = transactionTemplate.execute(new TransactionCallback<Company>() {
        @Override
        public Company doInTransaction(TransactionStatus transactionStatus) {
            Company attachedCompany = entityManager.find(Company.class, companyId);
            assertEquals(newCompany, attachedCompany);
            assertEquals(newCompany.hashCode(), attachedCompany.hashCode());
            return attachedCompany;
        }
    });
    assertEquals(newCompany, detachedCompany);
    assertEquals(newCompany.hashCode(), detachedCompany.hashCode());
}

Шаблон TransactionTemplate зафиксирует ваш код, поэтому нет необходимости в ручном сбросе.

Если вы вызываете методы службы @Transactional через их интерфейс, вам вообще не понадобится transactionTemplate, так как вы вызываете прокси-сервер Spring, который будет вызывать TransactionInterceptor (при условии, что вы указали Spring знать об аннотациях транзакций: ), и поэтому транзакции будут запущено/совершено от вашего имени.

person Vlad Mihalcea    schedule 15.05.2014

Кто-нибудь проверял аннотацию @TransactionConfiguration? Если вы используете аннотацию @Transactional, управляемую в вашем проекте, вы можете просто установить @TransactionConfiguration(defaultRollback = false, transactionManager = "YourTransactionManager") в вашем тестовом примере, и это будет работать отлично, надеюсь, это вам поможет.

person Wilson    schedule 04.01.2018