面向对象OO第三单元总结

第三单元OO总结博客

1 梳理JML语言的理论基础、应用工具链情况

由于篇幅原因,这里只梳理几个在本单元常用的

注释结构

  1. 行注释://@annotation

  2. 块注释:/* @ annotation @*/

    例如:纯粹查询方法/*@ pure @ */,即方法的执行不会有任何副作用

JML表达式

原子表达式

  • \result:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值

  • \old(expr): 用来表示一个表达式 expr 在相应方法执行前的取值。作为一般规则,任何情况下,都应该使用\old把关心的表达式取值整体括起来。

量化表达式

  • \forall:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。

    作业中的例子:

    1 (\forall int i; 0 <= i && (i < npath.length - 1); containsEdge(npath[i], npath[i + 1])));

    保证isConnected 的前提之一是,这两个点之间有连续的边将他们相连(否则不通)

  • \exist:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束

    作业中的例子:

    1 requires (\exists Path path; path.isValid() && containsPath(path); path.containsNode(fromNodeId)) &&
    2          (\exists Path path; path.isValid() && containsPath(path); path.containsNode(toNodeId));

    保证isConnected 的前提之一,要存在某个Path,这个Path上包含这个点(否则就Not Found Exception)

操作符

  • 子类型关系操作符: E1<:E2

  • 等价关系操作符: b_expr1<==>b_expr2或者 b_expr1<=!=>b_expr2

  • 推理操作符: b_expr1==>b_expr2 或者b_expr2<==b_expr1。对于表达式b_expr1==>b_expr2 而言,当 b_expr1==false ,或者b_expr1==trueb_expr2==true时,整个表达式的值为 true 。

  • 变量引用操作符

    • \nothing指示一个空集:

      例如:assignable \nothing表示当前作用域下每个变量都不可以在方法执行过程中被赋值

    • \everything指示一个全集

方法规格

正常行为规格normal_behavior

前置条件
  • 通过requires子句来表示

    • 例如增加一条路径的前提条件:增加的这条路径必须合法有效

      1 requires path != null && path.isValid();
后置条件
  • 通过ensures子句来表示

    • getPathId必须确保返回的ID确实属于当前存在的路径

    • 1 ensures (\exists int i; 0 <= i && i < pList.length; pList[i].equals(path) && pidList[i] == \result);
副作用
  • 使用关键词 assignable 或者 modifiable,这个函数要改什么变量,就在后面加那个变量的名字

    • 例如addPath

      1  /*@ normal_behavior
       2       @ requires path != null && path.isValid();
       3       @ assignable pList, pidList; 需要修改这两个List
       4       省略一部分
       5       @ also
       6         省略一部分
       7       @ assignable \nothing; 如果Path不合法,那么返回0,没有需要修改的东西
       8       @ ensures \result == 0;
       9       @*/
      10     public int addPath(Path path);

异常行为规格expcetional_behavior

  • also: 有两种场景使用

    1. 除了正常功能规格外,还有一个异常功能规格,需要使用also来分隔。

    2. 父类中对相应方法定义了规格,子类重写了该方法,需要补充规格,这时应该在补充的规格之前使用also;

  • signals子句

    • 结构为signals (***Exception e) b_expr,意思是当b_expr为true时,方法会抛出括号中给出 的相应异常e。

      例子:

      1 /*@ public normal_behavior
       2      省略
       3       @ also 下面是异常行为
       4       @ public exceptional_behavior
       5       @ assignable \nothing;
       6       @ signals (PathNotFoundException e) path == null;
       7       @ signals (PathNotFoundException e) path.isValid() == false;
       8       @ signals (PathNotFoundException e) !containsPath(path);
       9       @*/
      10     public int removePath(Path path) throws PathNotFoundException;
      • 当 path == null条件成立,抛出异常

      • 当 path.isValid条件成立,抛出异常

      • 当容器内不含 path,抛出异常

在线OpenJML链接

2 部署SMT Solver,至少选择3个主要方法来尝试进行验证,报告结果

跳过

3 部署JMLUnitNG/JMLUnit,针对Graph接口的实现自动生成测试用例, 并结合规格对生成的测试用例和数据进行简要分析

生成数据

我研究了好久貌似OpenJML对MacOS不是很友好……好像也没有搞清楚TestNG怎么生成测试样例

写了一个python程序来生成测试指令

1 import random
 2 SEQUENCE = 0;
 3 ID = 1;
 4 DOUBLE_ID = 2;
 5 NONE = 3;
 6 ID_NODE = 4;
 7 queries = [("PATH_ADD", SEQUENCE),
 8            ("PATH_REMOVE", SEQUENCE),
 9            ("PATH_REMOVE_BY_ID", ID),
10            ("PATH_GET_ID", SEQUENCE),
11            ("PATH_GET_BY_ID", ID),
12            ("PATH_COUNT", NONE),
13            ("PATH_SIZE", ID),
14            ("PATH_DISTINCT_NODE_COUNT", ID),
15            ("CONTAINS_PATH", SEQUENCE),
16            ("CONTAINS_PATH_ID", ID),
17            ("DISTINCT_NODE_COUNT", NONE),
18            ("COMPARE_PATHS", DOUBLE_ID),
19            ("PATH_CONTAINS_NODE", ID_NODE)
20            ]
21
22 NUMBER = 100
23 PATH_LENGTH = 500
24 query_count = len(queries)
25 path_count = 0
26
27
28 def gen_node():
29     """generate random node"""
30     return random.randint(-2**31, 2**31 -1)
31
32 def gen_none():
33     """"""
34     return []
35
36 def gen_sequence():
37     """generate id sequence"""
38     len = random.randint(2, PATH_LENGTH)
39     sequence = []
40
41     for i in range(len):
42         sequence.append(gen_node())
43
44     path_count + 1;
45     return sequence
46
47 def gen_id():
48     """generate id"""
49     return [random.randint(1, path_count * 2 + 1)]
50
51 def gen_doubel_id():
52     """generate two ids"""
53     return [random.randint(1, path_count + 1), random.randint(1, path_count + 1)]
54
55 def gen_id_node():
56     return [random.randint(1, path_count + 1), gen_node()]
57
58 # print(gen_sequence())
59
60 generators = {SEQUENCE : gen_sequence,
61               ID: gen_id,
62               DOUBLE_ID: gen_doubel_id,
63               NONE: gen_none,
64               ID_NODE: gen_id_node }
65
66 for i in range(13):
67     (op, arg) = queries[0]
68     print(op + ' ' + ' '.join(map(lambda x: str(x), generators[arg]())))
69 for i in range(NUMBER):
70     (op, arg) = queries[random.randint(0, query_count - 1)]
71     print(op + ' ' + ' '.join(map(lambda x: str(x), generators[arg]())))

IDEA TestMe插件和 TestNG和OpenJML的安装历程

  1. 安装TestMe插件,可以直接在Plugin 的Market里面下载,TestNG失败

  2. 下载TestNG

(TestNG下载及安装教程链接)

(TestNG6.8.7JAR包的下载链接)

Build TestNG from source code

TestNG is also hosted on GitHub, where you can download the source and build the distribution yourself:

 

然后把Jar包导入新创建的Maven项目当中,课程组的Jar包导入项目当中

在右侧边栏的Maven,点击加号,把课程组给的pom.xml导入

后来弄出了形如这样的一堆令人头疼的代码,遂放弃

1     @Test
 2     public void testGetConnectedBlockCount() {
 3         when(bfs.getBlockSize()).thenReturn(0);
 4         when(bfs.updateGraph(any())).thenReturn(new HashMap<Integer, HashMap<Integer, Integer>>() {{
 5             put(Integer.valueOf(0), new HashMap<Integer, Integer>() {{
 6                 put(Integer.valueOf(0), Integer.valueOf(0));
 7             }});
 8         }});
 9
10         int result = myRailwaySystem.getConnectedBlockCount();
11         Assert.assertEquals(result, 0);
12     }

(错误示范)

正确使用OpenJML

在许多大佬的帖子中已经把如何生成测试代码讲得很清楚了

示例主程序在Demo.java里面

java -jar jmlunitng.jar src/Demo.java
javac -cp jmlunitng.jar src/*.java
 

然后会出现很多名字奇怪的.java文件,其中有一个叫Demo_JML_Test.java,也就是测试时要运行的主程序

我把生成的.java测试文件都加入到IDEA工程的src文件夹下面,需要导入的包有openjml.jar,jmlunitng.jar

但是我在运行Demo_JML_Test的时候有一些奇怪的报错 “org.jmlspecs.utils.JmlAssertionError 中是 protected访问控制”

于是把报错代码Catch 的 Exception删了

在艰苦卓绝的研究下,和修改了一堆版本不兼容的问题后,测试代码终于运行成功了!

(racEnabled尚未解决)

Junit 测试代码

创建单元测试可以直接在IDEA里面自动生成一个测试代码的框架

Junit中集中基本注解,是必须掌握的:
  • @BeforeClass – 表示在类中的任意public static void方法执行之前执行

  • @AfterClass – 表示在类中的任意public static void方法执行之后执行

  • @Before – 表示在任意使用@Test注解标注的public void方法执行之前执行

  • @After – 表示在任意使用@Test注解标注的public void方法执行之后执行

  • @Test – 使用该注解标注的public void方法会表示为一个测试方法

junit中的assert方法全部放在Assert类中,总结一下junit类中assert方法的分类。

Junit中部分Assert方法总结:(来自这个博客)
  1. assertTrue/False([String message,]boolean condition); 判断一个条件是true还是false。感觉这个最好用了,不用记下来那么多的方法名。

  1. fail([String message,]); 失败,可以有消息,也可以没有消息。

  1. assertEquals([String message,]Object expected,Object actual); 判断是否相等,可以指定输出错误信息。 第一个参数是期望值,第二个参数是实际的值。 这个方法对各个变量有多种实现。在JDK1.5中基本一样。 但是需要主意的是float和double最后面多一个delta的值。

  1. assertNotEquals([String message,]Object expected,Object actual); 判断是否不相等。 第一个参数是期望值,第二个参数是实际的值。

  1. assertArrayEquals([java.lang.String message,] java.lang.Object[] expecteds, java.lang.Object[] actuals) ;

  1. assertNotNull/Null([String message,]Object obj); 判读一个对象是否非空(非空)。

  1. assertSame/NotSame([String message,]Object expected,Object actual); 判断两个对象是否指向同一个对象。看内存地址。

  1. failNotSame/failNotEquals(String message, Object expected, Object actual) 当不指向同一个内存地址或者不相等的时候,输出错误信息。 注意信息是必须的,而且这个输出是格式化过的。

下面仅写出部分函数的测试代码

1 public class MyGraphTest {
 2     MyGraph graph = new MyGraph();
 3     int[] nodelist1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13};
 4     int[] nodelist2 = {-1, 6, -2, -3, -4, -5, -6, -7, -8};
 5     int[] nodelist3 = {0,1205, 999, 888, 777, 666};
 6     @Before
 7     public void before() throws Exception { // 每个方法检测之前,都另这个图里面有这三条路径
 8         System.out.println("Ready to test " );
 9         //MyGraph graph = new MyGraph();
10         graph.addPath(new MyPath(nodelist1));
11         graph.addPath(new MyPath(nodelist2));
12         graph.addPath(new MyPath(nodelist3));
13     }
14
15     @After
16     public void after() throws Exception { // 每个函数检测完毕都会输出“test successfully!”
17         System.out.println("test successfully!");
18     }
19
20 @Test
21 public void testContainsPath() throws Exception {
22 //TODO: Test goes here...
23     System.out.println("Testing ContainsPath!");
24     Assert.assertTrue(graph.containsPath(new MyPath(nodelist1)));
25     Assert.assertTrue(graph.containsPath(new MyPath(nodelist2)));
26     Assert.assertTrue(graph.containsPath(new MyPath(nodelist3)));
27     int[] nodelist4 = {9,9,9,9};
28     Assert.assertFalse(graph.containsPath(new MyPath(nodelist4)));
29 }
30
31 @Test
32 public void testContainsPathId() throws Exception {
33     System.out.println("Testing contains Path id!");
34     Assert.assertTrue(graph.containsPathId(1));
35     Assert.assertTrue(graph.containsPathId(2));
36     Assert.assertTrue(graph.containsPathId(3));
37     Assert.assertFalse(graph.containsPathId(100));
38 }
39
40
41 @Test
42 public void testRemovePath() throws Exception {
43 //TODO: Test goes here...
44     graph.removePath(new MyPath(nodelist1));
45     Assert.assertFalse(graph.containsPathId(1));
46 }
47
48 @Test
49 public void testRemovePathById() throws Exception {
50 //TODO: Test goes here...
51     graph.removePathById(2);
52     Assert.assertFalse(graph.containsPath(new MyPath(nodelist2)));
53 }
54
55 @Test
56 public void testContainsNode() throws Exception {
57 //TODO: Test goes here...
58     Assert.assertTrue(graph.containsNode(999));
59     Assert.assertFalse(graph.containsNode(14));
60
61
62 @Test
63 public void testIsConnected() throws Exception {
64 //TODO: Test goes here...
65     System.out.println("Testing is Connected@");
66     Assert.assertTrue(graph.isConnected(2,-8)); // 判断这个连接是对的
67     Assert.assertTrue(graph.isConnected(13,-8));
68     Assert.assertFalse(graph.isConnected(0, 1));
69     // 把这个文件拖到src文件夹里面即可运行
70
71 }
72
73 }

愉快的Tests passed!

(0)

相关推荐