Spring annotation-based TestContext test framework using detailed

Source: Internet
Author: User
Tags assert date now testng

Original: Spring annotation-based TestContext test framework using detailed

Source code: Http://www.zuidaima.com/share/1775574182939648.h


Overview

Spring 2.5 comes down to the following 3 points compared to the most important features added to Spring 2.0:

    • The IoC function based on annotations;
    • Note-driven Spring MVC functionality;
    • Annotation-based TestContext testing framework.

Spring recommends that developers use the new annotation-based TestContext test framework, which we'll cover in detail here.

The Spring test box architecture provided by the lower version of spring is expanded on the basis of JUnit 3.8, which provides several test base classes. The new note-based TestContext test framework for Spring 2.5 has nothing to do with the lower version of the test framework. It uses the new annotation technology to make POJO a Spring test case, and in addition to having all the functionality of the old test framework, TestContext adds some new features that TestContext can run on JUnit 3.8, JUnit 4.4, TestNG such as the test framework.

Back to top of page

Direct use of JUnit to test the shortage of Spring programs

In my book, "Master Spring 2.x-Enterprise Application development in detail," the author once pointed out that if you use JUnit to test a spring-based program directly, there are 4 obvious deficiencies:

    • causes the Spring container to initialize multiple times: each execution of a test method will recreate a test case instance and invoke its SetUp () method, depending on the invocation process of the JUnit test case. Because in general, we initialize the spring container in the SetUp () method, which means how many test methods are in the test case and how many times the spring container will be initialized again.
    • you need to manually fetch the bean using hard coding: in the test case, we need to get the target Bean to be tested from the SPIRNG container by means of the Applicationcontext.getbean () method, and also to do the modeling operation.
    • The database site is vulnerable to damage: The test method may make changes to the database records, destroying the database site. While testing is done for the development database, if the impact of the data operation is persistent, there will be a cumulative effect and affect the re-execution of the test case. For example, suppose you insert a t_user record with ID 1 into a database in a test method, there is no problem with the first run, and the second run causes the test case execution to fail because of a primary key violation. Therefore, the test case should be able to complete the test firmware business functions of the correctness of the inspection, but also easy to restore the scene after the test, so that Snow no trace, geese have no trace.
    • It is not easy to access the database under the same transaction to verify the correctness of the business operation: when testing the firmware operation database, in order to detect the correctness of data operation, it is necessary to access the database through a convenient way in the same transactional environment as the test method to check the performance of test firmware data operation. It is difficult to do this if you are testing directly with JUnit.

The spring test framework, designed for testing based on spring framework applications, makes it easy for test cases to be combined with the spring framework, all of which can be solved.

A Spring service class that needs to be tested

Before we use the Textcontext test framework, let's begin by understanding the UserService service classes that need to be tested. The UserService service class has a service method that handles user logons, as shown in the following code:

Listing 1. Userservice.java service classes that need to be tested

Package Com.baobaotao.service;import Com.baobaotao.domain.loginlog;import Com.baobaotao.domain.user;import Com.baobaotao.dao.userdao;import Com.baobaotao.dao.loginlogdao;public class userservice{    private UserDao Userdao;    Private Loginlogdao Loginlogdao;    public void Handleuserlogin (user user) {        user.setcredits (5 + user.getcredits ());        Loginlog Loginlog = new Loginlog ();        Loginlog.setuserid (User.getuserid ());        Loginlog.setip (User.getlastip ());        Loginlog.setlogintime (User.getlastvisit ());        Userdao.updatelogininfo (user);        Loginlogdao.insertloginlog (Loginlog);    }    Omit Get/setter Method}

UserService needs to call the DAO layer's Userdao and Loginlogdao and user and Loginlog two PO to complete the business logic, the user and Loginlog correspond to T_user and T_login_log respectively Database tables.

After the user logon succeeds, call the Handleuserlogin () method in UserService to perform the business logic after the user has successfully logged on:

    1. Login user add 5 points (t_user.credits);
    2. The last access time (t_user.last_visit) and IP (T_USER.LAST_IP) of the logged-on user are updated to the current value;
    3. Adds a logon log to the user in the Log table (T_login_log).

This is a business method that requires access to the database and has data change operations, which work in a transactional environment. The following is the Spring configuration file that assembles the service class Bean:

Listing 2. applicationContext.xml:Spring configuration file, placed under Classpath

<?xml version= "1.0" encoding= "UTF-8"? ><beans xmlns= "Http://www.springframework.org/schema/beans" xmlns: Xsi= "Http://www.w3.org/2001/XMLSchema-instance" xmlns:p= "http://www.springframework.org/schema/p" xmlns:tx= "http ://www.springframework.org/schema/tx "xmlns:aop=" HTTP://WWW.SPRINGFRAMEWORK.ORG/SCHEMA/AOP "xmlns:context=" http ://www.springframework.org/schema/context "xsi:schemalocation=" Http://www.springframework.org/schema/beans http ://www.springframework.org/schema/beans/spring-beans-2.5.xsd HTTP://WWW.SPRINGFRAMEWORK.ORG/SCHEMA/TX/http Www.springframework.org/schema/tx/spring-tx-2.5.xsd HTTP://WWW.SPRINGFRAMEWORK.ORG/SCHEMA/AOP/HTTP Www.springframework.org/schema/aop/spring-aop-2.5.xsd "> <!--configuration data source--><bean id=" DataSource "class=" Org.apache.commons.dbcp.BasicDataSource "destroy-method=" Close "p:driverclassname=" Com.mysql.jdbc.Driver "p:u      Rl= "Jdbc:mysql://localhost/sampledb" p:username= "root" p:password= "1234"/>  <!--Configuring the JDBC template--><bean id= "JdbcTemplate" class= "Org.springframework.jdbc.core.JdbcTemplate" P:datas           ource-ref= "DataSource"/> <!--configuration dao--><bean id= "Loginlogdao" class= "Com.baobaotao.dao.LoginLogDao" p:jdbctemplate-ref= "JdbcTemplate"/><bean id= "Userdao" class= "Com.baobaotao.dao.UserDao" p:jdbctemplate-ref= "JdbcTemplate"/><!--transaction manager--><bean id= "TransactionManager" class= " Org.springframework.jdbc.datasource.DataSourceTransactionManager "p:datasource-ref=" DataSource "/><bean id= "UserService" class= "Com.baobaotao.service.UserService" p:userdao-ref= "Userdao" p:loginlogdao-ref= "Loginlogdao The/> <!--uses the AOP/TX namespace to configure transaction management, where the service class methods under the services package are provided--<aop:config><aop:pointcut id= " Jdbcservicemethod "expression=" Within (com.baobaotao.service.. *) "/><aop:advisor pointcut-ref=" Jdbcservicemethod "advice-ref=" Jdbctxadvice "/> </aop:config> <tx : Advice id= "JdbctxadvIce "transaction-manager=" TransactionManager "><tx:attributes> <tx:method name=" * "/> </ Tx:attributes></tx:advice></beans>

The DAO and PO classes associated with UserService are relatively straightforward, see the program code for the attachments in this article. Before you begin to test Userserivce, you will need to create a database table, which you can find in the schema directory of the attachment to the appropriate SQL script file.

Back to top of page

Writing test cases for UserService

Here we write a simple test case class for UserService, where the goal is to run the test class based on the TestContext test framework, and we'll refine the test case later.

Listing 3. Testuserservice.java: annotation-based test cases

Package Com.baobaotao.service;import ORG.SPRINGFRAMEWORK.TEST.CONTEXT.JUNIT4.    Abstracttransactionaljunit4springcontexttests;import org.springframework.test.context.ContextConfiguration; Import Org.springframework.beans.factory.annotation.autowired;import Org.junit.test;import Com.baobaotao.domain.user;import java.util.Date, @ContextConfiguration  //①public class Testuserservice extends     abstracttransactionaljunit4springcontexttests {   @Autowired  //②   private userservice userservice;   @Test  //③ public   void Handleuserlogin () {       User user = new User ();       User.setuserid (1);       User.setlastip ("127.0.0.1");       Date now = new Date ();       User.setlastvisit (Now.gettime ());       Userservice.handleuserlogin (user);}   }

Here, we let Testuserservice directly inherit from the abstract test class provided by Spring, and later this article will dissect this abstract test class. All you need to know here is that the purpose of this abstract test class is to let the TestContext test framework run on the JUnit 4.4 test framework.

At ①, a class-level @ContextConfiguration annotation is labeled, where Spring will find classpath:/com/baobaotao/service/by TestContext contract Testuserservice-context.xml the spring configuration file and use that profile to start the spring container. @ContextConfiguration annotations have the following two common properties:

    • Locations: You can specify one or more spring profiles by manually specifying where the spring configuration file is located. As shown below:

      @ContextConfiguration (locations={"Xx/yy/beans1.xml", "Xx/yy/beans2.xml"})

    • Inheritlocations: If you want to inherit the Spring configuration file from the parent test case class, the default is true. As in the following example:

      @ContextConfiguration (locations={"Base-context.xml"}) public class Basetest {     //...} @ContextConfiguration ( locations={"Extended-context.xml"}) public class Extendedtest extends Basetest {     //...}

If Inheritlocations is set to False, Extendedtest only uses the Extended-context.xml configuration file, otherwise the base-context.xml and Extended-context.xml the two configuration files.

The @Autowired annotations at ② let the Spring container automatically inject userservice-type beans. The @Test annotations noted at ③ let the Handleuserlogin () method be a test method for the JUnit 4.4 standard, @Test is the annotations defined by JUnit 4.4.

Before running the Testuserservice test class, let's take a look at the contents of the Testuserservice-context.xml configuration file:

Spring configuration file referenced by listing 4.TestUserService

<?xml version= "1.0" encoding= "UTF-8"? ><beans xmlns= "Http://www.springframework.org/schema/beans" xmlns: Xsi= "Http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation= "Http://www.springframework.org/schema/beans Http://www.springframework.org/schema/beans/spring-beans-2.5.xsd "><!--① introduces the spring configuration file defined in Listing 1-->< Import resource= "Classpath:/applicationcontext.xml"/></beans>

The Spring configuration file defined in Listing 1 is introduced at ① so that we can inject the UserService Bean defined in it into the testuserservice as a test firmware.

In your IDE (Eclipse, JBuilder, Idea, etc.), after introducing the JUnit 4.4 Class package into Project engineering, right-clicking in the Testuserservice class to run the test class will find that Testuserservice is ready to run successfully. As shown in Figure 1:

Figure 1. Running Testuserservice in Eclipse 6.0

Testuserservice can run correctly, stating that its userservice this test firmware has already enjoyed Spring auto-injection functionality. After running the test case, view the T_user table and the T_login_log table in the database, and you will find that the table data is the same as before the test! This means that although we performed the Userservice.handleuserlogin (user) operation in the Handleuserlogin () test method in Listing 3, it did not cause any damage to the database site: this is because Spring A transaction rollback operation before the test method returns.

Although the Testuserservice.handleuserlogin () test method has been successfully run, but it is not perfect in the test function, the reader friend can have found that it has the following two questions:

    • We only implemented the Userservice#handleuserlogin method, but verified the correctness of the method's execution results.
    • Testing using the user object with ID 1 directly in the test method is equivalent to requiring that the database T_user table must already have a record with ID 1, which would cause the test method execution to fail if the record does not exist in T_user.

Back to top of page

Prepare test data and detect running results

In this section, we will address the two issues raised above, preparing test data in test cases and detecting the correctness of business execution results in the database.

Preparing test data

Rather than accessing a predetermined data record directly in the test method, it is a good strategy to prepare some test data through the program before the test method executes, and then run the test method on that basis, since the latter does not require assumptions about the state of the database. In TestContext, you can do this by using the @Before annotations of JUnit 4.4, see the following code:

Listing 5. Preparing data for test methods

Package Com.baobaotao.service;import Java.sql.connection;import Java.sql.preparedstatement;import Java.sql.sqlexception;import Java.util.date;import Org.junit.before;import Org.junit.test;import Org.springframework.beans.factory.annotation.autowired;import Org.springframework.jdbc.core.preparedstatementcreator;import Org.springframework.jdbc.support.generatedkeyholder;import Org.springframework.jdbc.support.keyholder;import Org.springframework.test.context.contextconfiguration;import Org.springframework.test.context.junit4.abstracttransactionaljunit4springcontexttests;import Com.baobaotao.dao.userdao;import Com.baobaotao.domain.User; @ContextConfigurationpublic class Testuserservice       Extends Abstracttransactionaljunit4springcontexttests {@Autowired private userservice userservice;       @Autowired private Userdao Userdao;       private int userId; @Before//① prepare test data public void Preparetestdata () {final String sql = "INSERT into T_user (User_name,password) v AlueS (' Tom ', ' 1234 ') ";        Simplejdbctemplate.update (SQL);        Keyholder keyholder = new generatedKeyHolder (); Simplejdbctemplate.getjdbcoperations (). Update (new PreparedStatementCreator () {public prepareds Tatement createpreparedstatement (Connection conn) throws SQLException {Preparedstat                    ement PS = conn.preparestatement (SQL);                return PS;        }}, Keyholder);  UserId = Keyholder.getkey (). Intvalue ()//①-1 record the ID of the test data} @Test public void Handleuserlogin () {User user = Userdao.getuserbyid (userId);        ② Get test data user.setlastip ("127.0.0.1");        Date now = new Date ();        User.setlastvisit (Now.gettime ());    Userservice.handleuserlogin (user); }}

JUnit 4.4 allows annotations to specify that certain methods are called before and after the test method executes, that is, @Before and @After annotations. In Spring TestContext, methods for labeling @Before and @After are executed before and after each test method in the test case and run in the same transaction as the test method. In Listing 5, ①, we labeled the Preparetestdata () with @Before annotations, which prepare some test data for use by all the test methods in the Testuserservice (there is only one handleuserlogin () test party Law). Since the test method runs, the entire transaction is rolled back, and the test data inserted in Preparetestdata () is not persisted to the database, so we do not need to manually delete this record.

Methods and test methods that annotate @Before or @After annotations run in the same transaction, but sometimes we want to execute some methods before or after the transaction of the test method to get some information about the database site. At this point, you can use Spring TestContext's @BeforeTransaction and @AfterTransaction annotations to reach the table of contents (these two annotations are located in Org.springframework.test.context.transaction in the package).

While most business methods access the database, not all business methods that need to be tested need to deal with the database. By default, all test methods that inherit from the Abstracttransactionaljunit4springcontexttests test case will work in a transactional context, and you can explicitly pass @NotTransactional annotations, Let the test method not work in a transactional environment.

The Preparetestdata () method is used in the Simplejdbctemplate object Access operations database, which is defined in the Abstracttransactionaljunit4springcontexttests abstract class as long as The Spring container has a configuration data source, and Simplejdbctemplate is created automatically. The abstract class also has a spring container reference: ApplicationContext, you can access the spring container with this member variable, execute the Fetch Bean, publish the event, and so on.

In addition, Abstracttransactionaljunit4springcontexttests provides a number of convenient ways to access the database, as explained below:

    • protected int countrowsintable (String tableName): Calculates the number of records for the data table.
    • protected int Deletefromtables (String ... names): Deletes a record from a table, you can specify more than one table.
    • protected void Executesqlscript (String sqlresourcepath, Boolean ContinueOnError): Executes the SQL script file, in the script file, whose format must be one row of SQL statements.

At the ② of the test Method Handleuserlogin (), we get the test data added by Userdao Preparetestdata (), and the test method executes the business logic on the basis of the test data. With this test method, there are no business logic issues that run testuserservice under any circumstances.

Verify the correctness of business logic

To this end, Testuserservice's Handleuserlogin () test method is simply to execute the Userservice#handleuserlogin () business method, but does not check the correctness of the execution results after the business method is executed, So this test is not in place. That is, we must access the database to check whether the business method is successful with the data change: This includes points (credits), Last logon time (last_visit), last logon IP (LAST_IP), and logon log records (T_login_log) in the log-in log table. Below, we supplement this important work of checking the correctness of data:

Listing 5. Verify the correctness of the business method execution results

@Testpublic void Handleuserlogin () {    User user = Userdao.getuserbyid (userId);    User.setlastip ("127.0.0.1");    Date now = new Date ();    User.setlastvisit (Now.gettime ());    Userservice.handleuserlogin (user);    ------------------The following code for the business execution result check---------------------    User newuser = Userdao.getuserbyid (userId);    Assert.assertequals (5, Newuser.getcredits ()); ① detection points    //① detect last logon time and IP    assert.assertequals (now.gettime (), newuser.getlastvisit ());    Assert.assertequals ("127.0.0.1", Newuser.getlastip ());           ③ Detect logon log    String sql = "SELECT COUNT (1) from T_login_log where user_id=?" +        "and login_datetime=? and ip=?";    int Logcount =simplejdbctemplate.queryforint (sql, User.getuserid (),        user.getlastvisit (), User.getlastip ());    Assert.assertequals (1, Logcount);    }

After the business method is executed, we query the database for the corresponding record to check whether it is consistent with the desired effect, as shown in ① and ②. At ③, we use Simplejdbctemplate to query the T_login_log to see if a user login log has been added to the table.

Note: Because our DAO layer uses the Spring JDBC framework, it does not employ the service layer caching technology, so you can use the DAO class to return data from the database. If ORM frameworks such as Hibernate are adopted, because they adopt the technology of service layer caching, in order to get the corresponding data in the database, we need to call the Hibernatetemplate.flush () method after the business method executes, and synchronize the objects in the cache to the database. The execution of the business method can then be accessed through the simplejdbctemplate in the database.

Back to top of page

Spring TestContext Test Framework Architecture

In the front, we write test cases directly from the extension abstracttransactionaljunit4springcontexttests, and after we understand the test cases that are written based on the TestContext test framework, we now understand TestContext It's time to test the framework itself.

TestContext core classes, support classes, and annotation classes

The core of the TestContext test framework consists of three classes in the Org.springframework.test.context package, namely TestContext and Testcontextmanager classes, and Testexecutionlistener interface. The class diagram is shown in Figure 2 below:

Figure 2. Spring TestContext Test Framework Core class

    • TestContext: It encapsulates the context in which the test case is run;
    • Testcontextmanager: It is the main entrance to the Spring TestContext Framework, which manages a TestContext instance, and at the appropriate execution point to all registered in Testcontextmanager The Testexecutionlistener listener publishes events such as the preparation of test case instances, the invocation of methods before and after test method execution, and so on.
    • Testexecutionlistener: This interface is responsible for responding to events published by Testcontextmanager.

Spring TestContext allows you to register multiple listeners to Testcontextmanager with @TestExecutionListeners annotations in the test case class as follows:

@TestExecutionListeners ({     dependencyinjectiontestexecutionlistener.class,    Dirtiescontexttestexecutionlistener.class}) public class testxxxservice{...    }

Spring provides several Testexecutionlistener interface implementation classes, each of which is described below:

    • Dependencyinjectiontestexecutionlistener: This listener provides auto-injection capabilities that parse @Autowried annotations in test cases and complete automatic injection;
    • Dirtiescontexttestexecutionlistener: In general, the test method does not cause damage to the spring container context (changing the Bean's configuration information, etc.), and if a test method does break the spring container context, You can explicitly add @DirtiesContext annotations for the test method so that spring TestContext refreshes the context of the spring container after testing the method, and Dirtiescontexttestexecutionlistener The listener's job is to parse @DirtiesContext annotations;
    • Transactionaltestexecutionlistener: It is responsible for parsing @Transaction, @NotTransactional, and @ Annotations to Rollback and other business annotations. @Transaction annotations Let the test method work in a transactional environment, but the transaction is rolled back before the test method returns. You can use @Rollback (false) to have the test method return before the transaction is committed. @NotTransactional annotations Let the test method not work in a transactional environment. In addition, you can change the transaction management policy using @TransactionConfiguration annotations at the class or method level, as follows:
       @TransactionConfiguration (
      Transactionmanager= "Txmgr", Defaultrollback=false) @Transactionalpublic class Testuserservice {...} 

We know that in JUnit 4.4, you can specify the runner for a test case by @RunWith annotations, and the Spring TestContext Framework provides an extension of the Org.junit.internal.runners.JUnit4ClassRunner The Springjunit4classrunner runner, which is responsible for the assembly Spring TestContext test framework and unifies it into the JUnit 4.4 framework.

Abstract test cases provided by TestContext

Spring TestContext provides two abstract test case classes for the JUnit 4.4-based test framework, namely abstractjunit4springcontexttests and Abstracttransactionaljunit4springcontexttests, while the latter extends to the former. Let's look at the skeleton code for these two abstract test case classes:

@RunWith (springjunit4classrunner.class)//① Specify the test case runner @testexecutionlisteners (                 //② Registered two Testexecutionlistener listeners    {dependencyinjectiontestexecutionlistener.class,    Dirtiescontexttestexecutionlistener.class}) public class Abstractjunit4springcontexttests implements Applicationcontextaware {    ...}

① Specifies Springjunit4classrunner as the test case runner, which is responsible for seamlessly counter switch the TestContext test framework into the JUnit 4.4 test framework, which is where Spring TestContext can run. ② registers two Testexecutionlistener listeners in the test case class with @TestExecutionListeners annotations, which are responsible for processing @Autowired and @DirtiesContext annotations, respectively. Provides the ability to automatically inject and re-refresh the Spring container context for test cases.

Abstracttransactionaljunit4springcontexttests is extended to abstractjunit4springcontexttests and provides support for transaction management with skeleton code like this:

① registering a test case Transaction Management listener @testexecutionlisteners ({transactionaltestexecutionlistener.class}) @Transactional    //② All methods that make a test case will work in a transactional environment under public class Abstracttransactionaljunit4springcontexttests extends abstractjunit4springcontexttests {    ...}

At ①, abstracttransactionaljunit4springcontexttests registers the Transactionaltestexecutionlistener listener with the test case class so that the @ in the test case Annotations like Transaction, @NotTransaction, and @Rollback can work correctly. Note that you do not need to enable annotation transaction-driven and annotation auto-injection in the Spring configuration file through <tx:annotation-driven/> and <context:annotation-config/> for test case classes. This work is completely solved by TestContext itself (by registering Dependencyinjectiontestexecutionlistener and Transactionaltestexecutionlistener listeners), After all, the test case class is not registered with the spring container and is not a spring Bean.

Summary

By testing a typical UserService service class that involves database access operations, we describe the issues of integration testing using the Spring 2.5 TestContext test framework, including testing for automatic injection of firmware, automatic transaction rollback, Simplejdbctemplate direct access to databases and test data preparation issues.

After learning from a practical example, we have a specific understanding of how to use the TestContext test framework, based on which we analyze the Spring TestContext Test Framework Architecture and then analyze spring as TestContext The two abstract test case classes provided on the JUnit 4.4 test framework are grafted.

Spring's TestContext test framework can be integrated into the JUnit 4.4 test framework and can be integrated into the test frameworks of JUnit 3.8 and TestNG. Support for JUnit 3.8 and TestNG is now available, and you can do it separately in org.springframework.test.context.junit38 and Find the integrated Help class under the Org.springframework.test.context.testng package.

Spring annotation-based TestContext test framework using detailed

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.