一、测试过程

1.黑箱测试

​ 黑箱测试是一种基于需求和功能规格的测试方法,测试人员不需要了解系统内部的实现细节,主要关注系统的输入和输出,通过输入数据来检查系统是否按照预期的方式运行,并验证输出是否符合预期。

​ 优点是可以从用户的角度出发,更好地模拟用户实际使用系统的情况。缺点是可能无法覆盖系统的所有代码路径,以及对代码质量的评估不够深入。

2.白箱测试

​ 白箱测试是一种基于代码内部结构和逻辑的测试方法,测试人员需要了解系统的内部实现细节,通过检查代码的逻辑路径、分支覆盖率等指标来评估代码的质量,并进行相应的测试。优点是可以更全面地覆盖代码,发现潜在的逻辑错误。

3.单元测试

​ 单元测试是针对软件中的最小可测试单元进行的测试,旨在验证单个单元(通常是函数、方法或类)的功能是否按照预期工作,并尽可能地覆盖不同的代码路径和边界情况。例如我们写了三次的亲爱的Junit,真的是太实用了 (๑•̀ㅂ•́)و✧

4.功能测试

​ 功能测试是通过模拟用户操作来验证系统功能是否符合需求和规格说明,需要尽可能覆盖整个系统的功能,验证系统的输入与输出是否符合预期。我理解可能是把所有指令都测一遍(?

5.集成测试

​ 集成测试是验证不同模块之间的交互和集成是否正常工作的测试过程,将已经通过单元测试的模块组装成完整的系统,并进行接口测试、模块间通信测试等,以确保模块之间的集成没有问题。

6.压力测试

​ 压力测试是评估系统在负载较大或极端条件下的性能表现的测试方法,通过模拟大量用户访问、高并发操作等情况,来测试系统的稳定性、可靠性和性能表现。比如我们亲爱的1w强测数据和十分稳定好用的评测机,完美完成了压力测试的使命 (๑•̀ㅂ•́)و✧

7.回归测试

​ 回归测试是在软件发生变更后重新运行之前的测试用例,验证软件的修改是否对现有功能产生了影响,以确保修改没有引入新的错误或破坏现有的功能。回归测试是软件维护过程中的重要环节,能够确保软件在不断迭代更新中保持稳定和可靠。我选择扔到bug修复里跑上次的强测数据 (u‿ฺu✿ฺ) (然后发现上次跑9s的点这次时间直接砍半??????????)

8.数据构造

  • 边界条件

Personid为负数的情况

(bestId一开始设置的-1,后来改成这个人自己了,差点寄了 ヾ(^▽^*)))

Tagsize大于1111(好数字)的情况

(没有仔细阅读JML的反思吧 ヾ(^▽^*)))

Tag的size等于0的情况

(要不是突发奇想检查,差点寄了,互测直接大杀四方 ヾ(^▽^*)))

  • 充分覆盖

​ 将每一个normal_behaviorexceptional_behavior全部涉及到

​ 代码的每一个分支路径都覆盖

建议下届加入分支覆盖率指标 👿

  • 火力统治

​ 要充分考虑测评机的优异能力,狠狠进行压力测试

​ 比方说qtvs给他整个2222*2222*3333,O(n^2)遍历的就轻松炸了

二、架构设计

因为是实现JML,大家架构大同小异,所以我认为无需UML类图

1.图模型构建

祖传的并查集+路径压缩+按秩合并

代码不贴了,指路往届博客

2.维护策略

这里主要写几个指令吧(有些指令和图模型也没什么关系,就像hw9/10/11没什么关系一样ヾ(^▽^*)

  • ar、mr、qci、qbs

ar有伟大的并查集merge

qci有伟大的并查集find

qbs动态维护ap++,ar且原来不连通–

mr如果删边,懒狗选择延迟重建设置脏位

qciqbs的时候再整体重建

  • qts

动态维护 查询时间复杂度O(1)

1
2
3
4
5
6
for (Person person : ((MyPerson) person2).getAcquaintance().values()) {
if (person.isLinked(person1)) {
tripleSum--;//ar
tripleSum++;//mr
}
}
  • qtvs

懒狗选择遍历熟人,时间复杂度O(mn)

1
2
3
4
5
6
7
8
9
10
11
12
13
//MyTag.java
public int getValueSum() {
valueSum = 0;
for (Person person : persons.values()) {
for (Person person1 : ((MyPerson) person).getAcquaintance().values()) {
if (hasPerson(person1)) {
valueSum += person1.queryValue(person);
}
}
}

return valueSum;
}
  • qba、qcs

动态维护 查询时间复杂度O(1)

Person里维护bestIdbestValue

armr的时候进行分类讨论

只有 (1)删关系且正好删到bestId (2)减少bestId 关系的value值,这两种情况需要进行遍历重建bestId

qcsarmr的时候动态维护,亲亲舍友教的 (๑•̀ㅂ•́)و✧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void updateCoupleSum(int id1, int id2, int oldId1, int oldId2) {
boolean bestChange1 = (oldId1 != newId1);
boolean bestChange2 = (oldId2 != newId2);
if (bestChange1 || bestChange2) {
if (newId1 == id2 && newId2 == id1) { //现在凑了一对cp
coupleSum++;
}
if (oldId1 == id2 && oldId2 == id1) { //原来拆了一对cp
coupleSum--;
}

if (bestChange1) {
if (oldId1 != id1 && newId1 == id2 && oldP1.getBestId() == id1) {
//原来1的cp存在,原来1的cp的现在的cp还是1,但1的现在cp是2了
coupleSum--;
}
if (newId1 != id1 && newId1 != id2 && newP1.getBestId() == id1) {
//现在1的cp存在,现在1的cp的cp是1,1的现在cp还不是2
coupleSum++;
}
}
//2同理
}

}
  • qtav

伟大的往届博客

维护ageSumagePowSum,严格按照方差计算公式展开计算

1
2
3
4
5
6
7
public int getAgeVar() {
if (personSum == 0) {
return 0;
}
int mean = getAgeMean();
return (agePowSum - 2 * mean * ageSum + mean * mean * personSum) / personSum;
}
  • qsp

没有权值,懒狗选bfs遍历,时间复杂度O(n)

  • cn、dce

学到了一种优雅的东西 (u‿ฺu✿ฺ)

1
2
3
4
5
6
public int deleteColdEmoji(int limit) {
emojis.entrySet().removeIf(entry -> entry.getValue() < limit);
messages.entrySet().removeIf(entry -> entry.getValue() instanceof EmojiMessage &&
!containsEmojiId(((EmojiMessage) entry.getValue()).getEmojiId()));
return emojis.size();
}
1
2
3
4
5
6
public void clearNotices() {
if (noticeDirty) {
messages.removeIf(message -> message instanceof NoticeMessage);
noticeDirty = false;
}
}

PS:这里还设置脏位,苯人有点内个….一朝被蛇咬十年怕井绳

三、性能问题

本单元hw9炸了一个点,喜提90(u‿ฺu✿ฺ)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void modifyDisJointSet(int id1, int id2) {
disjointSet.modify(id1, id1);
disjointSet.modify(id2, id2); //TODO* id完全有可能是负数
ArrayList<Integer> visited = new ArrayList<>();

//所有与 id1 相连的点的 father[] 标记为 leftId
dfs(id1, id1, visited);

//id2 检查父节点是否为 id1
if (disjointSet.find(id2) != id1) {
blockSum++;
visited.clear();
disjointSet.modify(id2, id2);
dfs(id2, id2, visited);
}

}

public void dfs(int id1, int id2, HashSet<Integer> visited) {
visited.add(id2);
Person person2 = persons.get(id2);
for (Integer id : ((MyPerson) person2).getAcquaintance().keySet()) {
if (visited.contains(id)) {
continue;
}
disjointSet.modify(id, id1);
dfs(id1, id, visited);
}
}

夜 难寐,在学M百思不得解

后经逐行比对,发现了神奇的东西 ArrayList<Integer> visited = new ArrayList<>()

ArrayListcontains方法是遍历啊啊啊啊啊啊啊啊啊啊啊啊啊

遂合并修复后,改投延迟重建

四、规格化设计

谈谈自己对规格与实现分离的理解(6分)

按照高中语文答题的规格,我回答6分3点

  • 规格里的数据结构与实现分离

​ JML用数组,你真敢用数组?

​ 这单元作业我只敢用Hash开头的东西……

  • 规格里的方法与实际实现方法分离

​ JML用三重遍历,你真敢在代码里写高复杂度算法?

​ 规格落地的具体实现需要我们进一步优化

  • 规格甚至能与实际实现的位置分离

​ 一开始以为JML写在这个类这个方法里的东西,不能跨类跨方法使用((

五、Junit

本单元的Junit测我认为是构造数据+规格检查

构造数据是非常玄学的东西,你生成的图需要既稀疏又稠密,你发的消息要五毒俱全

规格检查看起来善良多了,大多数情况下你需要影子network

一个进行错误代码的测试,一个进行JML规格的完全模拟,然后对拍结果

需要注意JML的如下部分:

  • 对每一个normal_behavior进行模拟

  • 对每一个ensure进行结果对拍检查

  • \not_assigned()元素不能被改变!!!

  • /*@ pure @*/检查所有对象容器全都不能改变!!!

六、学习体会

墙头马上遥相顾,一见知君即断肠,从年少情深走到相看两厌

那年杏花微雨,你说你是JML只需要看着注释写代码,但你实际上是《图论入门与数据构造》,终究是错付了!!!

但我也确实学到了好多算法知识,没有第三单元我永远不会想去学习bfs/dfs怎么写(懒狗是这样的),设置脏位这种工程上常用的方法也给我打开了新世界的大门,真是神奇的JML啊


hw9强测结果出的那天晚上,在M吃眼泪拌饭,细想了许多之前根本不敢想的东西

客观来讲我确实是不适合写代码、对算法所知甚少的笨蛋,我一直不敢承认不愿意承认,如果承认自己不如别人,我该怎么欺骗自己继续努力就会有用呢?

吴老师的言传身教帮助我解开了心结,五一我俩都没出去玩,任何时间,任何地点,超级笨蛋,猛敲键盘,我认真重新检查了代码,对JML仔细阅读,尽我所能优化,剩下两次强测果然没寄,所以即使是笨蛋,也能通过认真通过努力勉强的维持对自己最低的期待吧!

感谢吴老师,润物细无声的比惨陪伴,总能给我心灵慰藉,以至于前几次博客居然忘了感谢你ヾ(^▽^*)

感谢良师益友们,或指导或陪伴或鼓励,都使我精神更加稳定ヾ(^▽^*),遇到问题不再法典而是冷静解决

感谢课程组,连课程组都在努力修bug修指导书,我还有什么借口不努力呢ヾ(^▽^*)

最后一定要感谢永不言弃的自己!!!

爱人如养花,茁壮成长的我是自己种出的花 (u‿ฺu✿ฺ)