一、實驗目的
1)掌握單元測試的方法
2) 學習XUnit測試原理及框架;
3)掌握使用測試框架進行單元測試的方法和過程。
二、實驗內容與要求
1、了解單元測試的原理與框架
1.1 單元測試原理
單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函數,Java里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人為規定的最小的被測功能模塊。單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。單元測試是由程序員自己來完成,最終受益的也是程序員自己。可以這么說,程序員有責任編寫功能代碼,同時也就有責任為自己的代碼編寫單元測試。執行單元測試,就是為了證明這段代碼的行為和我們期望的一致。
單元測試的內容包括
模塊接口測試、局部數據結構測試、路徑測試、錯誤處理測試、邊界測試
(1)模塊接口測試
模塊接口測試是單元測試的基礎。只有在數據能正確流入、流出模塊的前提下,其他測試才有意義。模塊接口測試也是集成測試的重點,這里進行的測試主要是為后面打好基礎。測試接口正確與否應該考慮下列因素:
-輸入的實際參數與形式參數的個數是否相同
-輸入的實際參數與形式參數的屬性是否匹配
-輸入的實際參數與形式參數的量綱是否一致
-調用其他模塊時所給實際參數的個數是否與被調模塊的形參個數相同;
-調用其他模塊時所給實際參數的屬性是否與被調模塊的形參屬性匹配;
-調用其他模塊時所給實際參數的量綱是否與被調模塊的形參量綱一致;
-調用預定義函數時所用參數的個數、屬性和次序是否正確;
-是否存在與當前入口點無關的參數引用;
-是否修改了只讀型參數;
-對全程變量的定義各模塊是否一致;
-是否把某些約束作為參數傳遞。
如果模塊功能包括外部輸入輸出,還應該考慮下列因素:
-文件屬性是否正確;
-OPEN/CLOSE語句是否正確;
-格式說明與輸入輸出語句是否匹配;
-緩沖區大小與記錄長度是否匹配;
-文件使用前是否已經打開;
-是否處理了文件尾;
-是否處理了輸入/輸出錯誤;
-輸出信息中是否有文字性錯誤。
-局部數據結構測試;
-邊界條件測試;
-模塊中所有獨立執行通路測試;
(2)局部數據結構測試
檢查局部數據結構是為了保證臨時存儲在模塊內的數據在程序執行過程中完整、正確,局部功能是整個功能運行的基礎。重點是一些函數是否正確執行,內部是否運行正確。局部數據結構往往是錯誤的根源,應仔細設計測試用例,力求發現下面幾類錯誤:
-不合適或不相容的類型說明;
-變量無初值;
-變量初始化或省缺值有錯;
-不正確的變量名(拼錯或不正確地截斷);
-出現上溢、下溢和地址異常。
(3)邊界條件測試
邊界條件測試是單元測試中最重要的一項任務。眾所周知,軟件經常在邊界上失效,采用邊界值分析技術,針對邊界值及其左、右設計測試用例,很有可能發現新的錯誤。邊界條件測試是一項基礎測試,也是后面系統測試中的功能測試的重點,邊界測試執行的較好,可以大大提高程序健壯性。
(4)獨立路徑測試
在模塊中應對每一條獨立執行路徑進行測試,單元測試的基本任務是保證模塊中每條語句至少執行一次。測試目的主要是為了發現因錯誤計算、不正確的比較和不適當的控制流造成的錯誤。具體做法就是程序員逐條調試語句。常見的錯誤包括:
-誤解或用錯了算符優先級;
-混合類型運算;
-變量初值錯;
-精度不夠;
-表達式符號錯。
(5)錯誤處理測試
檢查模塊的錯誤處理功能是否包含有錯誤或缺陷。例如,是否拒絕不合理的輸入;出錯的描述是否難以理解、是否對錯誤定位有誤、是否出錯原因報告有誤、是否對錯誤條件的處理不正確;在對錯誤處理之前錯誤條件是否已經引起系統的干預等。
通常單元測試在編碼階段進行。在源程序代碼編制完成,經過評審和驗證,確認沒有語法錯誤之后,就開始進行單元測試的測試用例設計。利用設計文檔,設計可以驗證程序功能、找出程序錯誤的多個測試用例。對於每一組輸入,應有預期的正確結果。
1.2 測試框架
xUnit是各種代碼驅動測試框架的統稱,這些框架可以測試 軟件的不同內容(單元),比如函數和類。xUnit框架的主要優點是,它提供了一個自動化測試的解決方案。可以避免多次編寫重復
底層是xUnit的framwork,xUnit的類庫,提供了對外的功能方法、工具類、api等
TestCase(具體的測試用例)去使用framwork
TestCase執行后會有TestResult
使用TestSuite控制TestCase的組合
TestRunner執行器,負責執行case
TestListener過程監聽,監聽case成功失敗以及數據結果,輸出到結果報告中
1.3 面向特定語言的,基於xUnit框架的自動化測試框架
Junit : 主要測試用Java語言編寫的代碼
CPPunit:主要測試用C++語言編寫的代碼
unittest , PyUnit:主要測試用python語言編寫的代碼
MiniUnit: 主要用於測試C語言編寫的代碼
三、實驗過程
1.源碼
import java.util.Scanner;
public int t_answer=0;//記錄答對題目數
public boolean com=false;//出題成功判斷標志
void check(int answer)
{
@SuppressWarnings("resource")
Scanner scanner=new Scanner(System.in);
int u_answer;
System.out.println("請回答:");
while(true){
try{
u_answer=scanner.nextInt();
break;
}
catch (Exception e) {
System.out.println("您輸入的不是正整數,請重新輸入");
scanner.next();
}
}
if(u_answer==answer)
{
System.out.println("答案正確");
this.t_answer++;
}
else
{
System.out.println("答案錯誤");
}
}
int p_add(int left,int right)
{
int answer;
answer=left+right;
if(answer<=100)
{
System.out.println(left+"+"+right+"=");
this.com=true;
}
return answer;
}
int p_sub(int left,int right)
{
int answer;
answer=left-right;
if(answer>=0)
{
System.out.println(left+"-"+right+"=");
this.com=true;
}
return answer;
}
int p_mul(int left,int right)
{
int answer;
answer=left*right;
if(answer<=100)
{
System.out.println(left+"*"+right+"=");
this.com=true;
}
return answer;
}
int p_div(int left,int right)
{
int answer;
int success;//余數判斷標志
success=left%right;
answer=left/right;
if(right!=0&success==0)
{
System.out.println(left+"/"+right+"=");
this.com=true;
}
return answer;
}
{
int answer=0;//存儲答案
int left;//產生式左部
int right;//產生式右部
producer producer=new producer();
@SuppressWarnings("resource")
Scanner scanner=new Scanner(System.in);
Random random=new Random();
System.out.println("請輸入要出的題目個數:");
int p_number=0;
while(true){
try {
p_number=scanner.nextInt();/*輸入的出題數目*/
break;
} catch (Exception e) {
System.out.println("您輸入的不是正整數,請重新輸入");
scanner.next();
}
}
for(int i=0;i<p_number;i++)
{
int r=random.nextInt(4)+1;/*隨機式子類型判斷*/
//System.out.println(r);
while(!(producer.com)){
left=random.nextInt(101);
right=random.nextInt(101);
//System.out.println("("+left+","+right+")");
switch (r) {
case 1:
/*產生加法式*/
answer=producer.p_add(left,right);
if(producer.com)
producer.check(answer);
break;
case 2:
/*產生減法式*/
answer=producer.p_sub(left,right);
if(producer.com)
producer.check(answer);
break;
case 3:
/*產生乘法式*/
answer=producer.p_mul(left,right);
if(producer.com)
producer.check(answer);
break;
case 4:
/*產生除法式*/
answer=producer.p_div(left,right);
if(producer.com)
producer.check(answer);
break;
}
}
producer.com=false;
}
System.out.println("您的得分是"+producer.t_answer*100/p_number);/*計算並打印得分*/
}
}
2.測試用例設計
加法測試用例
2+3=
55+45=
3-2=
3-3=
乘法測試用例
乘積小於等於100
設計正常測試用例
2*3=6
0乘用例
0*3=0
邊界測試用例
10*10=100
11*10=110
期望輸出
乘法測試結果如下
2*3=
0*3=
10*10=
除法測試用例
100以內除法,除數不可為0且必須整除
正常測試用例
6/2=3
不可整除用例
5/3=1
被除數為0用例
0/3=0
由於除數生成有0值判斷,會提示異常,且代碼內已避免此情況,此處不做處理
期望輸出
除法測試結果如下
6/3=
0/3=
核對測試用例
核對兩次答案1
分別輸入A,+,1,0
期望輸出
請回答:
A
您輸入的不是正整數,請重新輸入
+
您輸入的不是正整數,請重新輸入
1
答案正確
請回答:
0
答案錯誤
3.測試框架介紹,安裝過程
junit框架介紹



4.測試代碼
import org.junit.Before;
import org.junit.Test;
producer producer=new producer();
@Before
public void setUp() throws Exception {
}
public void tearDown() throws Exception {
}
public void testCheck() {
producer.check(1);
producer.check(1);
}
public void testP_add() {
System.out.println("加法測試結果如下");
assertEquals(5,producer.p_add(2,3));
assertEquals(100,producer.p_add(55,45));
assertEquals(101,producer.p_add(56,45));
}
public void testP_sub() {
System.out.println("減法測試結果如下");
assertEquals(1,producer.p_sub(3,2));
assertEquals(0,producer.p_sub(3,3));
assertEquals(-1,producer.p_sub(1,2));
}
public void testP_mul() {
System.out.println("乘法測試結果如下");
assertEquals(6,producer.p_mul(2,3));
assertEquals(0,producer.p_mul(0,3));
assertEquals(100,producer.p_mul(10,10));
assertEquals(110,producer.p_mul(11,10));
}
public void testP_div() {
System.out.println("除法測試結果如下");
assertEquals(2,producer.p_div(6,3));
assertEquals(1,producer.p_div(5,3));
assertEquals(0,producer.p_div(0,3));
}
5.測試結果
初始測試用例結果:
①由於開始時,並未對答案輸入進行異常處理,當輸入答案的時候輸入非數字會提示異常退出程序
調整前:
void check(int answer)
{
@SuppressWarnings("resource")
Scanner scanner=new Scanner(System.in);
int u_answer;
System.out.println("請回答:");
u_answer=scanner.nextInt();
if(u_answer==answer)
{
System.out.println("答案正確");
t_answer++;
}
else
{
System.out.println("答案錯誤");
}
}
並未進行異常處理
調整后:
void check(int answer)
{
@SuppressWarnings("resource")
Scanner scanner=new Scanner(System.in);
int u_answer;
System.out.println("請回答:");
while(true){
try{
u_answer=scanner.nextInt();
break;
}
catch (Exception e) {
System.out.println("您輸入的不是正整數,請重新輸入");
scanner.next();
}
}
if(u_answer==answer)
{
System.out.println("答案正確");
this.t_answer++;
}
else
{
System.out.println("答案錯誤");
}
}
增加了異常處理。
②方法實現不夠得當
調整前
static void p_add(int answer)
{
int left;
int right;
Random random=new Random();
do{
left=random.nextInt(101);
right=random.nextInt(101);
answer=left+right;
}while(answer>100);
System.out.println(left+"+"+right+"=");
}
每個計算式重復使用隨機數產生,產生了容易代碼,且參數傳遞方式不合理
調整后
int p_add(int left,int right)
{
int answer;
answer=left+right;
if(answer<=100)
{
System.out.println(left+"+"+right+"=");
this.com=true;
}
return answer;
}
將隨機數的產生置於main函數中,產生輸入參數left,right,答案結果作為返回值處理。
上述調整完畢后
測試結果如下圖,同預期結果相符,不合約束的測試用例並未產生實際輸出結果
加法測試用例對比
輸入 | 預期結果 | 實際結果 |
2+3=3 |
2+3= | ![]() |
55+45=100 | 55+45= | |
56+45=101 | 無輸出 |
減法測試用例對比
輸入 | 預期結果 | 實際結果 |
3-2=1 | 3-2= | ![]() |
3-3=0 | 3-3= | |
1-2=-1 | 無輸出 |
乘法測試用例對比
輸入 | 預期結果 | 實際結果 |
2*3=6 | 2*3= | |
0*3=0 | 0*3= | |
10*10=100 | 10*10= | |
11*10=110 | 無輸出 |
除法測試用例對比
輸入 | 預期結果 | 實際結果 |
6/2=3 | 6/2= | ![]() |
5/3=1 | 無輸出 | |
0/3=0 | 0/3= |
校驗模塊測試用例對比
輸入 | 預期結果 | 實際結果 |
字母A | 您輸入的不是正整數,請重新輸入 | ![]() |
字符+ | 您輸入的不是正整數,請重新輸入 | |
答案1,輸入答案1 | 答案正確 | |
答案1,輸入答案0 | 答案錯誤 |
實驗小結
1.簡單了解了junit框架的基本使用方法。
2.通過單元測試了解了編碼習慣對進行測試的重要影響,有利於督促養成更好的編碼習慣。
3.加強了編碼過程中對異常處理部分的認識和理解。
4.了解了測試用例基本的設計思路。
思考題
比較以下二個工匠的做法,你認為哪種好?結合編碼和單元測試,談談你的認識。
答:我覺得工匠一的做法好,從編碼和單元測試角度來看,一邊測試一邊編碼有助於養成良好的編碼習慣,同時當出現問題時可以及時修正,有效減少工作量或者利益損失。
附錄:
程序github地址:
https://github.com/rezero0523/sizeyunsuan_jiedui
程序運行結果示例