单元测试也是开发中面临的一个重要工作,出了我们熟悉的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">