BUAA OO UINT 3 总结

于恩泽-22371494 学生 2024-05-17 10:43:54

目录

  • 前言
  • 本单元测试过程
  • 黑箱测试&白箱测试 理解
  • 单元测试、功能测试、集成测试、压力测试、回归测试
  • 数据构造策略
  • 大方向
  • 样例制造
  • 架构设计
  • 总体架构设计
  • 图模型构建与维护
  • 实现与规格分离
  • BUGS
  • Junit测试
  • 学习体会
  • 总体感受
  • JML 与 Junit
  • 后话

前言

前两单元给我一种开创我代码新时代的豪情,这一单元给我的最大感受就是平静,平静地看规格,平静地迭代,平静的生活固然大家都在称赞,但是总感觉陷入了桎梏,不过也好,至少不用像之前一样每天都在战斗,爽!,可能这才是软件开发的常态?

本单元测试过程

黑箱测试&白箱测试 理解

  • 黑箱测试:只需要关注输入输出,根据规格说明或用户需求构建测试样例,完全不需要理会内部结构。

    ​ 例子:在自建评测机时随机生成测试样例,将结果与预期进行比对。

  • 白箱测试:已经掌握了源代码,了解程序的内部逻辑和结构,根据这些源代码构建测试样例,尽可能覆盖程序的所有路径。

    ​ 例子:在oo pre中要求的覆盖率测试,就是一种白箱测试,通过覆盖率确保每个分支都不出问题。

  • 运用:经过这一单元的测试工作,我发现这两种测试都有各自的问题

    • 首先是黑箱测试,最大问题就是数据强度和覆盖的问题。纯随机的数据,极有可能数据强度完全不足。在我搭建的demo版评测机中仅仅是将Tag部分的id纯随机了一下,强度就已经非常拉跨,直接改用受控生成了。另外,生成数据时的偷懒也极有可能酿成大祸,比如生成的数据可能缺少边界情况(在第二次作业漏掉了1111这个条件导致强测错了一个点),再比如我认为personId不可能出问题,直接采用0-10000顺序生成,结果因为(int溢出和初值问题)导致有同学因此错了点。
    • 白箱测试的话,工作量过大而且也很有可能在覆盖达标的情况下依旧出错,比如有同学因为bestAccqaintance的初值设置为-1导致错误,覆盖率测试确实显示覆盖到了这一条,但是它就是疏漏,白箱测试很难发现,但黑箱测试会发现这个问题。
    • 对此,我认为应该在对复杂方法进行白箱测试的同时,对整体也进行黑箱测试。

单元测试、功能测试、集成测试、压力测试、回归测试

  • 单元测试
    • 在大规模程序中,整体测试往往难以进行,通过对软件中的独立单元如函数、类等进行测试,确保它们的行为符合预期。
    • 例子: 我们编写的juint,对queryTripulesum等易错方法进行测试,这就属于单元测试。
  • 功能测试
    • 功能测试只是测试程序的功能性、正确性,确保程序符合预期的输出。
    • 例子: 在写评测机时先关掉timeout等限制,只考虑正确性,就能进行功能测试。
  • 集成测试
    • 在我们前面进行了单元测试的基础上,我们把这些经过测试的模块拼装在一起进行测试。通过构建集成测试用例,确保他们集成后还能正常工作。
  • 压力测试
    • 通过对我们的软件进行高负载测试,确定其在极限条件下的性能和稳定性。
    • 例子:在本单元中主要是对我们的程序进行大数据量的性能测试,比如测试极限条件下TripleSum的性能是否会超出限制。而在构建评测机时也可以通过抬高并发量模拟高并发等极端条件,发现TLE等问题。
  • 回归测试
    • 字面意思:“孩子们,我打赢复活赛了”。
    • 在进行了更改和修复之后进行的测试,确保程序不会出现正确性、功能、性能等方面的退化。
    • 例子:bug修复
  • 运用
    • 这些测试方法,本单元我基本都用到了
    • 单元测试 :有junit要求
    • 功能测试和集成测试:搭建了评测机,通过对拍解决正确性问题
    • 压力测试:为了模拟压力环境,我的评测机也搭成了高并发的评测机,为每个子进程限制5s的执行时间(因为官方服务器性能似乎不佳)。经过测试,如果能够在这样的情况下100轮测试均不TLE,在官方评测机大概率不出问题。
    • 回归测试:在改用了更高效的算法后要再回评测机进行测试,保证不发生倒退。

数据构造策略

大方向

本单元的junit编写还是挺考验测试数据编写水平的,当然这也演化出了两个方向:

  • 对指令随机生成:直接生成指令进行传入,早在oo pre时代就广为使用
  • 对数据随机生成:通过一些已经实现的方法直接构造network中的persontag以及message等,然后直接调用并测试指定方法。

我主要走的是第二个方向,其原因主要在于方便对数据进行备份,以防被测试的代码有悄悄改变数据的功能。

样例制造

我采用的是少量受控随机的策略:

  • 首先,手编数据是不可能的,太不优雅了
  • 其次,纯随机没必要,因为纯随机强度不一定够
  • 所以,以第三次作业为例,我主要考虑覆盖率的问题,比如
    • deleteColdEmoji的主要依据是limit,然后发展出删和不删两个分支,也就是说我们甚至都没必要去sendMessage,因为初始所有heat为0,limit取0会不删除,取>0就会全部删除,这样已经测试了这两个分支,没有必要取增加send,这会极大增加复杂度。
    • 然后随机生成4种message就行了,注意前后一致性的检查即可一次通过。
    • 在此基础上,如果还不放心,可以启动参数化测试,以防随机太非酋。
  • 基于这样的测试,可以用很少的时间和数据完成Junit部分

架构设计

总体架构设计

基本按照JML组织架构,做出的明显改动主要是设计了ExcepionCounter静态类,该类拥有各自静态的异常计数器实例,大大简化了异常类的构建:

public MyAcquaintanceNotFoundException(int id) {
        this.id = id;
        counter = ExceptionCounter.getAcquaintanceNotFoundCounter();
    //获取自己的计数器
        counter.NewException(id);
    //计数器++
}
@Override
public void print() {...
}

另一个明显改动是将并查集做成了单独的类,进行解耦。

最终图为

img

图模型构建与维护

  • demo使用了Arraylist纯按JML写,从此留下了卡到爆的心理阴影,之后几乎都是用了hashmap、hashset

  • 第一次作业:主要是查询是否连通这一操作费时间。对此,采用了维护并查集的策略,在新增人、关系和删除关系时进行维护。采用了局部更新的策略,通过广度优先遍历找到断关系时需要分开的人,然后对这部分进行重建并查集,性能还不错。

  • 第二次作业:主要费时间的是

    • Tag相关操作如Tag内value总和,这在对关系进行操作时维护,因为修改关系的两人在第三个人的Tag里,所以需要对所有共同邻居的所有tag遍历,放心,这不是O(n^2),这是O(m+n),并不会造成过度复杂。
    • 最短路径使用了两种方法并做了benchmark
      • 一种是迪杰斯特拉,但是做了高度的缓存,每次查询都是查到终点就停止然后保存现场退出,下次来如果没在现场的缓存中找到就在现场基础上继续进行迪杰斯特拉。现场保存在这个Person中的一个数据结构,当此人所在区块的关系发生改变,则无效化
      • 一种是双向广搜,这样相当于两圆相交,能少遍历很多点,但是不是很好做缓存
      • 最终这两种方法在大量随机查询时,点数100,较为稠密时持平,超过200迪杰斯特拉不如双向广搜。
      • 最终选择双向广搜
    • 最好邻居也使用了维护,每次来新人或修关系时判断是否需要重新找。
  • 第三次作业:

    • 没什么费时间的,消息直接使用了链表,似乎并没有什么。

实现与规格分离

经过TLE的提心吊胆,彻底理解了这一点。

  • 规格只保证最基础的正确性,别的什么也不管。
  • 对于一看就O(n^2)的方法,一定不要囿于规格,必须尝试高性能算法。
  • 按照规格写,几乎一定能通过功能测试,但是大概率被压力测试干掉。

BUGS

本次共计出现一个bug(为什么每次搭评测机偷懒都会出bug{{{(>_<)}}},上一次是第一单元hw2没测合法性错了俩),因为在做时忘记了1111这个限制条件并且做评测机时认为不会有人错就没有测这个。

其它主要讲讲hack别人的情况吧:

  • 前两次作业主要是抓别人TLE,直接用评测机生成一些比较阴险的数据(大量高复杂度操作),并且把本地评测机限时拉到3秒5(是的,有房友本地3秒9交上去被我爆了)。

  • 另外,对于qtvs操作可以用数学方法找一些极限数据,比如用函数计算出3000条数据的话,将600-700人加到一个tag里然后反复查计算次数会达到极值,可以直接爆按着jml写的同学。

  • 第三次作业主要是有房友爆除零异常,给大伙送福利了。

  • 私下hack到了两位同学,都是因为没有注意到第三次作业可能产生同id的两个tag对象,因为message里的那个 tag引用不会被回收, 脱离person后会直接失去控制。

  • 评测机的不足:没测id问题导致有房友逃过一劫,id这么简单,怎么会出问题呢。有同学在找最好邻居时使用优先队列并且采用id相减,这样如果相减超出int,就会发生可怕的事情

Junit测试

在上面讲了Junit的数据构造策略,此处再来补充一些Junit的使用技巧

  1. 与其说是测试,不如说是攻防战。一定要全面检测是否有数据被暗改,可以用深克隆或者“影子”数据备份。这个错误代码,给我一种他即将删库跑路的感觉。我确实不太理解为什么一个统计方法会暗改我的数据。
  2. Junit相比常规评测机的方便之处大概在于能够直接在内部构造指定数据进行单独测试,而且提供了覆盖率这一指标。在本单元中要测试的方法覆盖满了基本都能一次通过,不需要太考虑什么极端情况。
  3. 参数化测试很方便,可以快速构造数据和载入数据,是个不错的选择。
  4. BUT,如果你用参数化测试并且同时写了两个测试方法,一定要小心数据互相干扰,因为参数化测试似乎只会为这两个方法构建一份数据,主要数据共用的后果。
  • Junit的编写依据主要是规格,数据生成要符合规格,并且,一致性,尤其是assignablepure,一定要注意。
  • Junit效果还是不错的,基本写了Junit的方法都不会在其它测试中出现什么问题,也验证了Juint在检验代码规格一致性上的效果还是不错的。

学习体会

总体感受

感觉这一单元安逸中透露着焦虑。

  • 安逸是因为大部分方法按规格写就行,基本就是看图写话。
  • 焦虑是因为得做出很大的性能优化,而且由于评测机的不确定性要跟身边同学做很多性能对比,尤其是在最短路径那里就哪种方法更快发生了很大程度的分歧,最后靠benchmark解决。
  • 没有前两单元那么劲爆,总感觉少了点什么。

JMLJunit

  • JML初衷是好的,直接用规范化的语言描述规格,避免了自然语言的不确定性。本单元第三次作业有很多同学因为message的tag和人持有的tag可能发生重复而写出bug,如果用自然语言描述的话,我确实不敢想象混乱程度。
  • 没有要求我们写JML而是要求阅读也不错,工作量不是很大,挺休闲的。
  • Juint测试过的方法基本没见bug,说明它确实有效。而且经历了三次Junit编写,对数据构造水平也有所提升。不过一点小问题就是要测的出错的代码确实略显离谱,为什么一个统计方法会暗改数据。对assingale的测试确实是有点小难的。

后话

感觉自建评测机和Junit可以互补,对易错方法上个Junit保个险,其它用评测机测测,感觉会是个不错的选择。

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

301

社区成员

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

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