BUAA OO 第三单元总结

BUAA OO 第三单元总结

面向对象 JML 系列 — 开启规格化设计之旅🎈

第一部分:测试分析

黑盒测试与白盒测试

对于测试,从总体上可以分为黑箱测试与白箱测试两大类。

Note 1 White-box testing (also known as clear box testing, glass box testing, transparent box testing, and structural testing) is a method of software testing that tests internal structures or workings of an application, as opposed to its functionality (i.e. black-box testing). In white-box testing, an internal perspective of the system is used to design test cases. The tester chooses inputs to exercise paths through the code and determine the expected outputs. White-box testing can be applied at the unit, integration and system levels of the software testing process.

— “White-box testing”, Wikipedia.

Note 2 Black-box testing, sometimes referred to as specification-based testing, is a method of software testing that examines the functionality of an application without peering into its internal structures or workings. This method of test can be applied virtually to every level of software testing: unit, integration, system and acceptance.

— “Black-box testing”, Wikipedia.

黑盒测试不关注程序的内部细节,只关注正确的输入能否得到正确的输出,是一种 specification-based testing。这就恰恰对应了我们本单元的主题 —— 规格化设计。

与之对应,白盒测试需要关注内部的逻辑结构,因此在设计测试用例时,我们需要覆盖各种控制流与数据流。

在这里插入图片描述

测试的进一步划分

单元测试

单元测试,顾名思义,就是对每个设计单元的验证测试,通常是一种白箱测试。

需要强调的是,这个单元是 最小的单元

从 OOPre 以来,单元测试已不陌生,每完成一个小单元的构造就进行测试是必要的。如果等到代码规模变大再来寻找“犄角旮旯”的 bug 是极其痛苦而且得不偿失的。

集成测试

每个单元正确并不意味整个软件就正确。

通过集成测试,来看不同单元接口之间的协同配合工作是否正常,一般是由黑盒测试与静态白盒测试相结合。

系统测试

系统测试层次更高,是对于整个系统的测试,这一般就只能是黑盒测试了。

比如,第二单元的强测作为黑箱测试并不关注程序细节,因此“围师必阙”的 bug 就测不出来,而在白箱测试的互测中成为攻击主力。

压力测试

压力测试通过构建极端大规模数据、边界数据来进行测试。

比如第三单元 HW10 强测中大量输入同一类指令来测试。

回归测试

回归测试就是当代码迭代后再跑一遍以前的测试数据,确保迭代是不具有破坏性的。

数据构造的策略

自测

提交公测前,当然要对自己的程序进行白盒测试:)。

通过手动构造数据,确保各种正常的异常的输出都无误,从而保证控制流和数据流的分支覆盖。

互测

在与算法紧密相关的第三单元,在互测中采用压力测试针对时间复杂度进行攻击。

大规模的“query”指令或者大规模的“modify”不仅构造容易,而且命中率高,能够达到压力测试的目的。

第二部分:架构设计

第三单元,架构“设计”已经提供好了,我们主要干的是“构造”的事。

图模型的构建

  • Network 是一张图,图上又包括 PersonMessage 两类数据实体:
    • Person 是图上的节点,以邻接表的形式关联邻接点;
      • Tag 作为 Person 的属性,对邻接点进行了更细致的性质描述;
    • Message 则是对图上关系的更细致的性质描述。

第三部分:性能优化

如果按 JML 字面去构造,那强测互测必然是寄了。这也就体现了规格与实现的分离。

算法优化

本单元中的时间复杂度往往与 query 类指令有关。

先来谈谈其中相对来说比较 trivial 的指令——isCircle(以及类似的 queryBlockSum)、queryShortestPath。这类指令,直接采用图论中与之对应的方法,如 DFS, BFS, Dijkstra 等等,即可将复杂度降到 O ( n ) O(n) O(n) 级别。

指的一提的是,MyNetwork 这个类本身方法就很多,极其容易变得臃肿。出于避免超 500 行的考虑,同时也为了贯彻面向对象的思想,我引入新的类 Solver (求解器)作为 MyNetwork 的属性。

在这里插入图片描述

这样,MyNetwork 就专门负责派发各种异常,然后具体的算法求解过程移交 Solver完成。这样代码便变得清晰,不同职责也实现了解耦。

动态维护:时间复杂度从 query 到 modify 的转移

而其他的 query 类指令,就比较 non-trivial 了。

在 HW9 中,面对 queryBlockSumqueryTripleSum 指令,很好的方法是在添边、删改边的时候动态维护。

但是多想一下,就会发现时间复杂度并没有消失,只是从一类指令(query 类指令)指令转移到另一类指令(modify 类指令),如果 modify 的数量远大于 query,那时间复杂度也不低。

脏位维护:时间复杂度的 Trade-off

以上的问题是,时间复杂度只是在“转移”,更进一步的优化可以是将其“吸纳”。

受益于算法中线段树延迟修改的思想以及计组 OS 中 TLB 的脏位管理的启发,可以对查询的数据设置脏位,在有 modify 动作时并不进行维护,而是置脏位,等到 query 的时候再刷新数据并重置脏位。

第三单元的 modify 类指令主要有这么 5 个:addPersonaddRelationmodifyRelationaddPersonToTagdelPersonFromTag

针对的不同的 query 类指令,我采取的维护措施如下:

在这里插入图片描述

有的依靠动态维护,有的依靠脏位维护。

有的既动态维护又脏位维护。比如 queryBestAcquaintance,我并没有采用从容器上入手(因为我不想改以前的代码了 :))。对于一个 Person,当增加一条边时,只要维护的最大边权是 单调不减 的,我们的动态维护就是可靠的;而删改一条边时,依靠脏位刷新来保证可靠性。

规格与实现的分离

综上,我们并没有采用 JML 字面的构造方式。单元内部采用了与之不同的实现方式,而作为黑箱暴露出去的输入输出接口的正确性并没有因此改变。

这便体现了规格化设计/契约式编程中的规格与实现的分离。

第四部分:Junit 测试

这次的 Junit 测试任务设置让我们承担/体验了软件开发者以外的另一角色——测试者。

在中测提交测试几近用完之后 😠,我对测试还是有了很切身(非常切身)的体会。

"expect: Fail, actual: Pass" 的时候,说明我们的测试不充分;测试不充分说明以下两个方面没有得到保证:

  • 测试逻辑: 走查测试是否严格按照 JML 前置条件、后置条件等等的描述来全面测试的;
    • 检查 ensure:从数理逻辑的角度遍历对象,看是否满足 ∀ / ∃ \forall /\exist ∀/∃ 的要求;
    • 检查 pure/assignable:比对方法调用前后的对象的是否严格相同;
    • 检查 result;
    • 等等
  • 测试数据: 数据不能只强调 强度,还要强度 多样性。(在 HW9 中,我同一类数据弱的、强的数据都上了但还是无法通过 :(,而增加其他生成逻辑的简单数据竟然就过了 :)
    • 数据强度:进行压力测试;
    • 数据多样性: 提高测试覆盖率。

第五部分:学习体会

  • 很好 HW9 指导书朝令夕改让我晕头转向,很好 HW10 玩时间复杂度让我绞尽脑汁,很好 HW11 50 多行的 JML 让我眼花缭乱……
  • 经过第三单元,我体会到了规格化设计的作用。的确,一个程序到底正不正确,或者到底错误不错误,错误的话是需求者提需求的错误还是开发者开发的错误,光凭口说是没有可靠性的。因此规格化设计是必要的。(虽然 JML ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ \rm **************** 唉,JML)
  • 我体会到契约式编程的作用。诚然,前 2 个单元自己掌控架构的设计是非常爽的,但在团队协作中,不能全凭自己的想法。
  • 我加深了对一些图论算法的掌握。值得一提的是,不同于与以往只关注最坏时间复杂度,我了解了最坏时间复杂度和平均时间复杂度的区别,初步体会到甚至后者在实际应用中起到关键作用。
  • 我对测试(尤其是经过中测 Junit 测试的鞭笞拷打)有了系统的认识。
  • 综上,本单元的体验还是不错的 😄。JML 反响也挺猛烈。当然也能理解,我个人猜测是,民用互联网软件如果出错了那你再维护就行了,但是军工类软件可不能闹着玩,因为其高度强调 可靠性,就像现在太空 🌏 中商业卫星拍摄的照片是非常清晰的,但国家型号拍摄照片的质量看起来没有“与时俱进”,这其实都是为了 可靠性

第三单元闯关完成 🌿

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值