【问题标题】:Need explanation on the necessity of a prior flushing to avoid false positives whenTesting with Spring ?需要解释在使用 Spring 测试时需要提前冲洗以避免误报吗?
【发布时间】:2010-08-25 02:13:03
【问题描述】:

spring documentation regarding testing 中,它声明:

在测试 ORM 时避免误报 代码

当您测试涉及 ORM 框架,例如 JPA 或 休眠,刷新底层 测试方法中的会话 更新会话的状态。 未能刷新 ORM 框架的 基础会话可以产生错误 阳性:您的测试可能通过,但 相同的代码在 生活,生产环境。在里面 遵循基于 Hibernate 的示例测试 情况下,一种方法证明了错误 正面和其他方法 正确暴露结果 刷新会话。

有人可以解释为什么我需要调用flush吗?

【问题讨论】:

    标签: hibernate spring testing jpa false-positive


    【解决方案1】:

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

    // ...
    
    @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 会话(A.K.A. 一级缓存)与数据库同步内存中的更改,否则您并没有真正测试数据库集成,也可能不会测试真实的预期行为或遗漏问题。

    例如,数据库可能会因为违反约束而返回错误,如果您没有访问数据库,您将不会表现出这种正确的行为,就像上面的 falsePositive() 测试方法一样。这个测试方法应该失败,或者期待一个异常但会通过。另一方面,使用冲洗的另一种测试方法确实测试了真实行为。因此需要flush

    【讨论】:

    • 帕斯卡,谢谢。这里有什么建议?在任何保存后刷新()并在性能不是太大问题时避免一级缓存?我们应该在方法调用之外甚至内部调用被测方法之后调用flush吗?多么令人困惑!
    【解决方案2】:

    Spring 文档使用了错误的概念。已经很清楚了

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

    这里是wikipedia

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

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

    XUnit 测试模式定义

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

    所以正确的概念是falseNegative

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

    【讨论】:

      【解决方案3】:

      使用 @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 服务方法,则根本不需要事务模板,因为您正在调用将调用 TransactionInterceptor 的 Spring 代理(假设您指示 Spring 了解事务注释:),因此交易将代表您开始/提交。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 2017-12-04
          • 1970-01-01
          • 2020-06-30
          • 1970-01-01
          • 1970-01-01
          • 2014-09-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多