本单元考察了对于JML语言的理解和运用,根据课程组给出的JML的规格实现相应的代码,整体实现内容并不复杂,大多方法只需按照JML照葫芦画瓢,在少部分方法中需要通过选择合适的算法等方式提高性能
本单元测试过程
黑盒测试与白盒测试
黑白盒的概念更多出现在对抗攻防中,黑盒顾名思义,代表的是无法了解内部具体实现方式,只能通过输入输出判断运行的情况,白盒测试则可以详细了解代码内部实现方式,可以针对源码级别的bug进行针对性的攻击和测试
- 黑盒测试
在作业当中,我更多采用的是黑盒测试,即采用大量的随机数据,试图覆盖尽可能多的情况,从而测试出某些情况出现的bug,除了通过数量取胜之外,试图构造一些较强的数据点,对于性能进行挑战也是一种有效的黑盒测试方式 - 白盒测试
理论上来说,相较于黑盒测试,白盒测试更能够针对性地找出Bug,但是由于要求对进行测试的代码进行整体上的阅读,在实际的hack当中,被采用的更加小众,因为需要花费更多的时间和精力去完整的理解代码内容
单元测试
单元测试用于测试软件的最小可测试单元,通常是代码中的函数或方法。它的目的是测试单元的逻辑正确性和准确性,以确保单元能够按照预期工作。
我们运用junit
对代码中的方法进行测试的方式就属于单元测试,也是目前我们主要接触的测试方法之一
功能测试
功能测试用于测试软件的各个功能是否按照预期工作。它的目的是检查软件是否满足用户需求,以确保软件能够按照预期工作。
集成测试
集成测试用于测试多个模块或组件之间的交互是否按照预期工作。它的目的是检查软件模块之间的接口是否按照预期工作,以确保软件能够整体按照预期工作。
压力测试
压力测试用于测试软件在高负载下的表现。它的目的是确定软件在高负载情况下的性能瓶颈和极限,以便为软件性能优化提供指导。
回归测试
回归测试用于测试修改后的软件是否仍然按照预期工作。它的目的是确保软件修改或更新后没有引入新的错误或问题,以便确保软件在不同版本之间的兼容性。
数据构造策略
- 广泛性
全面性数据点构造,尽量测试到各种不同的情况,提高覆盖率,测试面对不同情况程序给出的结果是否符合预期,通过覆盖性测试保证方法的各个分支方法都能够正确实现 - 针对性
针对于某一特殊情况或功能进行针对性测试,如第十次作业强测中的第十个测试点,大量加入qtvs
指令,从而针对性测试该方法的性能(我就在这个点爆了ctle)
结构设计
本次作业整体架构与依据JML实现,没有过多添加新的实现,为了维护Person
之间的关系,新建了Graph
类,在该类中实现了并查集、压缩路径、最短路径查询等一系列方法,在Graph
中维护了记录祖先的数组和邻接表等数据,动态维护Graph
中的triNum
和blockNum
数据,在每次添加、删除时进行维护
性能问题
- 选取合适的容器
- 尽量采用
HashMap
和HashSet
作为容器而避免采用ArrayList
,提高在取出存放数据时的效率 - 由于第三次作业中的
getReceivedMessages()
方法,要求取出前五条Message
,因此在Person
中存放Message
的容器选用ArrayList
,按照顺序存取 - 在
Network
中除了ArrayList
之外仍采用HashMap
,以ID
为key,储存Message
- 尽量采用
- 选取合适的存储方法
针对于deleteColdEmoji()
方法需要删除Network
中的部分Message
,使用该种方式HashMap<Integer,ArrayList<Integer>> emojiId;
进行储存,以emojiId
作为key,记录下该emojiId对应的Message
的Id,在删除时只需要删除对应id的Message
,而不需要遍历Network
中所有的Message
去筛选出需要删除的Message
- 采用合适的算法
- 如在第一次作业中使用并查集,维护
triNum
和blockNum
,值得一提的是在删除两个节点关系时的方式,首先采用dfs
遍历所有与A节点有关联的点,将他们的祖先全部标记为A,若B的祖先为A,则说明A与B仍然相通,则不需要进行额外的操作,否则再对B进行dfs
,将与其有关系的节点祖先设为B,blockNum
+1 - 在第二次作业查询最短路径中,由于之前已经在
Graph
中维护了邻接表,直接采用bfs
算法查询即可,相较于dfs
,bfs
更适用于查询最短路径,一旦找到就是最短的
- 如在第一次作业中使用并查集,维护
- 一种奇妙的提升性能算法(针对于
qtvs
)
在Tag
中的getValueSum()
方法中,我原本采用的结构是
for (MyPerson person1 : persons.values()) {
for (MyPerson person2 : persons.values()) {
if (person1.isLinked(person2)) {
sum += person1.queryValue(person2);
}
}
}
然后就被strong10给爆ctle了o(╥﹏╥)o
之后在研究讨论 (看舍友的) 之后,发现了略加修改就可以修复ctle的问题
for (MyPerson person1 : persons.values()) {
for (MyPerson person2 : person1.getAcquaintance().values()) {
if (persons.containsKey(person2.getId())) {
sum += person1.queryValue(person2);
}
}
}
由于所有与person1
有关系的Person
都在acquaintance
中,相当于直接把所有满足isLinked()
的Person
直接取出来,减少了需要遍历的长度,降低了时间复杂度,同时不需要进行动态维护就可以蹭过强测的测试点
junit测试
- 全面性考虑JML的各项要求
针对JML规格的每一条要求都进行测试,包括前置require
,后置ensure
,以及对于变量的要求和pure
方法的测试,保证测试覆盖JML的每一条语句 - 设计样例能够覆盖各个分支
样例应能够充分覆盖到方法的各个分支,从而确保每个分支的正确性,除却充分考虑各种情况之外,也可以采用生成完全图或随机数据的方式进行重复测试,用数量代替质量
总结
本单元整体实现难度相较于前两单元较低,考察的更多是部分算法的性能实现以及能否将JML隐含的一些细节完整的实现