BUAA OO 第三单元博客总结

郭俊杰 -22371240 学生 2024-05-18 16:45:30

测试过程

黑箱测试与白箱测试

黑箱测试是根据软件的需求或功能来设计测试用例,不需要了解内部代码结构或实现细节,只关注输入和输出以及软件功能是否符合预期。在测评网站上进行的测评就是一种黑盒测试。

  • 优点:比较简单,且从功能的角度出发更能知道可能会遇到的问题,并据此构造数据进行测试
  • 缺点:分支的覆盖率较低,对内部逻辑的测试有限,难以发现潜在的bug

白箱测试是一种需要了解软件内部代码结构和实现细节的测试方法。其重点在于检查软件的内部逻辑是否正确,是否符合设计要求,以及代码是否达到预期的覆盖率。在作业中针对具体的方法,考虑到每个分支的情况,编写junit进行测试即是一种白盒测试。

  • 优点:深入测试代码的内部逻辑,发现一些黑盒测试无法发现的错误。同时可以评估代码的覆盖率,更全面地了解测试的质量。
  • 缺点:需要了解内部的具体时间,时间成本较高。

在作业的测试中针对关键的方法编写junit测试,保证每个分支下方法的正确性。同时在代码完成后通过测评网站进行黑盒测试,在测试出错时根据出错的测试数据进行调试,查看代码内部的问题。通过黑箱测试与白箱测试的结合,可以大大提高测试的效率以及代码的正确性。

对单元测试、功能测试、集成测试、压力测试、回归测试的理解

  • 单元测试是针对软件中的最小功能单元进行的测试,通常是对单个函数、方法或模块进行测试。验证每个模块的功能的正确性,可以尽早发现问题。我们针对不同方法编写的junit即为单元测试。
  • 功能测试是对整个软件系统的功能进行测试,根据功能需求编写测试用例,并通过输入不同的数据来验证软件是否满足设计要求。
  • 集成测试是在单元测试后将各个功能模块组装在一起进行测试。确保各个模块之间的集成能够正常工作,不会产生冲突和错误。在作业中可以通过在junit中调用多个方法来实现多个功能模块的集成测试。
  • 压力测试是对软件系统在高负载和大并发情况下进程测试的方法。在作业中由于测评机数据的随机性,可能不会测试极端的数据下的运行情况。因此我们有必要通过分析方法的复杂度,选择复杂度高的方法在测试数据中大量调用,以此对代码进行压力测试。
  • 回归测试指在软件系统进行修复或升级后,重新运行之前的测试用例以验证修改是否引入新的错误。在作业中体现为:在源代码的基础上进行优化或进行bug修复后进行测评,保证修改后代码的正确性。

数据构造策略

  • 随机生成:为了保证数据强度将人数设置为最大,并保证一定的稠密性。由于本次作业涉及到异常的情况,需要根据jml描述中不同的异常情况构造数据,并保证每种情况下数据的数量,具体地可以为每种情况下数据的构造设置一定的概率。
  • 极端数据:针对复杂度较高的方法,可以在构造的数据中大量添加相关的指令进行压力测试,比如qtsqtvs等,这些查询函数如果设计不好很容易出现性能问题。
  • 边界数据:对于代码功能中一些出现特殊数字的方法也需要专门编写边界数据进行测试,如addPersonToTagtag大小超过1111的情况。此外还有MyPerson类构建堆时两个person对象进行比较时,若处理不当还会出现数据溢出的情况导致出错。

架构设计

UML类图:

img

图模型构建和维护

第一次作业

采用并查集维护连通分支,在并查集构建的过程中采用路径压缩和按秩合并来进一步优化。在删边时选择重建并查集,但并不是在每次删边时都重建一遍,而是在查询时重建,从而减少开销。在isCircle()查找时只需要查询两节点的根是否相同即可,复杂度为O(1)。在查询分支数量时遍历查找根的数量,复杂度为O(n)。

对于queryTripleSum()使用变量tripleSum进行维护,在关系增加或删除时遍历其中一人的好友,若为两人的共同好友则对变量'tripleSum'进行增减。

第二次作业

本次作业引入了Tag类,对于Tag类变量的均值和方差使用变量进行维护,在增减人时进行更改。对于ValueSum的维护,在增减人时需要遍历tag中所有人与此人的value进行维护。此外在netWork类中关系变化时也需要进行维护。通过定义HashMap<Integer, HashMap<Integer, ArrayList<Tag>>>类型的变量tags,记录同时拥有两人的Tag变量。当修改关系时通过该tags获得响应的Tag变量维护其valueSum的值。

对于queryCoupleSum()也是选择维护变量coupleSum。当修改关系时:修改前分别判断两人如果为一对couple中的一个则coupleSum减1,修改后分别判断两人如果为一对couple中的一个则coupleSum加1。同时注意有些情况下两人互为couple的情况。从而实现对coupleSum的维护。

对于queryShortestPath()函数由于只需要返回路径长度,因而选择dfs来实现,并从两个端点同时开始遍历,这样在面对稠密图的时候会有更好的性能。

第三次作业

deleteColdEmoji()方法中需要删除某个emojiIdEmojiIdMessage变量,因此我定义了HashMap<Integer, ArrayList<Integer>> emojiToMes变量,在增添消息时进行维护。这样在删除时只需要根据emojiId获取相应的消息序列,而不用遍历所有消息,减少了开销。

性能问题与修复情况

在hw9中由于在每次删边时都进行并查集重建,开销过大。通过修改为查询时查询时重建解决。
在hw10中queryCoupleSum()通过遍历查询实现,开销过大。通过修改为上面提到的维护方法解决。

对规格与实现分离的理解

规格中对于方法的描述只保证了基本功能的正确实现,但并不保证性能的良好。我们在具体实现时要选择合适的数据结构与算法。

  • 对于数据结构,我们通过观察规格中对数据的描述,得到数据的特征并据此选择合适的数据结构。如在Person类的规格描述中反复强调acquaintancevalue数组的长度相等,相同位置的对应关系,我们很容易想到使用HashMap类型的变量进行存储。
  • 对于算法,像queryShortestPath()方法的规格中只对最后返回的结果以及中间变量的内容作了约束,我们不可能据此来实现方法,需要自己选择算法来实现。而像int queryTripleSum()方法的规格其实就描述了实现该方法的一个算法,但该方法复杂度较高,我们仍需要考虑其他更优的算法。

Junit测试

在编写Junit时我们需要检查规格描述的每一个信息,包含:

  • 方法功能的正确实现,输出的正确性
  • 所有的异常情况的捕获
  • 不可变对象是否发生变化
    对于方法的规格描述出方法实现的一个算法的情况,我们可以直接采用该算法以保证检验代码实现与junit的一致性。

学习体会

通过本单元的学习让我初步了解了JML这一规范化语言。虽然在初次接触的时候抱怨其描述的繁琐和复杂,但是通过三次作业的完成,我逐渐感受到JML独特的优势:通过前置条件、后置条件和类不变式的规定,我能够更清晰地了解程序的预期行为,这种严谨清晰的表述也减少了我在代码编写中出现错误的情况。同时在阅读规格描述时我能够深入地理解方法的作用和实现细节,为我实现方法提供了思路。

我还学习到一些图论的经典算法,如并查集,最短路径等,以及方法优化的一些思路,如变量的维护,脏位的设置以及数据结构的合理选择等。

...全文
89 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧