五、测试-六、优化方案
五、测试
1.单元测试
单元测试的方式是黑盒测试,即通过每个环节的输入输出情况进行测试。程序由四个类组成,对应生成四个测试类,使用Junit5对其中的主要方法进行测试。测试的大致思路是预先设计较为简单的数独用例,生成新的对象,运行方法,并将阶段性的结果与预先计算的结果相比较。
有些方法具有返回值,便于设计测试类,例如对 Main::isNumber()进行测试:
//Main::isNumber public static boolean isNumber(String str){ String reg = "^[0-9]+?$"; return str.matches(reg); }//MainTest::testIsNumber @Test final void testIsNumber() { Assert.assertEquals(true, Main.isNumber("100")); Assert.assertEquals(true, Main.isNumber("10a")); Assert.assertEquals(true, Main.isNumber("4.5")); }
对于更多没有返回值的方法,采取验证阶段结果的方法,预测方法执行后对象属性的变化并加以验证。例如对SudokuGenerator::creatFirstBlock()方法的测试,该方法执行后生成数独的第一小宫。测试类中将第一小宫所有值求和验证是否完成。
@Test final void testCreateFirstBlock() { SudokuGenerator gen=new SudokuGenerator(1);//只生成一个终局,目的是让测试更容易进行 gen.createFirstBlock(); //阶段执行到被测方法后,不继续执行后续方法,开始验证当前成果 boolean status = true; int numCrit = 0; Sudoku su = gen.getSudoku(); for(int i=0;i<3;i++) { for(int j=0;j<3;j++) { numCrit+=su.get(i, j); } } //完成的一小宫求和应为45 if(numCrit!=45 || su.get(0, 0)!=2) status = false; Assert.assertEquals(true, status); }?
所有测试类及运行结果:
(其中failure是对被测方法进行了错误输入的验证)
2.系统测试
本环节主要测试不同输入指令的运行结果。对指令输入的可能情况进行等价类划分后得到测试用例表如下:
(空白部分表示输入为合法输入,程序正常运行)
3.覆盖率测试
结合系统测试的用例与单元测试,使用eclipse的插件检测覆盖率。在项目上右键 -> Coverage As -> 2 JUnit Test,就可以在控制台窗口看见覆盖率结果。
4.性能测试
下载并安装JProfile 11.1试用版, 需要注意的是,当执行插件安装时,需要关闭Eclipse。 在安装向导处选择IDE(eclipse 4.8),点击“Integrate”选择IDE的安装路径。完成后,使用“-clean”参数在命令行窗口打开eclipse,在Window -> Perspective -> Customize Perspective -> Action Set Availability, 找到Profile并选中。之后就可以在main类窗口右键 -> Profile As运行JProfile。需要注意的是,因为项目需要输入参数,所以选择“Profile Configurations...”填写指令在运行。
在JProfile中,我们主要看的是CPU的占用情况。因此我查看了call tree和hot pot的图表。
从图表中可以看出与文件写入有关的FileWrite与BufferedWriter明显占用了很大资源,导致整体程序运行较慢。
六、优化方案
从性能分析中不难看出,由于算法采取单个写入,写入过程给整个程序造成较大拖累,是用时最长的环节。因此下一步优化提速的思路就是将单个写入转换成整体写入,将所有终局数独保存在同一个BufferedWriter中来提升效率。例如,按照一定的格式,开一个较大的int数组。String类由于操作涉及多次new,尽量都替代为StringBuffer的操作来节省空间。此外,本次算法中使用位运算标记数独中数字的使用情况,在判断的逻辑上已经比较简明,今后可以再思考如何从回溯的算法方面继续提高效率。
由于时间问题没有对程序进行具体的优化,谨记录进一步的想法。