需要解释在使用 Spring 进行测试时预先刷新以避免误报的必要性吗?

发布于 2024-09-16 01:09:34 字数 470 浏览 9 评论 0原文

spring 文档中测试,它指出:

测试 ORM 时避免误报 代码

当您测试涉及以下内容的代码时 ORM 框架,例如 JPA 或 休眠,刷新底层 测试方法中的会话 更新会话的状态。 无法刷新 ORM 框架 底层会话可能会产生错误 积极的一面:你的测试可能会通过,但是 相同的代码在 a 中抛出异常 生活、生产环境。在 以下基于 Hibernate 的示例测试 情况下,一种方法显示错误 正法和另一种方法 正确暴露结果 刷新会话。

有人可以解释为什么我需要打电话冲洗吗?

In the spring documentation regarding testing, it states:

Avoid false positives when testing ORM
code

When you test code involving an
ORM framework such as JPA or
Hibernate, flush the underlying
session within test methods which
update the state of the session.
Failing to flush the ORM framework's
underlying session can produce false
positives: your test may pass, but the
same code throws an exception in a
live, production environment. In the
following Hibernate-based example test
case, one method demonstrates a false
positive and the other method
correctly exposes the results of
flushing the session.

Can someone explain why i need to call flush?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

夜司空 2024-09-23 01:09:34

好吧,您实际上跳过了有趣的部分,即示例:) 这里是:

// ...

@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();
}

// ...

这个示例试图说明的是,除非您实际上刷新会话(也称为一级缓存)以同步内存中如果数据库发生变化,您并没有真正测试数据库集成,并且可能不会测试真正的预期行为或错过问题。

例如,数据库可能会因为违反约束条件而返回错误,如果您不访问数据库,则不会表现出正确的行为,如 falsePositive() 测试中所示方法同上。此测试方法应该会失败,或者预期会出现异常但会通过。另一方面,另一种带有刷新的测试方法确实测试了真实的行为。因此需要flush

Well, you actually skipped the interesting part, the example :) Here it is:

// ...

@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();
}

// ...

What this example tries to illustrate is that unless you actually flush the session (A.K.A. the first level cache) to sync in-memory changes with the database, you're not really testing the database integration and might not test the real expected behavior or miss a problem.

For example, the database could return an error because of, say a constraint violation, and if you don't hit the database, you won't exhibit this right behavior, as in the falsePositive() test method above. This test method should fail, or expect an exception but will just pass. On the other hand, the other test method with the flush does test the real behavior. Hence the need to flush.

别把无礼当个性 2024-09-23 01:09:34

使用 @Transactional 注释 Spring 测试很方便,但这不是生产代码的执行方式。 @Transactional 注释将在运行测试方法之前启动事务,并在测试方法完成时回滚它。

虽然提交之前有刷新,但回滚则不然,因此手动刷新是一种安全机制,可确保所有实体更改都转换为 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 服务方法的接口调用 @Transactional 服务方法,则根本不需要 transactionTemplate,因为您正在调用 Spring 代理,它将调用 TransactionInterceptor (假设您指示 Spring 了解事务注释:),因此事务将被代表您开始/承诺。

Annotating Spring tests with @Transactional is convenient but it's not how your production code will be executed. The @Transactional annotation will start a transaction prior to running your test method and it will roll it back when the test method finishes.

While commit is preceded by a flush, the roll-back is not, so a manual flush is a safety-mechanism to ensure all Entity changes are translated to SQL statements.

A more appropriate design would be to draw the transaction boundaries explicitly like this:

@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());
}

The TransactionTemplate will commit your code so there's no need for manual flushes.

If you call @Transactional service methods through their interface, you won't need the transactionTemplate at all, since you are calling a Spring proxy which will call TransactionInterceptor (assuming you instructed Spring to be aware of transaction annotations: ) and therefore transactions will be started/committed on your behalf.

迟到的我 2024-09-23 01:09:34

Spring 文档使用了错误的概念。已经明确了

但相同的代码在实时生产环境中抛出异常

这里是维基百科

II 类错误,也称为“第二类错误”、β 错误,或“假阴性”:未能拒绝原假设的错误事实并非如此。 一个例子是,如果测试显示一名女性没有怀孕,而实际上她怀孕了。

如果您看到Spring提供的示例,生产环境会抛出异常(A GenericJDBCException) ,但尚未检测到。为了查看,您必须在使用某些 ORM 提供程序时调用底层提交。

XUnit 测试模式定义

即使被测系统未正常工作,测试仍通过的情况。


所以正确的概念是 falseNegative

@Test // no expected exception!
public void falseNegative() {

Spring documentation uses the wrong concept. It has been clear

but the same code throws an exception in a live, production environment

Here goes wikipedia

Type II error, also known as an "error of the second kind", a β error, or a "false negative": the error of failing to reject a null hypothesis when it is in fact not true. An example of this would be if a test shows that a woman is not pregnant, when in reality, she is.

If you see the sample provided by Spring, The production environment throws an exception (A GenericJDBCException), but it has not been detected. In order to see, you must call the underlying commit when using some ORM provider.

XUnit Test patterns definition

A situantion in which a test passes even though the system under test is not working properly.

So the right concept is falseNegative

@Test // no expected exception!
public void falseNegative() {
神妖 2024-09-23 01:09:34

有人检查过@TransactionConfiguration 注释吗?如果您在项目中使用 @Transactional 注解驱动,则只需在测试用例中设置 @TransactionConfiguration(defaultRollback = false, transactionManager = "YourTransactionManager") 即可完美运行,希望这会对您有所帮助。

Does anyone checked with the annotation @TransactionConfiguration? If you are using @Transactional annotation driven in your project, you can simply set @TransactionConfiguration(defaultRollback = false, transactionManager = "YourTransactionManager") in your test case will work perfectly, hopefully this will helps you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文