北航2024春OO第三单元总结

李昊琛 + 22371042 学生 2024-05-17 08:05:57

北航2024年春OO第三单元博客总结

1 测试分析

1.1 黑箱测试与白箱测试

1.1.1 黑箱测试

黑箱测试是针对于功能的测试,是以用户角度从输入数据与输出数据的对应关系触发进行测试的。课程组提供的评测机就是一种黑箱测试,它给出了指定的输入和指定的输出,要求我们用自己的程序运行课程组给出的样例并得到课程组预期的结果。此外我们检查某些pure的JML对应的代码是否满足规格时也是用的黑箱测试的思路(比如查询方法),只需要判断输入输出是否正确对应即可。

1.1.1.2 白箱测试

白箱测试是黑箱测试的反义词,也可以称为基于代码的测试。在白箱测试中,程序对测试者是公开的,我们要通过检查软件内部的逻辑结构,对软件中的逻辑路径进行覆盖测试,在程序不同的地方设立检查点,检查程序的实际运行状态与运行预期是否一致。
在白箱测试中覆盖标准是非常重要的指标。在白箱测试中,我们需要满足语句覆盖(每条语句至少执行一次)、判定覆盖(每个判定的每个分支至少执行一次)、条件覆盖(每个判定的每个条件应取到各种可能的值)、条件组合覆盖(每个判定中各条件的每一种组合至少出现一次)、修改条件覆盖(每一个判断的所有可能结果都出现过、每一个判断中所有条件的所有可能结果都出现过、每一个进入点及结束点都执行过、判断中每一个条件都可以独立的影响判断的结果)。
白箱测试是直接针对于代码的测试,其对于代码bug检测的能力是强于黑箱测试的,但是相应的,白箱测试的工作量也显著高于黑箱测试。

1.2 测试方法

1.2.1 单元测试

单元测试是指对软件中的最小可测试单元进行检查和验证,对于Java来说单元指一个类。单元测试一般是由开发人员自己完成的,受益者也是开发人员自己。我们经常会在写完代码之后自己运行一下判断程序是否正确,这就是临时单元测试。在程序开发中,越早发现bug节省的时间越多,所以单元测试对程序员本身是非常重要的内容,我们在程序编写过程中应该积极地进行单元测试,这对我们程序开发是非常有帮助的。

1.2.2 功能测试

功能测试就是对产品的各功能进行验证,根据功能测试用例,逐项测试,检查产品是否达到用户要求的功能。黑盒测试就是一种常见的功能测试。功能测试用于检测程序的正确性,但是如果程序出现了bug,功能测试只能告诉我们哪一功能出现了问题,具体在哪里出现了什么问题需要我们自己去寻找。功能测试是一个比较宏观的测试方法。

1.2.3 集成测试

集成测试,也叫组装测试或联合测试。在单元测试的基础上,将所有模块组装成为子系统或系统,进行集成测试。我们可以将集成测试视作单元测试的扩展,单元测试关注于最小测试单元的本身,而集成测试在单元测试通过的情况下则是关注于单元与单元之间的连接的正确性。所有的软件项目都不能摆脱系统集成这个阶段。不管采用什么开发模式,具体的开发工作总得从一个一个的软件单元做起,软件单元只有经过集成才能形成一个有机的整体。具体的集成过程可能是显性的也可能是隐性的。只要有集成,总是会出现一些常见问题,工程实践中,几乎不存在软件单元组装过程中不出任何问题的情况。所以我认为集成测试是程序测试后期的重要工作之一。

1.2.4 压力测试

压力测试,也称为强度测试、负载测试。压力测试是模拟实际应用的软硬件环境及用户使用过程的系统负荷,长时间或超大负荷地运行测试软件,来测试被测系统的性能、可靠性、稳定性等。我们已经见过了很多压力测试,在第二单元电梯中将性能设置为最差然后在最后允许的时间发出最大允许数量的请求就是一种压力测试。在第三单元中比较典型的压力测试就是第10次作业中连续数次调用求和指令。压力测试是判断程序性能的重要方式之一,在实际生活中要保证安全或效率的话压力测试是非常重要的测试手段。

1.2.5 回归测试

回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。自动回归测试将大幅降低系统测试、维护升级等阶段的成本。回归测试作为软件生命周期的一个组成部分,在整个软件测试过程中占有很大的工作量比重,软件开发的各个阶段都会进行多次回归测试。我们在bug之后重新通过数据判断自己的bug修复是否成功实际上就是一种回归测试。

1.3 数据构造策略

我们在构造数据时有两种选择方法,第一种方法是进行黑箱测试,写随机的数据生成去比对;第二种是进行白箱测试,力求高的行覆盖率和分支覆盖率。此外构造极端数据的压力测试也是我们需要格外考虑的点。

2 单元架构设计

2.1 异常抛出类

实际上异常抛出类是最好实现的功能。虽然本单元作业有非常多的异常抛出类,但是异常抛出类的实现实际上都是一样的,传入的参数都是ID,都是打印异常触发次数,一个或两个ID异常出现次数。所以只要实现完单输入抛出和双输入抛出之后全部ctrl+C和ctrl+V就可以全部解决了。

2.2 MyPerson

MyPerson类是Person接口的实现,从JML规定的属性、JML规定的方法、自增的属性和方法三个方面进行分析。

2.2.1 JML规定的属性

首先分析JML以给出的要我们实现的属性:

int id
String name
int age
Person[] acquaintance
int[] value
Tag[] tags
int money
int socialValue
Message[] messages

然后分析其数据结构:
第一类为int或String类型,有id、name、age、money、socialValue。这些都是不需要过多考虑的,直接实现就行;
第二类为数组类型,这时候我们就需要仔细选择数据结构了,因为HashMap和数组的效率差异还是比较大的。考虑到各个方法的实现,我们发现只有messages是要求有序的,其他都可以是无序的,所以只有messages用ArrayList,其余均选择HashMap。
结果如下:

private int id;
private String name;
private int age;
private HashMap<Integer,MyPerson> acquaintance;
private HashMap<Integer,Integer> value;
private HashMap<Integer,MyTag> tags;
private int money;
private int socialValue;
private ArrayList<Message> messages;

2.2.2 JML规定的方法

我们发现方法实际上都是对JML规定的类的增删改查操作,绝大部分都可以直接按照规格朴素实现,但是有一个例外,在2.2.3中提到。

2.2.3 自定义的属性和方法

我自定义的属性有三个:

private int leaderId;
private int bestId;
private int bestValue;

leaderId是用于和并查集配套的,并配备了对应的增删改查方法,后面在MySet类中具体解释。
bestId和bestValue是我考虑到算法的时间复杂度自己采用的动态维护,这个主要在于getBestId()时调用,直接返回bestID,同时在增删改时进行维护。我主要是考虑到对qba这一函数的多次重复调用的情况,所以采用了此种算法。

2.3 MyTag

2.3.1 JML规定的属性

JML规定的属性只有两个:

int id
Person[] persons

我们阅读JML发现persons也是无序的,选择HashMap

private final int id;
private HashMap<Integer,Person> persons;

2.3.2 JML规定的方法

JML给出的方法同样是增删改查操作,绝大部分都可以朴素实现,但是有一个例外,在2.3.3中提到。

2.2.3 自定义的属性和方法

方法getValueSum()是本单元课程组给我们挖的一个坑,对于persons的value求和,实际上是一个o(n)方法,从直觉上来讲是不需要优化的。但是课程组对这个方法进行了多次不修改tag的调用,每次求和都占用CPU时间,所以我们也需要把这一方法改成动态维护的,要单独设计一个int sum进行维护。

2.4 MyMessages及其子类

这里面的属性和方法都非常直接,不涉及到什么算法,直接实现即可。

2.5 MyNetWork

JML规定的属性

Person[] persons
Message[] messages
int[] emojiIdList
int[] emojiHeatList

这里的数据结构比较有意思,persons和messages都是无序的,各自开一个HashMap即可。但是我们通过阅读JML发现,emojiIdList和emojiHeatList是一一对应的关系,所以我们不妨直接建立一个key为emojiId,value为emojiHeat的HashMap。最终结果如下:

private HashMap<Integer,MyPerson> persons;
private HashMap<Integer,MyMessage> messages;
private HashMap<Integer,Integer> emojiHeat;

JML规定的方法

时间复杂度为o(n)的方法直接朴素实现是一定没问题的,时间复杂度为o(n^2)的方法是可以部分保留的,时间复杂度更高的算法是一定要修改的。

自定义的方法

我们发现hw9涉及的都是图算法,朴素的便利实现都是超时的,所以需要用并查集来维护,其新增一个MySet类实现并查集相关内容。
此外qts是三层循环,时间复杂度高,要进行动态维护。新增private int qtsCount;

2.6 自定义MySet类

MySey类用ArrayList<HashMap<Integer,MyPerson>> sets来储存人与人之间的关系,联通的人放在同一个HashMap内,ArrayList的size就是qbs的值。
我采用的是路径压缩并查集的方法,一个联通块内的父亲均设为同一个,这样时间复杂度就会低很多,这也是MyPerson里的自定义属性。
在这个类中其实就只有三个方法:加人、合并、重建。
加人方法是在某人第一次生成时出现的,直接自己就是一个新的块。
合并方法是在新增关系时出现的,将新增关系的两个人的块合并为同一个块。
重建方法是在删除关系时出现的,将要删除关系的两个人所在的快重建并查集。

3 规格与实现分离

我们的JML只是用一种我们容易理解的方式来描述我们要做的工作,但是这并不意味着我们要严格按照JML来实现,我们应该选择时间复杂度低的算法来实现。比如说QTS函数,这个函数在JML里是用三层循环来描述的,这种方式会让我们很轻松的理解我们要实现的功能,但是这种三重循环作为一个方法显然是不合格的,我们实际上采用的也是动态维护的方式。

4 Junit相关

Junit进行检验时实际上应该检查JML的每一个描述,比如有pure的方法,我们要检测是否没有任何内容被修改;检查ensures是否全部实现等等。我个人认为我们进行的Junit测试应该是一种白盒测试,我们根据规格的描述来进行判断,要覆盖到规格中所描述的每一个分支和每一个结果,这样的测试方法行覆盖率和分支覆盖率是很高的,也是我们希望看到的。而随机生成数据进行黑盒测试的方法我个人认为这应当作为对白盒测试的一种补充,而不应该取代白盒测试的位置。

5 本单元学习体会

本单元我们学习了JML和Junit测试,对开发和测试有了一定的了解。在以后的编程实现过程中,我们应当严格按照规格编写,并进行充分的测试,测试包括但不限于临时单元测试和集成测试等等。

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

301

社区成员

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

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