一、实验目的
1)掌握单元测试的方法
2)学习XUnit测试原理及框架;
3)掌握使用测试框架进行单元测试的方法和过程。
二、实验内容与要求
1、了解单元测试的原理与框架
2、对“结对编程”实验的程序模块(类)进行单元测试,提交单元测试报告
(1)程序源码
1 package lifegame; 2 3 public class Methods { 4 public Methods() { 5 } 6 7 //该方法检测所有位置,并返回对应位置的point数组 8 //用point数组,记录对应位置下一轮的状态,1下一代死,2下一代继续活,3下一代复活 9 public int[] check(String[][] lifeMap, int[] point) { 10 11 int n = 0; 12 //统计周围邻居的情况 13 for (int i = 0; i < 4; i++) { 14 for (int j = 0; j < 4; j++) { 15 16 /* 17 规则如下,进行判断 18 ( 1)一个人可以有8个邻居; 19 ( 2)一个人若只有一个邻居,在下一代会孤独的死去; 20 (3)若有2或3个邻居,在下一代依然活着; 21 (4)若有4个或以上邻居,在下一代会因拥挤而死; 22 (5)死去的人若有3个邻居,在下一代会复活; 23 (6)所有的死去或复活都在下一代变化时同时发生。 24 */ 25 26 //用life变量记录周围活着的邻居个数 27 int life = 0; 28 29 //1.判断正下方的位置 30 if (i + 1 < 4 && lifeMap[i + 1][j].equals("●")) { 31 life++; 32 } 33 34 //2.判断右下位置 35 if (i + 1 < 4 && j + 1 < 4 && lifeMap[i + 1][j + 1].equals("●")) { 36 life++; 37 } 38 39 //3.判断左下位置 40 if (i + 1 < 4 && j - 1 >= 0 && lifeMap[i + 1][j - 1].equals("●")) { 41 life++; 42 } 43 44 //4.判断右侧位置 45 if (j + 1 < 4 && lifeMap[i][j + 1].equals("●")) { 46 life++; 47 } 48 49 //5.判断左侧位置 50 if (j - 1 >= 0 && lifeMap[i][j - 1].equals("●")) { 51 life++; 52 } 53 54 //6.判断正上方位置 55 if (i - 1 >= 0 && lifeMap[i - 1][j].equals("●")) { 56 life++; 57 } 58 59 //7.判断右上位置 60 if (i - 1 >= 0 && j + 1 < 4 && lifeMap[i - 1][j + 1].equals("●")) { 61 life++; 62 } 63 64 //8.判断左上位置 65 if (i - 1 >= 0 && j - 1 >= 0 && lifeMap[i - 1][j - 1].equals("●")) { 66 life++; 67 } 68 69 //用一个数组,记录对应位置下一轮的状态,1下一代死,2下一代继续活,3下一代复活 70 if (lifeMap[i][j].equals("●")) { 71 if (life == 1) 72 point[n] = 1; 73 else if (life == 2 || life == 3) 74 point[n] = 2; 75 else if (life >= 4) 76 point[n] = 1; 77 } else { 78 if (life == 3) 79 point[n] = 3; 80 } 81 n++; 82 } 83 } 84 return point; 85 } 86 87 public String[][] getNext(String[][] lifeMap, int[] point) { 88 89 int n = 0; 90 for (int i = 0; i < 4; i++) { 91 for (int j = 0; j < 4; j++) { 92 93 //变更状态 94 if (point[n] == 1) 95 lifeMap[i][j] = "○"; 96 if (point[n] == 3) 97 lifeMap[i][j] = "●"; 98 99 n++; 100 } 101 } 102 return lifeMap; 103 } 104 105 public void printLifeMap(String[][] lifeMap){ 106 for (int i = 0; i < 4; i++) { 107 for (int j = 0; j < 4; j++) { 108 if (j == 3) 109 System.out.println(lifeMap[i][j] + " "); 110 else 111 System.out.print(lifeMap[i][j] + " "); 112 } 113 } 114 } 115 }
1 package lifegame; 2 3 import java.util.Random; 4 import java.util.Scanner; 5 6 /* 7 (1)生命小游戏的邻居为上下左右和斜对角一共八个位置 8 (2)默认选择4*4的格子 9 (3)将默认的格子初始化,并打印输出 10 (4)使用Methods中的方法生成下一轮的状态,并打印 11 */ 12 13 public class LifeGame { 14 public static void main(String[] args) { 15 16 //设置一个二维数组存储所有的格子 17 String[][] lifeMap = new String[4][4]; 18 19 Methods me = new Methods(); 20 21 //将所有格子进行初始化输入,死为0,活为1 22 //随机生成各个位置的邻居情况 23 for (int i = 0; i < 4; i++) { 24 for (int j = 0; j < 4; j++) { 25 26 int num = new Random().nextInt(2); 27 if (num == 1) 28 lifeMap[i][j] = "●"; 29 else if (num == 0) 30 lifeMap[i][j] = "○"; 31 } 32 } 33 34 //打印格子初始状态 35 System.out.println("初始状态为:"); 36 me.printLifeMap(lifeMap); 37 System.out.println("==========="); 38 39 Scanner scan = new Scanner(System.in); 40 int n = 0; 41 int num = 0;//记录变化的次数 42 while (n == 0) { 43 44 //用point数组,记录对应位置下一轮的状态,1下一代死,2下一代继续活,3下一代复活 45 int[] point = me.check(lifeMap, new int[16]); 46 47 //将获得下一次变化后的图形 48 lifeMap = me.getNext(lifeMap, point).clone(); 49 50 System.out.println("第" + (++num) + "次变化:"); 51 52 //打印出来 53 me.printLifeMap(lifeMap); 54 System.out.println("==========="); 55 56 System.out.println("输入0继续进行下一步,输入其他数字退出。"); 57 if(scan.hasNextInt()) { 58 n = scan.nextInt(); 59 } 60 else n = 1; 61 } 62 scan.close(); 63 } 64 }
(2)测试用例设计
check方法测试用例:
输入参数 | 期望结果 |
{{"●","●","○","○"}, {"○","●","○","○"}, {"○","○","○","○"}, {"●","○","○","●"}} |
{2,2,0,0,3,2,0,0,0,0,0,0,1,0,0,1} |
getNext方法测试用例:
输入参数 | 期望结果 |
---|---|
{{"●","●","○","○"}, {"○","●","○","○"}, {"○","○","○","○"}, {"●","○","○","●"}} {2,2,0,0,3,2,0,0,0,0,0,0,1,0,0,1} |
{{"●","●","○","○"}, {"●","●","○","○"}, {"○","○","○","○"}, {"○","○","○","○"}} |
(3)框架选择与安装过程
由于源程序是java程序,所以这里选择JUnit4作为测试框架。
JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
安装过程:
进入eclipse
(4)测试代码
1 package lifegame; 2 3 import static org.junit.Assert.*; 4 5 import java.lang.reflect.Array; 6 import java.util.ArrayList; 7 import java.util.Arrays; 8 9 import org.junit.Before; 10 import org.junit.Test; 11 12 public class MethodsTest { 13 14 @Before 15 public void setUp() throws Exception { 16 } 17 18 @Test 19 public void testCheck() { 20 Methods me = new Methods(); 21 String[][] teststr = {{"●","●","○","○"},{"○","●","○","○"},{"○","○","○","○"},{"●","○","○","●"}}; 22 int[] exp_result = {2,2,0,0,3,2,0,0,0,0,0,0,1,0,0,1}; 23 assertEquals(true, Arrays.equals(exp_result, me.check(teststr))); 24 } 25 26 @Test 27 public void testGetNext() { 28 Methods me = new Methods(); 29 String[][] teststr2 = {{"●","●","○","○"},{"○","●","○","○"},{"○","○","○","○"},{"●","○","○","●"}}; 30 int[] point = {2,2,0,0,3,2,0,0,0,0,0,0,1,0,0,1}; 31 String[][] exp_str = {{"●","●","○","○"},{"●","●","○","○"},{"○","○","○","○"},{"○","○","○","○"}}; 32 me.getNext(teststr2, point); 33 int c = 0; 34 for(int m = 0; m < 4; m++) { 35 for(int n = 0; n < 4; n++) { 36 if(exp_str[m][n].equals(teststr2[m][n])) c++; 37 } 38 } //判断生成串是否与期望串相等 39 assertEquals(16, c); 40 } 41 }
(5)测试结果与分析
测试结果如图:
经测试 check方法无法得到期望返回。对check方法代码段进行分析:
1 //用一个数组,记录对应位置下一轮的状态,1下一代死,2下一代继续活,3下一代复活 2 if (lifeMap[i][j].equals("●")) { 3 if (life == 1) 4 point[n] = 1; 5 else if (life == 2 || life == 3) 6 point[n] = 2; 7 else if (life >= 4) 8 point[n] = 1; 9 } else { 10 if (life == 3) 11 point[n] = 3; 12 }
发现未指明当life==0时的情况(所找到的游戏规则不严谨)
修改后:
1 //用一个数组,记录对应位置下一轮的状态,1下一代死,2下一代继续活,3下一代复活 2 if (lifeMap[i][j].equals("●")) { 3 if (life == 2 || life == 3) 4 point[n] = 2; 5 else 6 point[n] = 1; 7 } else { 8 if (life == 3) 9 point[n] = 3; 10 }
再运行单元测试:
通过测试!!!
3、push测试报告和测试代码到各自的github仓库
三、思考题:
比较以下二个工匠的做法,你认为哪种好?结合编码和单元测试,谈谈你的认识。
我觉得工匠一的做法比较好,在初始阶段就做好纠错的工作,避免成型之后进行纠错的困难与极大开销。
编码初期就应该时刻注意代码的整体构思,确定好类或函数的接口参数,尽可能的避免不必要的错误。
实验小结
本次实验是对单元测试的考察,由于我们的程序基本都属于字符串的生成与检查,再进行单元测试时,assertEquals()方法不能直接使用,较为麻烦。
更令人意想不到的是check方法没有通过测试,虽然后来找出了原因,但这也让我对单元测试的重要性有了进一步的认识。该方法中存在的错误虽然不会导致程序无法运行,但也是很大的隐患(原本该置“1”的元素没有执行),如果不是在单元测试中发现了问题,可能这个Bug会存在很久。