單元測試也是開發中面臨的一個重要工作,出了我們熟悉的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.javapackage 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">