- JML理论基础
- JML工具链
- openjml使用
- openjml总结
- jmlunitng使用
- 代码分析
- 第一次作业
- 第二次作业
- 第三次作业
- 测试&bug分析
- 黑盒测试
- 白盒测试(Junit)
- 总结
JML理论基础
jml是基于一阶谓词逻辑来对类进行形式化描述的语言,jml语言通过表达式、方法规格、类规格三个部分来进行描述。其中表达式是在java语言的基础上扩展了非修改类型的表达式;方法规格中包括了前置条件、后置条件、副作用作用范围三个部分;类规格包含不变式和状态变化约束两个部分。jml相较于自然语言而言,可以消除歧义,同时配合相关工具进行形式化验证。
jml方法规格写法举例
/*@ public normal_behavior //正常行为规格
@ requires (\exists int i; 0 <= i && i < acquaintance.length; //前置条件
@ acquaintance[i].getId() == person.getId());
@ assignable \nothing; //副作用范围
@ ensures (\exists int i; 0 <= i && i < acquaintance.length; //后置条件
@ acquaintance[i].getId() == person.getId() && \result == value[i]);
@ also
@ public normal_behavior
@ requires (\forall int i; 0 <= i && i < acquaintance.length;
@ acquaintance[i].getId() != person.getId());
@ ensures \result == 0;
@*/
JML工具链
目前能用的jml工具链不多,我在本单元使用了openjml和jmlunitng两种工具
openjml使用
openjml使用过程中槽点相当之多,包括jdk版本、大量报错,静态检查巨慢等...
除了用官网的简单代码的测试外,我是用魔改了的MyPerson类来进行了静态测试,结果如下:
可以看到有很多奇怪的报错,后面我用原版的MyPerson执行
java -jar $opjml/openjml.jar -cp ../../explaintion/package/offical.jar -esc MyPerson.java
可以看到时间相当长,而且报了7个奇怪的错误,怀疑可能是opjml支持性的问题
为了证明opjml的效果,我改了一下MyPerson的getId()方法
结果如下
可以看到静态检查确实发现了问题,但是有价值的错误信息混在大量无意义的信息中无疑加大了阅读难度
openjml总结
虽然opjml能够发现问题,但是时间长、报错多的问题使得它并不是特别实用
jmlunitng使用
jmluniting的使用由于Group一直报错,我就自己写了一个MainClass来测试。
MainClass代码如下:
public class MainClass{
private /*@spec_public@*/ int id;
private /*@spec_public@*/ String s;
/*@ assignable id;
@ ensures id == \old(id)+1;
@*/
public void addid(){
id++;
}
/*@ requires num>=0;
@ assignable id;
@ ensures id == \old(id)+num;
@*/
public void main(int num){
if(num<0||num>150) return;
for(int i=1;i<=num;i++){
addid();
}
}
/*@ requires ins!= null;
@ assignable s;
@ ensures !s.equals(ins);
@*/
public void setString(String ins){
this.s = ins;
}
/*@ requires ss != null;
@ assignable \everything;
@ ensures id == 0 && s==ss;
@*/
public MainClass(String ss){
s=ss;
id=0;
}
}
使用如下的指令来进行测试:
java -jar %opjml%/jmlunitng.jar MainClass.java
javac -cp %opjml%/jmlunitng.jar *.java
java -jar %opjml%/openjml.jar -rac MainClass.java //生成openjml的断言
java -cp .;%opjml%/jmlunitng.jar MainClass_JML_Test
pause
运行结果
可以看到主要对于边界数据进行了一些测试,其实按照官方文档的说法是可以在生成的代码里面添加自己想要测试的数据的,但是这样的化其实会比写junit好一点,自己写junit的话如果理解错误就凉凉了。
代码分析
第一次作业
非常简单,自己写了一个并查集,其余的就是一步步按照jml来,这也为后来的错误埋下了伏笔
第二次作业
同样非常简单,就是把理按照jml一步步来,把某些事件复杂度大的方法稍加修改,结果由于第一次的getPerson采用遍历的方式,导致强测直接原地爆炸,强测0分。
第三次作业
这次作业复杂一些,我先理解完了jml后然后再按照自己的方法来写,其中把queryAgeSum、queryMiPath、queryStrongLink三个方法的实现封装在了Work里面。
测试&bug分析
在这个单元中可以说吃了测试不全的大亏,导致三次作业分数都非常不理想。
黑盒测试
黑盒测试是我这次在互测中采用的主要方法,我的测试主要由两部分构成,一部分是数据生成器,一部分是对拍器。对排器比较简单,主要讲讲数据生成生成。我本来是随机生成的,后来第一次作业观摩了房内某个基本道道全中的dalao的数据,发现采用不同权重的随机数,对于权重高的数据每次生成一小批,例如
A
A
A
B
C
C
C
B
这种,同时尽可能多的把图变得稠密,这样也能增加数据的强度。
在本单元的测试中,前两次我都是用黑盒测试来互相测试,第三次我利用黑盒和同学的代码进行对拍。比较血亏的是我以为queryAgeSum的缩写是qas结果导致本地一个数组开小的错误没有测出,互测中数据强度也不太够,只刀了4个人。
白盒测试(Junit)
本单元,我觉得Junit测试有一定风险,如果对jml理解错误就会导致一些比较尴尬的情况。我觉得junit测试方面有两个心得,一个是要尽可能多的覆盖掉所有代码路径,这样能够比较大限度地判断已经完成代码逻辑与没有问题。另外一个方面是尽可能多地覆盖数据的情况,对数据进行分类,比如采用判断表法或者数据等价类划分能够很好地解决黑盒测试中难以生存的某些边界数据问题。
总结
在本单元的学习中,我对于形式化验证的方法有了一定了解,但也认识到形式化验证还存在效率不高等问题。同时本单元的连续多次踩坑,也让我更加注重测试的重要性,对测试代码的迭代也让我了解了许多测试的方法。当然本单元学习还是有一些遗留问题,比如为什么jml的是如此设计的,为什么通过前置和后置条件副作用就能完美地描述一个方法,这其实有赖于对于公理逻辑系统的理解,希望以后有空能去看看相关论文深入了解下。