目录
1、为什么要编写单元测试?
1、为了模块功能的测试(看下这个功能是否满足我们的要求)
黑盒子测试:只对方法的整体进行测试。只看整体的方法是否满足要求
白盒子测试:不光要对这个方法整体做测试 还要对代码中所有的分支都要编写测试用例
2、为了回归测试
开发了一个功能之后 这个功能是否影响了其他 已经开发好的功能?那么当你开发好一 个功能之后 只需要运行下所有的测试用例 如果是所有的测试用例 都不收影响的话 那么说明你开发的这个功能 没有对其他的功能进行影响.
在实际开发中并不是像我们的软件工程一样要做很多很多的测试
一般情况下 我们的测试 :单元测试 回归测试 集成测试 公测
2、Junit的使用
2.1、Junit的基本使用
2.1.1、Junit的几个注解
-
@Before
:这个before不同于在SpringAOP中的Before,它表示的意思是在执行任何的测试方法之前,都要调用被他注解的方法(初始化方法)。 -
@After
:表示在执行完测试之后所要调用的被注解的方法 -
@Test
:用于表示该方法是一个测试方法 -
@BeforeClass
:用于进行测试方法测试前初始化,在运行整个测试类只初始化一次(后面与Before进行对比) -
@AfterClass
:表示在执行完测试之后所要调用的被注解方法,在运行整个测试类只调用一次(后面与After进行对比)
2.1.2、编写需要测试的类
public class Calture {
/**
* 加法运算
* @param a
* @param b
* @return
*/
public int add(int a,int b){
return a+b;
}
/**
* 除法运算
* @param a
* @param b
* @return
*/
public int cf(int a,int b){
return a/b;
}
}
2.1.3、在test目录下创建测试类,导入Junit包
注意:测试类的类名尽量保证为:Test+要测试的类名。
导入Junit测试包,这里给出的是4.13.2版本,如需其他版本可前往仓库拷贝
仓库地址:Maven Repository: Search/Browse/Explore (mvnrepository.com)
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
2.1.4、编写测试类
import org.junit.After;
import org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.hamcrest.core.*;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestCalture {
//维护被测试类的对象
private Calture calture;
@Before//执行任何方法之前都要调用初始化方法
public void init(){
System.out.println("---------Before---------");
calture = new Calture();
}
@Test
public void add(){
System.out.println("---------Test---------");
int result = calture.add(1, 2);
//第一种断言方式
// Assert.assertEquals(result,3);
//第二种断言方式---使用Hamcrest与junit整合使用
assertThat(result,AnyOf.anyOf(IsNull.notNullValue(),IsEqual.equalTo(10)));
/**
* IsNull.notNullValue() :表示的意思是不等于空值
* IsEqual.equalTo():表示等于某一个值
*
*/
}
@After
public void after(){
//将被测试对象进行还原
System.out.println("-----------After---------");
calture = null;
}
}
注意import导的包是否正确
运行结果:
增加一个除法测试方法:
import org.junit.After;
import org.junit.Assert;
import org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.hamcrest.core.*;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestCalture {
//维护被测试类的对象
private Calture calture;
@Before//执行任何方法之前都要调用初始化方法
public void init(){
System.out.println("---------initBefore---------");
calture = new Calture();
}
@Test
public void add(){
System.out.println("---------addTest---------");
int result = calture.add(1, 2);
//第一种断言方式
// Assert.assertEquals(result,3);
//第二种断言方式---使用Hamcrest与junit整合使用
assertThat(result,AnyOf.anyOf(IsNull.notNullValue(),IsEqual.equalTo(10)));
/**
* IsNull.notNullValue():表示的意思是 不等于空值
* IsEqual.equalTo:表示等于某一个值
* AllOf.allOf() :表示的是 所有成立 才成立
* IsInstanceOf.instanceOf():这个对象是不是 某一个类的实例
* IsNot.not():表示的是不等于某一个值
* IsNull.nullValue():判断是不是等于空值
* IsNull.notNullValue():表示的是,是不是不等于空值
* StringStartsWith.startsWith():是不是以某一个字符串开头
* StringContains.containsString():是不是包含某一个字符串
* StringEndsWith.endsWith():表示的是,是不是以某一个字符串结尾
*
*
*
* assertThat(); 给Hamcrest提供接口
*/
}
@Test
public void cf(){
System.out.println("---------cfTest---------");
int result = calture.cf(10,2);
Assert.assertEquals(result,5);
}
@After
public void after(){
//将被测试对象进行还原
System.out.println("-----------After---------");
calture = null;
}
}
运行整个测试程序(注意不是只运行cf()方法):
我们可以看到:每次调用一个测试方法,@Before和@After注释的方法都被调用了一次。
那么,@BeforeClass和@AfterClass呢?
小小的修改一下,注意两个注释修饰的方法要用static修饰
import org.junit.*;
import org.junit.Assert.*;
import org.hamcrest.core.*;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestCalture {
//维护被测试类的对象
private static Calture calture;
// @Before//执行任何方法之前都要调用初始化方法
// public void init(){
// System.out.println("---------initBefore---------");
// calture = new Calture();
//
// }
@BeforeClass//执行任何方法之前都要调用初始化方法
public static void init(){
System.out.println("---------initBefore---------");
calture = new Calture();
}
@Test
public void add(){
System.out.println("---------addTest---------");
int result = calture.add(1, 2);
//第一种断言方式
// Assert.assertEquals(result,3);
//第二种断言方式---使用Hamcrest与junit整合使用
assertThat(result,AnyOf.anyOf(IsNull.notNullValue(),IsEqual.equalTo(10)));
/**
* IsNull.notNullValue() :表示的意思是不等于空值
* IsEqual.equalTo():表示等于某一个值
*
*/
}
@Test
public void cf(){
System.out.println("---------cfTest---------");
int result = calture.cf(10,2);
Assert.assertEquals(result,5);
}
// @After
// public void after(){
// //将被测试对象进行还原
// System.out.println("-----------After---------");
// calture = null;
// }
@AfterClass
public static void after(){
//将被测试对象进行还原
System.out.println("-----------After---------");
calture = null;
}
}
运行结果:我们发现@BeforeClass和@AfterClass修饰的方法只执行了一次
思考:两种注释那种更好呢?第二种?
实际上,我们测试时,基本上使用的是第一种注释来进行方法测试,主要原因是避免测试数据(对象)的改变。
比如,我们使用第二种方法进行测试,只进行一次初始化操作,但在调用第一次测试方法之后,方法内对这个对象进行了一些改变,那么我们在调用的二个测试方法的时候,若我们还需要这个对象,那么这个对象就不是原来的那个对象了,可能就会引起测试错误。
所以我们尽量使用第一种测试,在每次测试方法后将数据还原。
最后关于@Test
我们来看看@Test类
其中有一个timeout()方法,那么也就意味着在@Test中我们可以传递一些东西啦!!!
@Test(timeout = 5000)
public void cf(){
System.out.println("---------cfTest---------");
Thread.sleep(5000);
int result = calture.cf(10,2);
Assert.assertEquals(result,5);
}
//@Test(timeout = 5000),即若5S没有完成方法则测试不通过!为了方便看到效果可以直接加sleep
JUnit完成!
3、dbunit测试的使用
在开发的时候,一般情况下,我们都是分层开发,那么这个时候也就需要分层做测试。
dbunit是一款专门用来进行DAO层测试的这样一款框架,可以理解为用来测试数据库访问的
这个时候要注意一个问题:
我们在进行数据库的测试的时候,是不会去动原来的数据库中的数据的!一般测试的都是我们插入的测试数据。
示例表t_user为:
开整!!!
3.1、导包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.6</version> </dependency><dependency> <groupId>dbunit</groupId> <artifactId>dbunit</artifactId> <version>2.1</version> </dependency>
3.2、JDBCUtils工具类的编写
public class JdbcUtils {
private static DruidDataSource dataSource;
static{
dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("xxxxxx");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///xxxx?userUnicode=true&characterEncoding=UTF-8");
}
public static QueryRunner queryRunner(){
return new QueryRunner(dataSource);
}
}
3.3、User实体类编写
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String username;
private String password;
}
3.4、业务逻辑测试代码基本步骤
在进行业务编写之前,我们要了解数据库测试的基本步骤:
* //第一个步骤:备份数据库中的数据,将数据库中的数据写入到硬盘中的某个位置 * //第二个步骤:插入提前准备好的数据 * //第三个步骤:进行业务逻辑测试 * //第四个步骤:还原数据库中的数据
3.4.1、UserDAO编写
public class UserDao {
/**
* 通过id找用户
* @param id
* @return
*/
public User findUserById(String id) throws SQLException {
return JdbcUtils.queryRunner().query("select * from t_user where id = ?",new BeanHandler<User>(User.class),
"1583005060883841026");//根据自己需要测试的编写
}
}
3.4.2、TestUserDao
3.4.2.1、备份数据库中数据
import com.mq.utils.JdbcUtils;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.XmlDataSet;
import org.dbunit.ext.mssql.MsSqlConnection;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.SQLException;
/**
* @author
* @version V1.0.0
* @date 2022/10/21 16:40
* //第一个步骤:备份数据库中的数据,将数据库中的数据写入到硬盘中的某个位置
* //第二个步骤:插入提前准备好的数据
* //第三个步骤:进行业务逻辑测试
* //第四个步骤:还原数据库中的数据
*/
public class TestUserDAO {
private UserDAO userDAO;
private DatabaseConnection conn;//会出错,不同数据库中表名重复
// private IDatabaseConnection conn;
@Before
public void init() throws SQLException {
userDAO = new UserDAO();
conn = new DatabaseConnection(JdbcUtils.getConnection());
}
@Test
public void testFindUserById() throws Exception {
//备份所有表中的数据
//backAllTables();
backOneTable();
}
/**
* 备份表中的所有数据
*/
private void backAllTables() throws IOException, SQLException, DataSetException {
IDataSet dataSet = conn.createDataSet();
FlatXmlDataSet.write(dataSet,new FileOutputStream(new File("D:/back.xml")));
}
private void backOneTable() throws Exception{
QueryDataSet queryDataSet = new QueryDataSet(conn);
queryDataSet.addTable("t_user");
//XmlDataSet.write(queryDataSet,new FileOutputStream(new File("D:/back.xml")));
FlatXmlDataSet.write(queryDataSet,new FileOutputStream(new File("D:/back.xml")));
}
注意:可能会出现数据库表名重复或 不存在问题,原因是由于数据库连接访问权限过大,解决方法可以删除相同表名,更改表名或改变权限。
运行结果:
back.xml:
3.4.2.2、编写测试数据
编写测试用例文件
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<t_user id="1583005060883841026" username="xma" password="123321" version="0" create_time="2022-10-24 11:06:08.0" update_time="2022-10-26 11:06:12.0" deleted="0"/>
<t_user id="1583005060883841036" username="ma" password="123123" version="0" create_time="2022-10-04 11:56:13.0" update_time="2022-10-14 11:56:17.0" deleted="0"/>
</dataset>
清空数据库数据,并插入测试数据
/**
* 插入测试数据到数据库
*/
private void insertTestData() throws DatabaseUnitException, IOException, SQLException {
IDataSet dataSet = new FlatXmlDataSet(new InputSource(
TestUserDAO.class.getClassLoader().getResourceAsStream("user-test.xml")
));
DatabaseOperation.CLEAN_INSERT.execute(conn,dataSet);
}
执行完成后:观察数据库数据是否改变
成啦!!!!
现在开始测试咯!
@Test
public void testFindUserById() throws Exception {
//备份所有表中的数据
//backAllTables();
//backOneTable();
//插入测试数据到数据库中去
//insertTestData();
//测试方法的准确性
User user = userDAO.findUserById("1583005060883841026");//调用查询方法
//接下来进行断言
System.out.println(user);
Assert.assertEquals(exUser.getId(),user.getId());
Assert.assertEquals(exUser.getUsername(),user.getUsername());
Assert.assertEquals(exUser.getPassword(),user.getPassword());
}
结果:
完美测试成功!
测试完成之后,需要还原数据库中的数据:
/**
* 还原数据库表中的数据
*/
private void resumeTable() throws IOException, SQLException, DatabaseUnitException {
IDataSet dataSet = new FlatXmlDataSet(new InputSource(
new FileInputStream(new File("D:/back.xml"))));
DatabaseOperation.CLEAN_INSERT.execute(conn,dataSet);
}
还原成功!
最后附上TestUserDAO完整代码:
package com.mq.dbunit;
import com.mq.pojo.User;
import com.mq.utils.JdbcUtils;
import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.XmlDataSet;
import org.dbunit.ext.mssql.MsSqlConnection;
import org.dbunit.operation.DatabaseOperation;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.InputSource;
import java.io.*;
import java.sql.SQLException;
/**
* @author
* @version V1.0.0
* @date 2022/10/21 16:40
* //第一个步骤:备份数据库中的数据,将数据库中的数据写入到硬盘中的某个位置
* //第二个步骤:插入提前准备好的数据
* //第三个步骤:进行业务逻辑测试
* //第四个步骤:还原数据库中的数据
*/
public class TestUserDAO {
private UserDAO userDAO;
private DatabaseConnection conn;//会出错,不同数据库中表名重复
private User exUser;
// private IDatabaseConnection conn;
@Before
public void init() throws SQLException {
userDAO = new UserDAO();
conn = new DatabaseConnection(JdbcUtils.getConnection());
exUser = new User("1583005060883841026","xma","123321");//测试的User
}
@Test
public void testFindUserById() throws Exception {
//备份所有表中的数据
// backAllTables();
backOneTable();
//插入测试数据到数据库中去
insertTestData();
//测试方法的准确性
User user = userDAO.findUserById("1583005060883841026");//调用查询方法
//接下来进行断言
System.out.println(user);
Assert.assertEquals(exUser.getId(),user.getId());
Assert.assertEquals(exUser.getUsername(),user.getUsername());
Assert.assertEquals(exUser.getPassword(),user.getPassword());
//测试完成,还原数据库中数据
resumeTable();
}
/**
* 还原数据库表中的数据
*/
private void resumeTable() throws IOException, SQLException, DatabaseUnitException {
IDataSet dataSet = new FlatXmlDataSet(new InputSource(
new FileInputStream(new File("D:/back.xml"))));
DatabaseOperation.CLEAN_INSERT.execute(conn,dataSet);
}
/**
* 插入测试数据到数据库
*/
private void insertTestData() throws DatabaseUnitException, IOException, SQLException {
IDataSet dataSet = new FlatXmlDataSet(new InputSource(
TestUserDAO.class.getClassLoader().getResourceAsStream("user-test.xml")
));
DatabaseOperation.CLEAN_INSERT.execute(conn,dataSet);
}
/**
* 备份表中的所有数据
*/
private void backAllTables() throws IOException, SQLException, DataSetException {
IDataSet dataSet = conn.createDataSet();
FlatXmlDataSet.write(dataSet,new FileOutputStream(new File("D:/back.xml")));
}
private void backOneTable() throws Exception{
QueryDataSet queryDataSet = new QueryDataSet(conn);
queryDataSet.addTable("t_user");
//XmlDataSet.write(queryDataSet,new FileOutputStream(new File("D:/back.xml")));
FlatXmlDataSet.write(queryDataSet,new FileOutputStream(new File("D:/back.xml")));
}
}
4、SpringTest
4.1、编写基类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:bean-base.xml")
public class AbstractSpringTestCase {
}
4.2、使用
public class TestUserDAO extends AbstractSpringTestCase {
@Autowired
private UserDAO userDAO;
@Test
public void testA(){
System.out.println("------------:"+userDAO);
}
}
5、 SpringBootTest的使用
5.1、编写springboot的测试基类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class})
public class AbstractSpringTestCase {
}
5.2、 编写dbunit测试的基类
public class AbstractDbunitTestCase extends AbstractSpringTestCase{
private File tempFile;
private DatabaseConnection databaseConnection;
//测试数据的流
private InputStream testDataIn;
public AbstractDbunitTestCase(InputStream testDataIn){
this.testDataIn=testDataIn;
}
//这里在Set方法中去进行赋值
public void setDatabaseConnection(Connection connection) {
this.databaseConnection=new DatabaseConnection(connection);
}
/**
* 备份多张表的数据
* @param tabNames
*/
public void backManyTable(String... tabNames) throws Exception {
QueryDataSet queryDataSet = new QueryDataSet(databaseConnection);
for (String tabName:tabNames) {
queryDataSet.addTable(tabName);
}
tempFile=File.createTempFile("back",".xml");
FlatXmlDataSet.write(queryDataSet,new FileOutputStream(tempFile));
}
/**
* 备份一张表
* @param tableName
*/
public void backOneTable(String tableName) throws Exception {
backManyTable(tableName);
}
/*
* 插入测试数据
*/
public void insertTestData() throws Exception {
IDataSet dataSet=new FlatXmlDataSet(new InputSource(testDataIn));
DatabaseOperation.CLEAN_INSERT.execute(databaseConnection, dataSet);
}
/**
* 还原表的数据
*/
public void resumeTable() throws Exception {
IDataSet dataSet=new FlatXmlDataSet(new InputSource(new FileInputStream(tempFile)));
DatabaseOperation.CLEAN_INSERT.execute(databaseConnection, dataSet);
}
}
5.3、编写测试
public class TestUserMapper extends AbstractDbunitTestCase {
@Autowired
private UserMapper userMapper;
@Autowired
private DataSource dataSource;
public TestUserMapper() {
super(
TestUserMapper.class.getClassLoader()
.getResourceAsStream("user-test.xml"));
}
@Before
public void init() throws Exception {
setDatabaseConnection(dataSource.getConnection());
backOneTable("t_user");
insertTestData();
}
/**
* 查询所有数据
*/
@Test
public void testSelectList(){
List<User> userList = userMapper.selectList();
//下面来一个数据的大小
Assert.assertEquals(2,userList.size());
}
@Test
public void testAddUser(){
User user = new User();
user.setUsername("中国好");
user.setPassword("你懂的");
user.setId("222222");
userMapper.addUser(user);
//直接进行查询
List<User> userList = userMapper.selectList();
Assert.assertEquals(3,userList.size());
}
/**
* 更新数据的测试
*/
@Test
public void testUpdateUser(){
//要将这个更新成 xxxx
User user = new User("1582976230446129199", "xxxx", "xxxx");
//接下来怎么玩呢?
userMapper.updateUser(user);
//接下来查询出来断言
User user1 = userMapper.selectOne("1582976230446129199");
//接下来进行断言
Assert.assertEquals(user.getId(),user1.getId());
Assert.assertEquals(user.getUsername(),user1.getUsername());
Assert.assertEquals(user.getPassword(),user1.getPassword());
}
@After
public void resumeTable1() throws Exception {
resumeTable();
}
}
以上便是java中基本测试的使用。
撒花!