一、JML 基础及工具链
JML语法
行注释://@annotation
块注释:/*@ annotation @*/
JML表达式
(1)原子表达式
\result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。
\old(expr)表达式:用来表示一个表达式expr在相应方法执行前的取值。
\not_assigned(x,y,…)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。
\not_modified(x,y,…)表达式:限制括号中的变量在方法执行期间的取值未发生变化。
\nonnullelements(container)表达式:表示container对象中存储的对象不会有null。
(2)量化表达式
\forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
\exists表达式:与forall表达式使用结构类似,为存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
\sum表达式:返回给定范围内的表达式的和。
\product表达式:返回给定范围内的表达式的连乘结果。
\max表达式:返回给定范围内的表达式的最大值。
\min表达式:返回给定范围内的表达式的最小值。
\num_of表达式:返回指定变量中满足相应条件的取值个数。
应用工具链
OpenJML
::openjml.bat @echo off set java=java -jar "%~dp0openjml.jar" -noPurityCheck set prove=-prover cvc4 -exec "%~dp0Solvers-windows\cvc4-1.6.exe" set runtime=-cp %~dp0jmlruntime.jar; set allparam= set rac= :param set str=%1 if "%str%"=="" ( goto end ) if "%str%"=="-rac" ( set rac=rac ) if "%str%"=="-prove" ( set str=%prove% ) set allparam=%allparam% %str% shift /0 goto param :end if "%allparam%"=="" ( goto eof ) rem remove left right blank :intercept_left if "%allparam:~0,1%"==" " set "allparam=%allparam:~1%"&goto intercept_left :intercept_right if "%allparam:~-1%"==" " set "allparam=%allparam:~0,-1%"&goto intercept_right :eof %java% %allparam% set filename= if "%rac%"=="rac" ( :input set /p filename=Input file name: if "%filename%"=="" ( goto input ) java %runtime% %filename% ) pause
这是从讨论区分享的一篇博客看到的。可以将上述代码拷贝到解压后的文件夹中,然后将该文件夹加入环境变量,即可通过命令行调用openjml
命令了。当然也可以用
java -jar .\openjml.jar -exec .\z3-4.7.1.exe -esc Person.java调用。
这是对一个功能特别简单的Person类检查后的结果,没有问题就不会有显示
JMLUnitNG
JMLUnitNG测试主要侧重于边界条件,比如对传入数据的可行域取边界进行测试。
下载jar包后,可以使用 java -jar jmlunitng-1_4.jar *.java 调用。
但其实我的环境运行不了那个下载下来的jar包。但从其他人的博客发现它这个生成样例就是使用极端数据看是不是会报错。这样就感觉实用价值并不会很大。
JMLUnit
它的配置在实验课的PPT就有。使用也比较简单。我觉得以下几种断言一般就够用了。
• assertEquals(int expected, int actual) 是否相等(输入参数的类型还可以为long、float、double、Object)
• assertTrue(boolean condition) 是否为真
• assertFalse(boolean condition) 是否为假
• assertNotNull(Object object) 是否非null
• assertNull(Object object) 是否为null
下面是我的测试程序片段和运行结果
总结:
1)OpenJML:用于检查JML文档规格语法,不过使用起来很麻烦而且效果不好
2)JMLUnitNG:基于JML的单元测试工具,能够自动生成测试用例,主要是边界条件的测试
3)Junit:单元测试,可用于编写和可重复运行的一些测试用例,测试用例是自己编写的
作业架构设计和bug分析
这个单元的后两次作业都因为TLE在强测扣了分。第二次最惨,几乎没得分。
作业一
作业一基本上不考算法。主要是要熟悉JML的语法。Arraylist 和Hashmap都有各自的好处和坏处,所以我所有数据都用Arraylist 和Hashmap各自存了一遍。
作业二
第二次作业我做得及其不认真。因为没有仔细看指导书的指令数所以没想到这次作业会卡时间。最后修bug的时候采用了记忆化的方法。
作业三
第三次作业就是考察算法了。我交的版本用的方法是继续使用记忆化的方法想让qmp的复杂度变成1,但是这就让ap操作的复杂度变成了n^2。我当时没想到这一点。所以最后有六个点TLE.最后bug修复我就放弃了记忆化,同时qmp用的是堆优化dij。blocksum的复杂度为n,所以一般不会被卡。还有就是qsl,我采用的方法是暴力删点然后查询的方法。虽然过了但是时间是1.9秒。总的来说我认为这次作业的核心就在于使用合适的算法,而且算法的复杂度是不可以达到n^2的(除了qsl)。
以下是类图
心得体会
本单元对规格的学习让我了解了契约式设计的重要性,通过规格的撰写,能够明确各类及方法需要实现怎样的功能,一方面使得代码实现人员能够根据规格完成代码,另一方面能够提高代码的可维护性,使得维护人员能够迅速理解各个方法的功能。虽然我出现了很多bug,但还是要承认这个单元让我收获很大。除了熟悉了JML语言外,我还更加熟悉了Java不同容器的优缺点。这我认为是很重要的,针对不同的需求采用最优的容器,这在以后写程序过程中也是很重要的。最重要的是我被迫学会了使用堆优化dij等算法。不过我感觉这个单元的重点除了JML,还有算法和对项目的整体架构。第二个单元因为contains方法复杂度太高,然后又有一些方法使用了contains方法导致了一些点tle;第三单元就主要是算法问题。这个单元作业完成的不理想,下个单元就必须认真做了。