BUAA 2024 面向对象 Unit3 总结BLOG

BUAA 2024 面向对象 Unit3 总结BLOG

一、前言

本单元的作业有一个贯穿始终且“饱受诟病”的关键词:JML,这是Java语言中的一种行为接口规范语言,可用于指定Java模块的行为,本质上是一种契约设计方法规范。

三次作业迭代的过程仅仅有需求的增加,且增加需求关联度并不像前两个单元那么大,与之前作业的不同之处在于,本单元的作业需求的增加仅仅通过JML语言的增加实现,且我们最后实现的测试方案也都围绕其进行。

值得一提的是,虽然按照课程组的要求,对性能要求并不高,也不需要过于追求算法的优化。但是根据实际的评测情况来看,还是需要慎重选择算法,对图的一些查询改变操作的策略有一定的设计。尤其是前两次作业,不仅要保证代码功能的正确性,还需要预防超时。因此,最终的目标不仅仅是学会阅读JML并将其转化为正确的代码,还需要兼顾一定的性能因素。

二、测试

本单元除了编写代码之外,还提出了测试的要求,涉及到了不同的测试方法、思路和策略。

1. 黑箱测试&白箱测试

首先上定义(定义来自维基百科):

  • 黑箱测试也称功能测试、数据驱动测试或基于规格说明的测试。测试者只知道程序的输入、输出和系统的功能,这是从使用者的角度针对软件的接口、功能及外部结构进行的测试,不考虑程序内部实现逻辑。

  • 白箱测试也称结构测试、逻辑驱动测试或基于程序本身的测试,测试程序内部结构或运行。在白箱测试时,从程序设计语言的角度来设计测试样例。测试者输入数据并验证数据在程序中的流动路径,并确定适当的输出,类似测试电路中的节点。

其实,抛开这些定义,一句话总结下来,就是:

黑箱测试只针对输入输出接口判断功能是否合乎规格,测试人员看不到内部,总之,实现过程无所谓,答案对就行;

白箱测试要将程序拆解开,测试人员对其中的每一段代码、每一个结构都要了解,判断数据流动过程是否与预期一致。

在本单元的测试中,黑箱测试和评测机的逻辑是一致的,只关注对于特定的输入,程序的输出对不对,只关注结果是AC还是WA或者其他错误。

而白箱测试,通常体现在我们在debug的过程中,想知道错误的来源而进行的测试,最常见的做法,是在每个方法中加入一定的输入输出,或者点击单步调试查看每一步的路径和结果,从而找出bug所在。但是,这是我们在发现错误之后才进行的测试,严格意义上来说不能算测试(因为测试是用来发现问题的)。事实上,白箱测试可以用一些自动化的方式实现,例如为每一个模块、方法单独编写Junit测试方法,检测每个模块、方法实现的正确性。

2. 单元测试

顾名思义,单元测试的意思是对软件中的最小可测试单元进行检查和验证。只是,对于什么事最小单元,没有固定的标准,但是在面向对象编程中,我们可以简单理解为一个方法、一个类、一个功能模块…具体最小到什么程度取决于我们测试的目的和粒度。

通常,单元测试和白箱测试联系到一起,在本单元的实现中,可以考虑用Junit实现,利用Junit方法,可以针对每一个方法编写测试程序,包括检验输入输出的匹配,是否符合JML规格,是否满足全局的不变式等等。

测试最小单元选择的不同可以实现不同目的的测试,对方法的测试可以确保方法实现的正确性,对某一类的测试可以确保其实现规格符合要求…

3. 功能测试

测试软件的功能是否符合预期,这部分的测试通常和黑箱测试联系到一起,通过构造数据检验程序的输出的正确性,只要程序的实现是正确的就可以通过测试。

值得注意的是,一个程序往往满足的需求包含不同的功能,不同的功能以不同的顺序组合也可能产生新的功能,因此进行功能测试的时候,应当采用恰当的策略覆盖不同的功能。

4. 集成测试

这部分往往是建立在单元测试的基础上的,单元测试已经检验了最小模块的正确性(例如方法、类的正确性),但是不同模块是要通过接口连接集成起来才能发挥作用,集成测试的目的是在集成这些不同的软件模块时揭示它们之间交互中的缺陷。

5. 压力测试

压力测试,不在常规条件下运行手动或自动测试,而是在计算机数量较少或系统资源匮乏的条件下运行测试。通常要进行软件压力测试的资源包括内部内存、CPU 可用性、磁盘空间和网络带宽。

而对于我们的U3,压力测试更侧重于性能的测试,即通过大量可能耦合交织的数据点,检验程序能否在有限的时间内完成,这项测试对程序的性能有了一定的要求。

在一般的压力测试中,还包含在一定的压力下程序的稳定性、程序的并行性。

6. 回归测试

简单来说,回归测试是当我们更改了程序的时候,需要对原来的功能进行测试,确保更改的部分没有破坏程序原来的可靠性和结构,即不对原来的程序结构产生影响。

本单元的程序具体体现在每一次增量开发的时候,不能破坏原有的功能,即增加新指令之后原来的指令实现还要和原来一样。理论上来说,因为此次作业是纯增量开发,如果非算法的更改,不应该改变破坏原有的代码结构。

数据构造策略

一方面,要针对功能进行测试,即对要求实现的不同功能模块进行测试,例如hw9中的维护一个社交网络的不同功能、hw10中的好友分组功能、hw11中的收发消息功能,对所有指令全覆盖,同时,对有依赖关系的不同功能调换指令顺序,判断实现是否正确;对于异常,考虑通过为每一种异常发生的每一种可能的情况手动编写非法数据,判断抛出异常是否正确。

另一方面,由于此次作业对于性能有一定的要求,因此可以考虑进行压力测试,首先构造完全图,再针对容易导致超时的方法专门生成大规模数据,例如对于某些耗时的查询操作反复调用,或频繁地改变社交网络的状态再查询,每一次只针对一个方法进行压力测试,在测试数据允许范围内做到极限测试,分析性能的优劣。

三、我的架构

需求描述:
  • hw9:本次作业要实现一个虚拟社交网络,维护了一个persons数组和person之间的关系;
  • hw10:本次作业增加了好友标签,在person中维护一个标签数组,在每个标签中维护一个persons数组,同时要维护标签中的一些基本参数;
  • hw11:本次作业新增消息系统,分为表情消息,红包消息,通知消息和普通消息,各自可以被一对一发送或群发。
最终架构设计图:

请添加图片描述

对于本单元的作业,主要的类为Network类,其中需要管理一个Person的数组,这里我用HashMap保存,记录idPerson的对应关系,同时,全局有一个同样由HashMap维护的Message数组,记录消息idmessage之间的对应关系。

Person类中,有一个消息队列List<Message>,同时记录其基本属性和社交情况。

Tag类中,有一个由HashMap维护的Person数组。

对于消息而言,有一个消息的接口Message,其有一个实现为普通消息,有三个接口——EmojiMessage,RedEnvelopeMessage,NoticeMessage继承Message接口,这三个接口分别有一个对应的实现。

同时,由于最后一次作业中,代码行数超过500,导致代码风格有问题,因此,我将Network类中的非实现接口的方法全都封装到了Tool类中。

图的维护:

社交网络的本质即是一个图,由于我们要维护的为一个没有权重的无向图,因此,可以考虑用一些基本的方法和数据结构进行一些简单的维护即可。

我利用了并查集维护了节点与节点之间的关系,对并查集的维护和修改发生在几处:加关系,删关系,查询连通的时候。

加关系:加的是A,B之间的关系,若A,B原本在一个树中,则不做任何改变,否则,将A的根节点置B;

删关系:先在A,B的好友列表中删除其关系,再维护并查集,先判断此时A,B是否连通,若连通,不做任何改变,否则,对A的分支和B的分支分别重建。

查询连通:此时,由于之前建立并维护的并查集没有进行路径压缩,可能导致最后的查询复杂度仍然较高,因此,我采用了在查询时路径压缩的策略。

四、性能问题

三次作业中,我并未出现性能导致的超时问题,但是,我仍然注意到了可能会导致性能出现问题的一些实现细节。

例如:用并查集维护图,提高查询连通性的效率;对于某些查询操作,例如查询三元组,查询最好的朋友,查询Tag的value总和等等操作,通过动态维护的方式,有效减少查询的时间;对于查询最短路径,采用BFS算法,同时设置全局脏位,若脏位为0,可以直接用之前保留下来的BFS结果,否则,重新使用BFS查找最短路径。

其实,很多方法的具体实现和JML的描述相去甚远,JML仅仅给出规格,但是若严格按照JML的描述实现方法,只能保证正确性,而性能表现会很差,因此,可以采取一定的算法或策略使得在不违背规格的前提下提高性能。

五、Junit测试

Junit可以天然地做到单元测试,在课程组给出的要求中,每一次作业都要对一个方法进行Junit测试。

其实这里的测试仅仅包含了对功能正确性的测试,因此只需要保证输入输出符合要求,满足JML规定的不变式和限制条件即可,因此本质上,测试就是对JML的翻译。

  • 对基本结构的翻译:前置条件,后置条件,副作用依次检验。
  • 对关键细节的翻译:如对pure关键字需要验证前后未产生任何副作用。
  • 对不变式的翻译:JML中会有全局的不变式,这是所有代码都要遵循的,因此此处也要验证。

除此之外,还要实现数据点的生成,可以考虑半自动化的方式,即由程序自动随机生成指令,但是人为控制指令出现的范围和概率,至于如何构造数据点可以参考测试的数据构造策略,Junit可以更多关注于查询操作的实现。

心得体会

整体而言,本单元的作业并无多少难度,可能几次作业都会在Junit中卡住,但最终都能够通过加强数据点的方式解决问题,且数据点本身也不用过强,至于要实现的任务本身,除了要考虑算法实现和设计的策略之外,并无太多需要注意的地方,只需要按照JML进行实现即可。

本单元的主题是JML,本单元与其他单元的最大区别,就是将自然语言描述的需求转移到JML语言的注释中去,也即希望我们不是通过指导书的描述而是直接阅读JML获取需求。

但是在我们实际上手的过程中,可能还是将JML语言转化为自然语言置于脑海中思考,再与往常一样将自然语言落实到代码中区,那这样一来,无非就是增加了翻译JML语言的工作,大脑就是一个从JMLjava语言的翻译工具。

用规格注释表达需求的方式的好处在于其推理逻辑的唯一性,从而避免歧义,这是其比自然语言描述的优点;但是其在表述的过程中,通常会出现将一件简单的事情描述得很冗杂,甚至导致容易在写JML的时候出错的情况出现。因此,我觉得在课程设置中,对于一些简单基本方法的实现或大家约定俗成的方法用自然语言描述即可,对于可能产生歧义的部分,加以JML的辅助,实现起来会更清晰。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值