一、测试过程分析与测试策略
1. 测试体系分层实践
在本单元的作业中,我们构建了多层次测试体系:
• 单元测试:针对每个JML规格方法编写独立测试用例,同时包装addPerson、addRelation等原子操作
• 功能测试:只需考虑需要方法的功能,不需要考虑内部结构及代码。
@Test
//验证pure
public void testPureProperty() throws Exception {
Network network = createSampleNetwork();
// 记录初始状态
PersonInterface[] before = network.getPersons();
// 调用方法
network.queryCoupleSum();
// 验证状态未改变
PersonInterface[] after = network.getPersons();
assertArrayEquals("Data modified after pure method call", before, after);
}
• 集成测试:验证模块间协作。在本单元中,我们有时需要对一些值进行维护,以加快查找速度,以queryTagValueSum方法为例,构建跨标签的关系网络,测试添加/删除人员时多个标签的数值同步更新逻辑
• 压力测试:通过自动化脚本生成超大规模数据测试程序的性能和正确性,包括可能的tle以及wa
• 回归测试:每次迭代后运行历史测试集,我在测试时候并未采用该方法
2. 数据构造策略
白箱+黑箱策略提升测试覆盖率:
-
白箱
-
基础场景
构造基础特征数据组合,如:// 五种图结构 EMPTY_GRAPH, SINGLE_NODE, TWO_NODES, TRIANGLE, COMPLEX_GRAPH
-
边界值构造
重点关注空图、完全图、最大节点数等边界场景:// 测试网络容量边界 for (int i=0; i<MAX_PERSON; i++) { network.addPerson(new Person(i, "Test", 20)); }
-黑箱
- 随机化测试
开发自动化数据生成工具,数据构造器来随机生成数据
二、大模型辅助开发实践
JML规格解析
本单元大模型在写代码过程中最大的作用是读取jml信息并以自然语言描述出来辅助我们的理解,加快jml阅读速度
[用户输入]
解释JML中的ensures子句:
@ ensures \result == (\sum int i; 0<=i<people.length; people[i].getAge());
[模型输出]
该规格要求方法返回值等于people数组中所有人员年龄的总和
但是往往直接将jml以及作业要求喂给AI,其写出来的方法很容易出现tle或者wrong answer
同样的,在ai给出的代码基础上自己进行修改工作量大,对于条件容易遗漏
三、图模型构建与优化
在本单元中,社交网络可以抽象为一个无向图,其中人员表示为图中的节点,人员之间的关系表示为图中的边,边的权重表示关系的数值。具体实现时,使用 Network
类中的 persons
列表来存储所有的人员节点,每个 Person
类实例中使用 acquaintance
数组和 value
数组来存储其相邻节点和对应的边权重。
图模型维护
- 添加人员:当调用
Network
类的addPerson
方法时,会检查人员 ID 的唯一性 - 添加关系:当调用
Network
类的addRelation
方法时,会检查两个人员是否存在以及关系是否已经存在。如果人员存在且关系不存在,则在两个人员的acquaintance
数组和value
数组中添加相应的信息。 - 修改关系:当调用
Network
类的modifyRelation
方法时,会检查两个人员是否存在、关系是否存在以及修改后的权重是否大于 0。如果满足条件,则更新关系的权重;如果权重小于等于 0,则删除该关系,并从相关标签中移除对应的人员。
存储person时我采用的是
private final ArrayList<Person> persons = new ArrayList<>();
但是性能不佳 可以采用邻接表+哈希索引:
private Map<Integer, Person> personMap = new HashMap<>();
private Map<Integer, List<Edge>> adjacencyList = new HashMap<>();
四、性能问题与修复
典型问题与解决方案
第十一次未出现问题,第九次和第十次作业问题如下:
问题 | 根因分析 | 优化方案 |
---|---|---|
queryTripleSum超时 | 三重循环遍历所有三元组 | 使用bitset |
qtvs缓存失效 | 未及时更新关系变更对Tag的影响 | 实现增量更新 |
五、JUnit测试实践
1. 基于规格的测试设计
// 验证后置条件
@Test
public void testAddRelation_EnsureSum() {
int initialSum = network.queryValueSum(tagId);
network.addRelation(1, 2, 100);
assertEquals(initialSum + 200, network.queryValueSum(tagId));
}
// 异常场景测试
@Test(expected = RelationNotFoundException.class)
public void testModifyRelation_RemoveNonExist() {
network.modifyRelation(1, 2, -150);
}
2. 覆盖率控制
六、JML与JUnit协同测试
一、测试逻辑
JML的三种核心结构(ensures、invariant、signals)与JUnit的断言机制形成对应:
- 后置条件验证:
ensures
子句直接转化为assertEquals
:// JML: ensures \result == size; assertEquals(size, collection.size());
- 不变式检查:类的不变式(如社交值与金额的关系)在每次操作后进行验证,确保对象状态的一致性
- 异常处理:
signals
子句通过try-catch
结构验证异常的类型和内容
二、测试策略
基于JML的测试具有以下策略优势:
- 黑盒测试:测试者只需关注规格描述,无需了解具体实现细节,有效分离了设计与验证。
- 边界条件:JML中的量化表达式(如
\forall
、\exists
)自然引导测试用例覆盖各种边界情况和特殊值。 - 状态维护:通过检查,确保类的内部状态在任何操作后都保持合法,有效预防状态泄露和数据不一致问题。
七、学习收获与展望
- 规格规范开发:JML规范成为设计与实现的桥梁,有效提升代码质量,还记得上OOpre时感叹助教代码写的怎么如此规范
- JML是一个优秀的规格化语言,可以帮助我们避免一些通用语言表达上的二义性问题
- 这本质上是契约式设计的实践,通过明确的前置条件、后置条件和不变式,在代码层面实现了设计文档与测试用例的同步更新