- 1. 测试过程
- 1.1 黑箱测试 & 白箱测试
- 1.2 各种测试
- 1.3 数据构造策略
- 2. 架构设计
- 2.1 图模型策略
- 2.2 维护策略
- 3. 性能 & 规格与实现分离
- 3.1 性能问题
- 3.2 规格与实现分离
- 4. Junit测试
- 5. 学习体会
1. 测试过程
1.1 黑箱测试 & 白箱测试
- 黑箱测试(Black Box Testing): 黑箱测试也被称为功能测试,是一种不考虑内部结构和实现的测试方法。在黑箱测试中,软件被视为一个黑箱,测试人员只关注输入和输出,而不关注软件的内部结构。黑箱测试的主要目标是验证软件的功能性和可用性。
- 黑箱测试的主要步骤包括:
- 确定测试的需求和条件。
- 根据需求设计测试用例。
- 执行测试用例并记录结果。
- 对比实际结果和预期结果,如果两者一致,则测试通过;否则,需要进行调试和修复。
- 白箱测试(White box testing): 白箱测试也被称为结构测试或逻辑驱动测试,是一种需要了解软件内部结构和工作原理的测试方法。在白箱测试中,测试人员需要了解软件的内部逻辑,以便设计覆盖所有可能路径的测试用例。
- 白箱测试的主要步骤包括:
- 了解软件的内部结构和逻辑。
- 设计测试用例以覆盖所有可能的执行路径。
- 执行测试用例并记录结果。
- 对比实际结果和预期结果,如果两者一致,则测试通过;否则,需要进行调试和修复。
1.2 各种测试
单元测试(Unit Testing):一种对软件中的最小可测试单元进行检查的过程。通常,一个单元是一个特定功能的一部分,如一个函数或方法。单元测试主要用于验证每个单元的功能是否正确。
功能测试(Functional Testing):一种黑箱测试过程,用于验证系统的功能是否符合预定的需求。这种测试主要关注的是软件系统的外部行为,而不是内部结构。
集成测试(Integration Testing):在单元测试的基础上,集成测试是将这些单元组合在一起并测试的过程。这有助于发现单元之间交互时可能出现的问题。实践表明,一些模块虽然能够单独地工作,但并不能保证连接起来也能正常的工作。一些局部反映不出来的问题,在全局上很可能暴露出来。
压力测试(Stress Testing):一种测试方法,用于确定系统在高负载或压力条件下的行为。这可以包括测试系统在大量用户同时访问时的行为,或者在资源(如CPU、内存或磁盘空间)有限时的行为。
回归测试(Regression Testing):在迭代开发的场景下,每次修改了软件的一部分或进行了一些更新后,都需要进行回归测试。这是为了确保修改没有引入新的错误,或者没有重新引入旧的错误。
1.3 数据构造策略
- 主要是通过随机生成的方法,以及针对单独指令进行压力测试。
2. 架构设计
- 由于接口中的JML已经给出了非常详细的规格指导,大框架只需要照着内容去搭建即可。
2.1 图模型策略
- 本单元主要要求我们以Person为点,Person间的relation为边,再加上一些奇怪的属性(比如Tag、Message)来构建一张图。由于担心自己在写并查集删边的时候写错或者爆掉导致一些奇怪的bug,我并没有使用并查集的数据结构,只是用了一些朴素的搜索以及一些简单的优化,达到尽量不要出现$O(n^2)$的方法。事实证明,这样也能达到评测的需求。
2.2 维护策略
- 维护了一些简单的属性,如Network的tripleSum、Person的BestfriendID、Tag的一些平均数、平方和等等。
3. 性能 & 规格与实现分离
3.1 性能问题
- 在第十次作业中,由于偷懒把获取Tag的ValueSum的方法写成了$O(n^2)$,后来将其改成$O(m)$就过了(虽然这样感觉更多是针对数据点来debug,出现边数多的图时也可能会爆,汗。采用动态维护的话才能根本性解决这一问题)
// 原先n^2的写法
public int getValueSum() {
int valueSum = 0;
for (Person person : persons.values()) {
for (Person other : persons.values()) {
if (person.isLinked(other)) {
valueSum += person.queryValue(other);
}
}
}
return valueSum;
}
// 改成边数的复杂度
public int getValueSum() {
int valueSum = 0;
for (Person person : persons.values()) {
for (Person other : ((MyPerson) person).getFriends().values()) {
if (persons.containsKey(other.getId())) {
valueSum += person.queryValue(other);
}
}
}
return valueSum;
}
3.2 规格与实现分离
- 在本单元中,规格为我们定义了一种基础实现方法,我们的任务是优化这种方法,确保优化不引入副作用。例如将 EmojiList 和 EmojiHeatList 合并成一个 HashMap 等。
- 我们作为实现者,可以将 JML 看成一个更详尽的需求指导书,而在高性能要求的实现场景下,还需另起炉灶,采用合适的数据结构与算法来进行代码编写。
4. Junit测试
主要有以下几点需要注意:
- 深拷贝需要检测的对象
- 数据生成时采用随机的构造方法,但同时需单独构造极值的测试数据,专门用来测试边界条件
- JML 中出现的属性如 pure、invariant、not_assigned 以及每条 ensure 都需考虑到
- 迭代场景下注意使用回归测试
5. 学习体会
- 经过这一单元的学习,我了解了 JML 规格和其语法,也在强侧出 bug 后对规格与实现分离有了更深刻的认识。
- 在实际的软件开发中,我们其实不需要对每个方法都大费周章,设计如此详细繁杂的规格,但在高精度开发场景下,比如航空航天领域,我们需要严格满足规格的要求,以保证系统的质量。