单元测试也是开发中面临的一个重要工作,出了我们熟悉的junit,还可以采用testNg来实现这项工作。并且我们可以把它集成到Jenkins里面。本节开始介绍如何使用Jenkins与Ant、TestNg、mock进行单元测试并提高测试覆盖。首先第一部分是IDE环境(Eclipse)如何集成TestNg,并且与Mock一起完成测试代码编写
下面就就介绍一下整个过程。:
目标:
1、在eclipse中集成TestNg
2、编写测试类测试一般类
2、 通过powermock编写静态方法和调用静态类测试
一、集成TestNg
集成TestNg 分两部分,首先是我们开发工具里面集成TestNg,然后是在Jenkins中集成(下一节再说),在Jenkins中集成的目的是通过一些Jenkins的分析工具来分析项目的测试覆盖率等等。
1、Eclipse中集成TestNg
可以通过install 方式从http://beust.com/eclipse 从网站进行更新
2、因为要测试静态类和静态方法,所以需要引入powermock进行,需要注意的是powermock是基于mock基础上,分junit 和testng 两套框架的,所以下载的时候需要根据自己的工程进行区分。因为后期是为了在jenkins+testNg中使用,所以本次测试实践采用的是testNg路线。
Powermock 下载地址https://github.com/powermock/powermock/wiki/Downloads
进入后,下载基于testNg的 最新版本,如下图中的红色内容
注意 powermock有两套框架基于junit 和testNg,这两套是不同的不能混用。
二、准备测试工程
编写mock测试实践,主要通过demo进行日常常见的几个测试问题:
1、 普通类的测试
2、测试静态方法
3、测试静态类(如数据库连接类)
在构造的这个测试demo例子中, student 是实体类, StudentDao定义了一些student的操作接口, StudentDaoImpl 是操作接口的一个数据库的实现,在这个实现里面,会调用 DBOpt 进行数据库的操作。在DBOpt中,会调用 DBUtil 工具类(静态),进行数据库的连接。另外还写了StudentUtils 类(含静态方法),服务接口类StudentService
下面先给出待测试工程的结构
首先列出Student和StudentDao,StudentDaoImpl 三个类的代码
Student.java
package com.study.testngproj.entity;
public class Student {
int StuNumber;
public int getStuNumber() {
return StuNumber;
}
public void setStuNumber(int stuNumber) {
StuNumber = stuNumber;
}
String Name;
String BirthDay;
String Sexual;
String Grade;
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getBirthDay() {
return BirthDay;
}
public void setBirthDay(String birthDay) {
BirthDay = birthDay;
}
public String getSexual() {
return Sexual;
}
public void setSexual(String sexual) {
Sexual = sexual;
}
public String getGrade() {
return Grade;
}
public void setGrade(String grade) {
Grade = grade;
}
@Override
public String toString() {
return "Student [StuNumber=" + StuNumber + ", Name=" + Name
+ ", BirthDay=" + BirthDay + ", Sexual=" + Sexual + ", Grade="
+ Grade + ", getClass()=" + getClass() + ", hashCode()="
+ hashCode() + ", toString()=" + super.toString() + "]";
}
}
StudentDao.java
package com.study.testngproj.entity.dao;
import com.study.testngproj.entity.Student;
public interface StudentDao {
//add a new student
boolean addStudent(Student stu) ;
//del a student
boolean delStudent(Student std);
//query a student by student number
Student queryStudent( int stuNumber );
}
StudentDaoImpl.java
package com.study.testngproj.entity.dao.impl;
import java.util.ArrayList;
import java.util.List;
import com.study.testngproj.dbutil.DBOpt;
import com.study.testngproj.entity.Student;
import com.study.testngproj.entity.dao.StudentDao;
public class StudentDaoImpl implements StudentDao {
DBOpt dbopt = new DBOpt();
@Override
public boolean addStudent(Student stu) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean delStudent(Student std) {
// TODO Auto-generated method stub
return false;
}
@Override
public Student queryStudent(int stuNumber) {
// TODO Auto-generated method stub
// query stduent info from db
List myList = new ArrayList();
myList = dbopt.queryStudentByNumFromDB(stuNumber);
if(myList.size() != 1) {
return null;
}
else {
Student myStudent = new Student();
myStudent.setBirthDay( ((DBOpt)myList.get(0)).getStuBirthDay() );
myStudent.setName( ((DBOpt)myList.get(0)).getStuName() );
myStudent.setGrade( ((DBOpt)myList.get(0)).getStuGrade() );
myStudent.setSexual( ((DBOpt)myList.get(0)).getStuSexual() );
myStudent.setStuNumber( ((DBOpt)myList.get(0)).getStuNumber() );
return myStudent;
}
}
}
接着,附上DB操作的两个类, DBOpt 和DBUtil
DBUtil.java 代码如下
package com.study.testngproj.dbutil;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
/**
* <p>
* Title:
*
* @author not attributable
* @version 1.0
*/
public class DBUtil implements Serializable {
private static final long serialVersionUID = 1L;
public Integer getStuNumber() {
return stuNumber;
}
public void setStuNumber(Integer stuNumber) {
this.stuNumber = stuNumber;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public String getStuBirthDay() {
return stuBirthDay;
}
public void setStuBirthDay(String stuBirthDay) {
this.stuBirthDay = stuBirthDay;
}
public String getStuGrade() {
return stuGrade;
}
public void setStuGrade(String stuGrade) {
this.stuGrade = stuGrade;
}
public String getStuSexual() {
return stuSexual;
}
public void setStuSexual(String stuSexual) {
this.stuSexual = stuSexual;
}
private static Logger myLog = Logger.getLogger(DBUtil.class);
String resource = "sqlmapconf.xml";
Reader reader;
SqlMapClient sqlMap;
// here define db object begin
public Integer stuNumber;
public String stuName;
public String stuBirthDay;
public String stuGrade;
public String stuSexual;
// here define db object end;
int iCount = 0;
private final static DBUtil singleton = new DBUtil();
/**
* 返回这个类的静态实例的引用
*
* @param // //
* @return
*/
public static DBUtil getInstance() {
return singleton;
}
/** default constructor */
public DBUtil() {
//init();
}
// 初始化,获取sqlMap,reader
public synchronized boolean init() {
myLog.info("DbOpt created!!");
try {
reader = Resources.getResourceAsReader(resource);
sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
myLog.info("DbOpt sqlmap Object Create Success->"
+ sqlMap.getDataSource().getConnection().getMetaData()
.getURL()
+ ";UserName:"
+ sqlMap.getDataSource().getConnection().getMetaData()
.getUserName());
} catch (SQLException ee) {
ee.printStackTrace();
myLog.error("DbOpt create error:" + ee.getMessage());
return false;
} catch (IOException e) {
e.printStackTrace();
myLog.error("DbOpt create error:" + e.getMessage());
return false;
} finally {
myLog.info("DbOpt init complete");
}
return true;
}
public List queryForList( String arg, Object obj) {
List list = new ArrayList();
try {
this.sqlMap.startTransaction();
list = this.sqlMap.queryForList(arg, obj);
}
catch (SQLException e) {
myLog.error("queryForList error" + e.toString());
}
finally {
myLog.info("queryForList finally...");
try {
this.sqlMap.endTransaction();
} catch (SQLException e) {
e.printStackTrace();
myLog.error("queryForList finally error" + e.toString());
}
}
return list;
}
public static void main(String[] args) {
}
}
DBOpt.java 代码如下
package com.study.testngproj.dbutil;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
/**
* <p>
* Title:
*
* @author not attributable
* @version 1.0
*/
public class DBOpt {
// here define db object begin
public Integer stuNumber;
public String stuName;
public String stuBirthDay;
public String stuGrade;
public String stuSexual;
// end
public Integer getStuNumber() {
return stuNumber;
}
public void setStuNumber(Integer stuNumber) {
this.stuNumber = stuNumber;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public String getStuBirthDay() {
return stuBirthDay;
}
public void setStuBirthDay(String stuBirthDay) {
this.stuBirthDay = stuBirthDay;
}
public String getStuGrade() {
return stuGrade;
}
public void setStuGrade(String stuGrade) {
this.stuGrade = stuGrade;
}
public String getStuSexual() {
return stuSexual;
}
public void setStuSexual(String stuSexual) {
this.stuSexual = stuSexual;
}
private static Logger myLog = Logger.getLogger(DBOpt.class);
// 查询通过学生的ID号
public List queryStudentByNumFromDB(Integer stuNumber) {
List retlist = new ArrayList();
List mylist = new ArrayList();
this.setStuNumber(stuNumber);
myLog.info("queryStudentByNumFromDB,begin...");
mylist = DBUtil.getInstance().queryForList("queryStudentByNumFromDB",
this);
myLog.info("queryStudentByNumFromDB, result list size is:"
+ mylist.size() + ";");
for (int i = 0; i < mylist.size(); i++) {
String tmp ="Number=" +
((DBOpt) mylist.get(i)).getStuNumber() +";Name="+
((DBOpt) mylist.get(i)).getStuName()+";Birthday="+
((DBOpt) mylist.get(i)).getStuBirthDay()+";Grade="+
((DBOpt) mylist.get(i)).getStuGrade()+";Sexual="+
((DBOpt) mylist.get(i)).getStuSexual();
myLog.info("queryStudentByNumFromDB result:" + tmp);
retlist.add(tmp);
}
myLog.info("queryStudentByNumFromDB, End....");
return retlist;
}
public static void main(String[] args) {
}
}
最后附上StudentUtil 和 StudentService 代码
StudentUtil.java 代码如下:
package com.study.testngproj.entity;
public class StudentUtils {
public static int getStudent() {
throw new UnsupportedOperationException();
}
public static void createStudent( Student student) {
throw new UnsupportedOperationException();
}
}
StudentService.java 代码如下:
package com.study.testngproj;
import com.study.testngproj.entity.Student;
import com.study.testngproj.entity.StudentUtils;
public class StudentService {
public void createStudent(Student student) {
StudentUtils.createStudent(student);
}
}
三、下面编写测试代码
1、建立test目录,依据类的包结构,编写测试类
先将powermock解压后,整个目录拷贝到工程里面,通过右键加入到buildpath里面
2、建立测试目录,编写测试代码
最后的目录结构如下图:
一般建议在原类的包路径建立测试类,这样比较清晰,由于根目录区分开,所以也不容易混淆
3、在工程目录下,建一个testng.xml 文件,这个文件是为testng调用进行配置指引的
testng.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite">
<test name="Test">
<classes>
<class name="com.study.testngproj.entity.student"/>
<class name="com.study.testngproj.entity.dao.impl.StudentDaoImplTest"/>
</classes>
</test> <!-- Test -->
</suite> <!-- Suite -->
4、下面附上具体4个测试用例的代码
1)StudentServiceTest.java 测试静态方法
package com.study.testngproj;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.study.testngproj.entity.Student;
import com.study.testngproj.entity.StudentUtils;
import org.testng.annotations.Test;
@PrepareForTest(StudentUtils.class)
public class StudentServiceTest {
@Test
public void testCreateStudentWithMock() {
PowerMockito.mockStatic( StudentUtils.class);
Student stu = new Student();
PowerMockito.doNothing().when(StudentUtils.class);
final StudentService stuService = new StudentService();
stuService.createStudent(stu);
}
}
2)
StudentTest.java 的源码如下:
package com.study.testngproj.entity;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.study.testngproj.entity.Student;
public class StudentTest {
@Test
public void studentCreate() {
Student stuObj = new Student();
stuObj.setName("solo");
Assert.assertEquals(stuObj.getName(), "solo");
}
}
编写好测试代码后,可以右键执行:
3) StudentDaoImplTest.java
package com.study.testngproj.entity.dao.impl;
import org.testng.annotations.Test;
import org.testng.AssertJUnit;
import org.powermock.api.mockito.PowerMockito;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.study.testngproj.entity.Student;
import com.study.testngproj.entity.dao.impl.StudentDaoImpl;
public class StudentDaoImplTest {
private Student stuObj4Test;
@BeforeTest
public void init() {
stuObj4Test = new Student();
stuObj4Test.setGrade("4");
stuObj4Test.setStuNumber(10);
stuObj4Test.setName("solo");
stuObj4Test.setBirthDay("19880418");
stuObj4Test.setSexual("femal");
}
@Test
public void testQueryStudent( ) {
//生成一个dao对象,查询number 为 7的student对象,并确认student对象的name是不是 solo
StudentDaoImpl obj = PowerMockito.mock( StudentDaoImpl.class);
PowerMockito.when(obj.queryStudent(10)).thenReturn(stuObj4Test);
Student retObj = obj.queryStudent(10);
Assert.assertNotNull( retObj);
Assert.assertEquals(retObj.getName(), "solo");
}
}
4) DBOptTest.java 测试静态类
package com.study.testngproj.dbutil;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.List;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.reflect.Whitebox;
@PrepareForTest(DBUtil.class)
public class DBOptTest {
private DBOpt dbopt;
@BeforeTest
public void init() throws Exception{
dbopt = new DBOpt();
}
@Test
public void testQueryStudentByNumFromDBWithMock(){
DBUtil instanceMock = PowerMockito.mock( DBUtil.class );
Whitebox.setInternalState(DBUtil.class , "singleton", instanceMock);
String tmp ="Number=7;Name=solo;Birthday=20071001;Grade=4;Sexual=male";
List retList = new ArrayList();
DBOpt retDBOpt = new DBOpt();
retDBOpt.setStuBirthDay("20071001");
retDBOpt.setStuGrade("4");
retDBOpt.setStuName("solo");
retDBOpt.setStuSexual("male");
retDBOpt.setStuNumber(7);
retList.add(retDBOpt);
PowerMockito.when(instanceMock.queryForList("queryStudentByNumFromDB", dbopt) ).thenReturn(retList);
List myList =new ArrayList();
myList = dbopt.queryStudentByNumFromDB(7);
int count = myList.size();
assertEquals(count, 1);
System.out.println("output"+myList.get(0));
assertEquals( tmp, myList.get(0));
}
}
四:测试代码调试
1) 在单个类上,可以右键点击TestNg Test
如果代码没有错误,IDE打印如下内容:
[RemoteTestNG] detected TestNG version 6.12.0
PASSED: studentCreate
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================
===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================
2) 可以利用testng.xml 进行测试类的整体测试
通过Run Configurations,指定testng.xml
运行完成后,提示如下
=====
Creating E:\cwqwork\eclipse_workspace\StudyTestNg\test-output\Suite\Test.html
Creating E:\cwqwork\eclipse_workspace\StudyTestNg\test-output\Suite\Test.xml
PASSED: testQueryStudent
PASSED: studentCreate
PASSED: testCreateStudentWithMock
PASSED: testQueryStudentByNumFromDBWithMock
===============================================
Test
Tests run: 4, Failures: 0, Skips: 0
===============================================
===============================================
Suite
Total tests run: 4, Failures: 0, Skips: 0
===============================================
五、异常
1、测试静态类提示错误
FAILED: testQueryStudentByNumFromDBWithMock
org.powermock.api.mockito.ClassNotPreparedException:
[Ljava.lang.Object;@1b0b4509
The class com.study.testngproj.dbutil.DBUtil not prepared for test.
这个异常比较诡异,在myeclipse2015里面没有问题,在eclipse下一直有这个问题,通过在testng.xml 中加入
<suite name="Suite" verbose="10" parallel="false"
object-factory="org.powermock.modules.testng.PowerMockObjectFactory">