Devops學習實踐(六) Eclipse集成TestNg,mock編寫單元測試

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章