Need explanation on the necessity of a prior flushing to avoid false positives whenTesting with Spring ?

后端 未结 4 1063
伪装坚强ぢ
伪装坚强ぢ 2021-01-02 08:43

In the spring documentation regarding testing, it states:

Avoid false positives when testing ORM code

When you test code involving an

相关标签:
4条回答
  • 2021-01-02 08:58

    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() {
    
    0 讨论(0)
  • 2021-01-02 08:59

    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.

    0 讨论(0)
  • 2021-01-02 09:06

    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.

    0 讨论(0)
  • 2021-01-02 09:16

    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.

    0 讨论(0)
提交回复
热议问题