1.分析本单元的测试过程
黑箱测试
黑箱测试 (Black Box Testing) 是一种软件测试方法,测试人员在不考虑软件内部结构和实现的情况下,对软件功能进行测试。这种方法关注软件的输入和输出,以验证软件是否按照预期工作。
白箱测试
白箱测试(White Box Testing),也称为结构化测试或透明盒测试,是一种软件测试方法,测试人员在了解软件内部结构和实现的情况下进行测试。与黑箱测试不同,白箱测试关注软件的内部逻辑、路径、条件和数据流。
单元测试
单元测试(Unit Testing)是一种软件测试方法,旨在验证软件中的最小可测试单元(通常是一个函数、方法或类)的正确性。单元测试在软件开发过程中扮演着关键角色,帮助确保代码在开发早期阶段就能发现并修复缺陷。通常有测试驱动开发(TDD),断言(Assertions),模拟(Mocking),覆盖率分析等方法。
本单元三次作业分别对queryTripleSum, queryCoupleSum, deleteColdEmoji方法进行了单元测试。
功能测试
功能测试(Functional Testing)是一种软件测试方法,旨在验证软件系统的各项功能是否按照需求规格说明书或业务需求正确工作。功能测试关注的是软件的功能行为,而不是内部实现细节。
集成测试
集成测试(Integration Testing)是一种软件测试方法,旨在验证各个模块或组件在集成后的交互行为和协同工作情况。它介于单元测试和系统测试之间,确保模块之间的接口和交互符合预期,从而保证整个系统的稳定性和可靠性。
压力测试
压力测试(Stress Testing)是一种软件测试方法,旨在验证系统在高负载或极端条件下的性能和稳定性。通过压力测试,测试人员可以识别系统的瓶颈和潜在问题,确保系统在超出预期工作负载的情况下仍能正常运行或至少能优雅地失败。
回归测试
回归测试(Regression Testing)是一种软件测试方法,旨在验证在对软件进行修改、升级或修复缺陷后,未修改的部分仍然正常工作。它的目的是确保新代码的引入不会影响现有功能的正确性。
构造数据策略
HW9
要求:为 Network 类中的 queryTripleSum 方法编写 Junit 单元测试。
MyNetWorkTest 类中有 addPerson , addTriple ,
deleteRelation 等方法。
一开始调用addPerson()方法两次,构建两个新的person
之后每次调用addTriple() 或者 deleteRelation()方法
public void addTriple() {
int p1 = random.nextInt(persons.size());
int p2;
do {
p2 = random.nextInt(persons.size());
} while (p1 == p2);
addPerson();
int p3 = persons.size() - 1;
addEdge(persons.get(p1).getId(),persons.get(p3).getId());
addEdge(persons.get(p2).getId(),persons.get(p3).getId());
addEdge(persons.get(p1).getId(),persons.get(p2).getId());
try {
myNetwork.addRelation(persons.get(p1).getId(),persons.get(p3).getId(),1);
} catch (Exception ignore) {}
try {
myNetwork.addRelation(persons.get(p2).getId(),persons.get(p3).getId(),1);
} catch (Exception ignore) {}
try {
myNetwork.addRelation(persons.get(p1).getId(),persons.get(p2).getId(),1);
} catch (Exception ignore) {}
optNum++;
}
构造数据的核心在于addTriple函数,它的逻辑是每次找两个不同的人,然后加入一个新的人,这三个人两两之间尝试加边,这样能构造出很多三角关系。
HW10
要求:为 Network 类中的 queryCoupleSum 方法编写 Junit 单元测试。
这次作业没有特殊的构造策略,随机构建一个图即可。
public void testCoupleSum() {
optNum = 2;
addPerson();
addPerson();
int op;
while (optNum < 300) {
op = random.nextInt(3);
if (op == 0) {
addPerson();
} else if (op == 1) {
addRelation();
} else {
deleteRelation();
}
testCoupleSum();
}
}
HW11
要求:为 Network 类中的 deleteColdEmoji 方法编写 Junit 单元测试。
单元测试只需要测试这一个方法是否正确,所以我们把关注点放在Person之间的Message上,不需要构造复杂的人物关系。
构造策略是:
首先加入person1, person2, 并addRelation(person1,person2,value)
之后在两个人之间不断地addMessage 或者sendMessage,之后对deleteColdEmoji方法进行测试。
while (optNum < 1000) {
times = random.nextInt(100);
for (int i = 0;i < times;i++) {
sendAddMessage();
}
limit = random.nextInt(20);
testDeleteColdEmoji(limit);
}
添加和发送的Message需要包含全部种类的信息。
2.本单元的架构设计
本单元维护了一个社交网络。
每个Person可能有多个好友,他们之间的关系可以添加或删除。
每个Person可能有多个Tag,每个Tag可能包含多个他的好友。tag的含义类似于好友分组。
每个人可以向他的一个朋友或者一个tag里面的所有朋友发送Message,Message的种类有Notice,RedEnvlope,Emoji,和普通的Message。
HW9
核心的问题是 queryTripleSum , queryBlockSum , isCircle
对于tripleSum ,我们可以在每次加边和删边时进行维护。
例如:对点A和B加边
tripleSum += count;
count的含义是 到A点距离为1的点 与 到B距离为1的点 的交集的个数。
对于blockSum,通过并查集判断整个图共有多少个联通块。
isCircle可以通过并查集判断两个点是否在同一个联通块。
因为有删边操作,而并查集是无法维护删边操作的,所以我们需要在删边之后对并查集进行重建。为了避免反复重建,我们增加一个懒标记rebulid,若删边则rebulid = true,需要用到并查集且rebulid == true时再进行重建。
HW10
核心问题是 queryTagValueSum 需要动态维护
MyTag 类中 addPerson,delPerson,deleteRelation,modifyRelation 和 addRelation时需要更新valueSum。
public void addPerson(Person person) {
for (Integer id : persons.keySet()) {
if (persons.get(id).isLinked(person)) {
valueSum += 2 * persons.get(id).queryValue(person);
}
}
persons.put(person.getId(),person);
}
public void delPerson(Person person) {
persons.remove(person.getId());
for (Integer id : persons.keySet()) {
if (persons.get(id).isLinked(person)) {
valueSum -= 2 * persons.get(id).queryValue(person);
}
}
}
public void deleteRelation(Person person1,Person person2) {
if (hasPerson(person1) && hasPerson(person2)) {
valueSum -= 2 * person1.queryValue(person2);
}
}
public void modifyRelation(Person person1,Person person2,Integer oldValue,Integer newValue) {
if (hasPerson(person1) && hasPerson(person2)) {
valueSum += 2 * (newValue - oldValue);
}
}
public void addRelation(Person person1,Person person2) {
if (hasPerson(person1) && hasPerson(person2)) {
valueSum += 2 * person1.queryValue(person2);
}
}
值得注意的是在MyNetwork中deleteRelation时,要在所有Tag中执行deleteRelation方法,对于删的这条边两端的person1,person2,如果一个人在另一个人的一个tag里面,也要分别执行他们各自tag中的delPerson方法。
HW11
无需要动态维护的查询,按照JML架构编写代码即可,注意控制时间复杂度在O(n)。
3.分析作业中出现的性能问题
在HW9中,由于我每次删边都将并查集进行了重建从而超时,为了解决这一问题,我加入了懒标记rebuild,当需要使用并查集且rebuild = true时再进行重建。
规格是对要实现目的的一种严谨描述,实现可以通过各种方式,但是最终达成的效果要满足规格要求。在本单元作业中由于数据范围限制,所有方法都需要在O(n)内完成,因此我们在实现方法时需要进行各种优化,只要达成的结果满足规格描述即可。
4.JUnit测试
规格为JUnit测试提供了严格的保障,我们只需要构造合理的数据,之后按照JUnit中ensure要求逐句验证即可。同时需要注意pure和assignable等要求。构造数据的策略上文已提到。
为了保证pure等要求,通常需要在调用测试文件方法前对相关数据进行深克隆,查询之后在get相关信息进行比较,保证无关信息不被修改。
5.学习体会
本单元主要内容为JML和契约式编程,JML可以让代码编写和测试变得更加地严谨可靠,也可以让自己代码架构变得清晰简单,很多方法可以直接按照规格信息进行编写。本单元规格化编程的思想让我很有收获。