OO第三单元总结


测试过程

黑箱测试和白箱测试

黑箱测试注重“结果”,它只关心程序能否完成需求,而不关心代码实现方式、内部架构;白箱测试注重“过程”,需要去关注代码内部结构、实现方法,测试时要关注方法覆盖率、分支覆盖率等。

互测我一般采用的是黑箱测试,对自己的代码主要采用的是黑箱测试,同时有部分的白箱测试。

对单元测试、功能测试、集成测试、压力测试、回归测试的理解

  1. 单元测试:对代码的最小单元进行测试,如一个方法、一个类。典型的例子就是Junit,这个单元的作业也要求了用Junit完成一些方法的单元测试,它是一种白箱测试。
  2. 功能测试:关注系统的功能性需求,通常是从使用者的角度出发,观察测试系统的输入和输出是否符合预期,是一种黑箱测试。
  3. 集成测试:在将单个软件模块组合在一起形成完整的系统后,测试这些模块之间的交互是否正常的过程。它的目标是验证不同模块之间的接口是否正确,并且模块之间的通信是否按照预期工作。集成测试可以帮助发现模块之间的兼容性问题和集成错误。
  4. 压力测试:在正常操作条件的前提下,输入具有压力的数据量,以确定程序在超出正常工作负载的情况下的表现,它主要考虑一些极端情况,如高并发、大量数据、极端数据。个人感觉这个单元的重点就是压力测试,强测中大量压力测试,算法稍微设计不合理就会CPU_TIME_LIMIT_EXCEED。
  5. 回归测试:对代码进行修改后重新运行之前的测试样例,以确保没有引入新的错误或者影响到现有的功能。我在测试过程中会经常使用此方法,因为本单元作业很多地方需要对算法进行优化,我一般第一版是没有进行算法优化的,我会保留第一版运行后的输出结果,后续修改算法后会重新运行这些样例,然后与之前的输出结果进行比对。

数据构造策略

很明显感觉到这个单元随机生成数据无法很好得保证正确率,尤其是性能方面。所以需要手动构造一些极端数据,比如构造高并发的指令数据,反复执行某个指令,看看运行时间是否超过限制;构造边界数据,避免数据溢出导致的出错。

架构设计

本单元的架构基本是参照着JML实现各个类和方法,在此基础上添加了UnionFind、Dijkstra类做性能的优化,Sender类单纯是为了满足MyNetwork小于500的要求分出去的一个方法。

架构图

性能改进

HashMap替代ArrayList

JML中的描述都是数组形式,所以刚开始很自然地选择了ArrayList,但是这些数据结构有很多查找操作,所以最后都改成了HashMap,以降低查找操作的时间复杂度

并查集

用于判断两个人是否有联系,抽象出来就是判断图中两个结点是否属于同一个连通子图。

实现细节方面,首先进行了路径压缩的优化,避免每次查找时都要递归。其次,由于人与人之间的关系会修改,有可能会断开连接(即关系图中结点之间的边可能会被删掉),所以需要重建并查集,这里借鉴了“写时复制”的思想,

  1. 在MyNetwork里设置了一个标记resetUnion,初始为false
  2. 若有添加人的命令,则在并查集中加入该person
  3. 若有添加关系的命令,则调用union把两人放入同一个集合
  4. 若有修改关系的命令
    • 若resetUnion为false,则看修改关系是否会导致两个人的连接被删除,若会删除,则resetUnion置为true
    • 若resetUnion为true,就不要管了(千万别因为没删关系就重新置为false),这说明之前有命令已经导致并查集需要重建
  5. 当要查询两个人之间是否关联时
    • 若resetUnion为false,则直接调用isSameSet()判断两人是否连接
    • 若resetUnion为true,则先调用initialize()清空,然后调用setup()重建并查集,然后查询。同时不要忘了把restUnion置为false

dijkstra求最短路径

这个没什么好说的,数据结构课讲过的算法,但是不记得了,然后又重新学了一遍。java可以用优先队列实现,但是注意比较器不要用两个数相减的方式比大小,会爆int。

动态维护

在添加人、关系的时候就计算出来结果,每新添加人、关系时都要判断是否需要修改,这个方法用在计算bestAquaintance、tripleSum、valueSum等地方。能动态维护的都要动态维护,事实上这个单元不能出现任何复杂度大于O(n^2)的方法……不然强测一定会寄

Junit测试

上学期已经用Junit做过一些单元测试,但是本单元的Junit让我新学会了用Parameters做参数化测试。参数化测试可以生成多组数据一同进行测试,保证了数据的覆盖率,生成过程中我会尽量考虑所有情况,例如生成关系时稠密图、稀疏图都要有一定的生成概率,Message信息每种类型都要有可能出现。

断言时JML就派上用场了,根据JML的规格信息一一编写断言。注意对于pure方法,要保证属性前后没有发生任何改变,所有可能会有深浅克隆的问题,我的解决办法是在数据生成阶段就进行克隆,用一个shadowNetwork与Network同步生成数据,保证二者一模一样。

用junit进行测试确实帮助我发现了一些代码的bug,例如sendMessage后message没有正确移除,然后发现我的remove语句有问题。

不过编写Junit的过程也确实很繁琐,而且要保证数据生成没有问题,有时候还得先改junit的bug(汗流浃背了)

学习体会

本单元了解了JML的语法,训练了根据JML给出的规格编写代码的能力,体会到了JML语言的严谨性,但是理解JML的过程非常痛苦,有时候还会看错括号。

另外,本单元对性能优化的要求非常高,对我算法的学习有很大帮助,有的算法以前学过但是忘了,所以又重新复习、实践了一遍;有的算法是第一次了解,学到了新知识非常好!(虽然后来可能因为优化不够被替换掉了)

这个单元也有很多遗憾,感觉我对高并发的情况考虑还是不够周全,所以每次作业最后都出现了一些CTLE的bug,哎需要好好反思,这个问题在第二单元也出现过,感觉它其实是一个在实际应用中也很需要关注的问题。总之,牢记这次的教训!


Author: CuberSugar
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source CuberSugar !
  TOC