BUAA-OO-UNIT3-Summary
面向对象设计与构造第三单元总结
- 分析本单元的测试过程
- 谈谈你对黑箱测试、白箱测试的理解
- 对单元测试、功能测试、集成测试、压力测试、回归测试的理解
- 是否使用了测试工具
- 数据构造有何策略
- 梳理本单元的架构设计,分析自己的图模型构建和维护策略
- 分析作业中出现的性能问题及其修复情况,谈谈自己对规格与实现分离的理解
- 本单元中同学们实现了OK测试方法,请同学们思考OK测试对于检验代码实现与规格的一致性的作用,有何改进何建议
- 本单元学习体会
作业要求
- 单元第一次作业,需要完成的任务为实现简单社交关系的模拟和查询,学习目标为 入门级JML规格理解与代码实现。
- 单元第二次作业,需要完成的目标是进一步实现社交关系模拟系统中的群组和消息功能,学习目标为进一步掌握JML规格的理解与实现。
- 单元第三次作业,需要完成的目标是进一步实现社交关系系统中不同消息类型以及相关操作,学习目标是理解JML规格在面向对象设计与构造中的重要意义,并掌握利用JML规格提高代码质量的能力
这个单元主要根据课程组提供的类、方法及其相关的 JML 指导,由我们自行书写的这些要求的实现。本单元指导书内容较少,主要的信息来源是我们的官方代码包。阅读 JML 并提取信息,完成代码实现是本单元的主要难点和内容。
思路重点
由于我们实现的类 Person
、Group
、Network
含有包含关系,并且每个类中都含有不同的字段和相关信息。对于整个代码的实现,我们要考虑的点有以下几个:
- 这些信息由输入提供,所以就需要合理选择数据结构对其内的对象进行管理。
- 由于会出现大量的
query
(查询)操作,所以如何快速获取对象、查询信息是提高程序效率的一大关键。 - 同时在后两次作业中,还出现了修改、破坏已有数据的修改操作,对已有数据的维护需要着重考虑。
- 在最后一次作业中出现了某个节点的最小环查询,我们需要提高图论中的算法效率以满足大量的查询请求。
测试过程
本单元测试过程
本单元构造了自动数据生成器,针对网络中节点数、组的数量、关系的数量和抛出异常的综合性进行了考虑,通过与同学输出等进行对拍的形式进行黑盒测试。检测出了多个异常、Group 计算中发生的错误。
黑箱测试 & 白箱测试
黑箱测试又称为“功能测试”,是将测试对象看做一个黑盒,在并不考虑软件产品的内部结构和处理过程的基础上对软件产品进行功能测试。
白箱测试是测试人员要了解程序结构和处理过程,按照程序内部逻辑测试程序,检查程序中的每条通路是否按照预定要求正确工作。它主要的针对被测程序的源代码,测试可以完全不考虑程序的功能。
简单来说,黑盒测试不关注程序的内部构造、数据的存储与处理方式,只关心输出的结果是否有效、程序的最终效率如何。黑盒测试更多地用在系统级别的验收、检测上。因为系统中的黑盒范围大,各个模块能够产生更多的交互,检验模块间的组合正确性。
而白箱测试考虑的方向则大相径庭。白箱测试更注重信息在程序的各个方法(模块)内部的流动,保证其在单个方法内的正确性,再将多个方法的接口连接,从而检查整个系统的正确性。白箱测试由系统逻辑而来,需要测试人员知晓程序的工作方式,并据此进行测试用例的编写,并对覆盖情况进行分析。
单元测试、功能测试、集成测试、压力测试、回归测试
单元测试:完成最小的软件设计单元(模块)的验证工作,目标是确保模块被正确的编码,使用过程设计描述作为指南,对重要的控制路径进行测试以发现模块内的错误,通常情况下是白盒的,对代码风格和规则、程序设计和结构、业务逻辑等进行静态测试,及早的发现和解决不易显现的错误。
单元测试大多为静态的白盒测试,它从系统的功能指导书出发,静态地检测代码的功能,避免要求读错等基础错误。
功能测试:使用人工和自动手段来运行或测试某个系统的过程。其目的在于检验它是否满足规定的需求或弄清楚预期结果与实际结果之间的差别。
功能测试,一般是对单个模块测试,提供指定输入后检测输出,从而检测功能实现的正确与否。
集成测试:通过测试发现与模块接口有关的问题。目标是把通过了单元测试的模块拿来,构造一个在设计中所描述的程序结构,应当避免一次性的集成(除非软件规模很小),而采用增量集成。
- 自顶向下集成:模块集成的顺序是首先集成主模块,然后按照控制层次结构向下进行集成,隶属于主模块的模块按照深度优先或广度优先的方式集成到整个结构中去。
- 自底向上集成:从原子模块开始来进行构造和测试,因为模块是自底向上集成的,进行时要求所有隶属于某个给顶层次的模块总是存在的,也不再有使用稳定测试桩的必要。
集成测试,则是将经过单元的功能测试后的模块进行一定程度组合,检测其能不能满足某种实际情况的应用。这种测试要求的综合性更强。
压力测试是一种基本的质量保证行为,它是每个重要软件测试工作的一部分。通过在有限时间内进行大量功能上的请求和并行请求以测试程序处理的效率与可靠性。
在通过集成测试后,我们可以认为程序已经有了处理基础请求的能力。下一步进行的压力测试就是检测程序的效率和可靠程度。
回归测试:回归测试是指在发生修改之后重新测试先前的测试用例以保证修改的正确性。理论上,软件产生新版本,都需要进行回归测试,验证以前发现和修复的错误是否在新软件版本上再次出现。根据修复好了的缺陷再重新进行测试。回归测试的目的在于验证以前出现过但已经修复好的缺陷不再重新出现。一般指对某已知修正的缺陷再次围绕它原来出现时的步骤重新测试。
在检测过程中如果发生了修改与版本迭代,为了保证原有正确的样例、功能仍然可用,我们需要进行回归测试,保证之前正确的功能仍然正确,并且能把错误的样例修复。我们的 bug 修复采用的也是这种回归测试的思路。
测试工具 & 数据构造
课程组推荐我们进行单元测试,但限于网络资源的覆盖性与时效性,我个人没有使用单元测试工具进行白箱测试,而是使用了数据构造并对拍的黑盒测试方式进行测试。
在进行自行数据构造的过程中,我主要对人数进行了限制,这样可以保证边、组等信息更加集中,查询数据时返回不为无效值的期望也会更高,测试的效果更好。
架构设计
图模型
本次作业的图结构中,节点是 Person
类型,边是 person
之间存在的 acquaintance
关系。我将 acquaintance
保存在每个 Person
结构体内,也就是说通过查询节点,可以获得与其相关的边。
同时,网络结构由 Network
结构实现,其中包含了一个所有 Person
的集合 people
,以及包含所有边的集合 arcPool
。他们都以 HashMap
的形式存在,便于对整个网络进行查询、获取操作。
维护策略
由于本单元作业存在大量的查询、计算操作,所以对数据进行动态维护十分重要,良好的动态维护结构能降低多次重复查询时的高消耗
对于指令:
query_group_value_sum id(int) |
由于 Person
的年龄不会发生改变,所以在 addToGroup
指令发生时,对 Group
的 age 进行动态维护就可以避免反复的方差计算。由于只有 addToGroup
、addRelation
、modifyRelation
三个途径可以更改 value
(前者直接改变 Group
后两者通过更改 Person
的 value
来实现对所属的所有 Group
进行改变),同理此时应该进行动态维护。
对于指令:
query_best_acquaintance id(int) |
由于 acquaintance
和 couple
只在 addRelation
和 modifyRelation
时发生改变,所以直接在这两类指令后进行更新即可,其余情况直接返回缓存的状态值即可满足要求。
性能与修复
在前两次作业中,由于动态维护的实现较为完备,所以没有出现性能问题。在第三次作业中由于课程组劝导我们不要过度卷算法,所以在查询最小块时造成的部分性能损耗没有做修订,最后导致一个点出现了超时的情况。在 BUG 修复环节,对于这部分损耗的性能做了部分优化,通过了修复的回归测试。
规格与实现
课程组提示我们要考虑规格与实现分离,我个人认为,规格是程序开发者对于程序功能的一种约束,它从黑盒的角度出发,只对输入、结果、副作用做了约束。而实现则不仅需要考虑以上这些,还要考虑程序内部的逻辑结构。实现可以不完全按照规格的要求,如果能保证输入、输出、副作用等前后置条件满足,也能称其满足了规则。
OK 测试
对于检验代码实现与规格的一致性的作用
我认为 OK test 对于编写代码没有什么有效的帮助,因为在我们实现的 OK test 中,并没有存在检查出被检测方法错误的情况,反倒是这个 OK test 出过问题。
但它对于完全按照规格实现方法的程序员的还有点作用,它能帮你看出发生错误的操作具体在哪一步,从而根据当前状况反推、修改代码,修复 bug。而且我没有查出什么是“规格的一致性”。。。在这我无法做出解释。
总结体会
实际上,尽管本单元整体难度低,往届的学长也说第三单元的难度略有降低,但是实现的效果或者说最终成绩却和前两单元基本持平。最后一次作业在课程组的压力测试下没有抗住。
在实现规格时,不仅要考虑规格的要求,即完成度;也要考虑能否通过完善的方法去改进算法的实现,即效率。没有效率,你再对也没用,没有完成度,算的再快也没用。
本单元在设计方面的主要收获是学会了一种严谨的表达设计的方式。通过实现 JML 书写的方法引导,我对于如何表达设计以及准确表达自己的设计的重要性有了更深入的理解。
但可惜没有抗住,无言。