BUAA_OO第三单元总结

马宇航-19373012 2024-05-19 12:53:43

BUAA_OO第三单元总结

测试过程

黑箱测试和白箱测试

黑箱测试也称功能测试、数据驱动测试或基于规格说明的测试。测试者只知道程序的输入、输出和系统的功能,这是从使用者的角度针对软件的接口、功能及外部结构进行的测试,不考虑程序内部实现逻辑。该测试方法对于测试者的优点是测试比较简单,无需花费时间了解程序内部的代码和具体实现方法。但同时也存在着覆盖率相对来说较低、自动化测试复用性低的缺点。

白箱测试也称结构测试、逻辑驱动测试或基于程序本身的测试,测试程序内部结构或运行。在白箱测试时,从程序设计语言的角度来设计测试样例。测试者输入数据并验证数据在程序中的流动路径,并确定适当的输出。该测试方法的优点是能够帮助软件测试人员增大代码的覆盖率、提高代码质量,发现代码中隐藏的问题。但同时也存在着测试开销大等问题。

对于面向对象课程的作业测试,中测和强测属于黑箱测试。而对于我们编写JUnit测试以及在阅读其他同学的代码后进行的互测则属于白箱测试。

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

单元测试:指的是针对程序模块来进行正确性检验的测试工作。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。在作业中对单独某一方法的测试以及JUnit测试都可看作是单元测试。

功能测试:一般指对软件的功能进行测试,判断其功能是否符合需求,通常采用黑盒测试的方法。在作业中对具体某一查询功能编写数据进行测试的行为可看作功能测试。

集成测试:又称整合测试或组装测试,即对程序模块采用一次性或增值方式组装起来,对系统的接口进行正确性检验的测试工作。整合测试一般在单元测试之后、系统测试之前进行。

压力测试:压力测试包括程序在极端情况下的稳定性测试以及在大量数据情况下的性能测试,压力测试的目标是在极其沉重的负载条件下测量软件的健壮性和错误处理能力,并确保软件在危急情况下不会崩溃。在本单元作业中,强测中存在着压力测试,主要测试对象为queryCoupleSum、queryBlockSum、queryTagValueSum、queryTagAgeSum、queryTagAgeVar、queryBestAcquaintance、queryShortestPath等方法。

回归测试:当发现并修改缺陷后,或在软件中添加新的功能后,重新测试。用来检查被发现的缺陷是否被改正,并且所做的修改没有引发新的问题。回归测试可以通过人工重新执行测试用例,也可以使用自动化的工具来进行。在课程作业中,我们在进行bug修复后重新交上去进行测试的行为可以看作是进行回归测试。

数据构造策略

在本单元作业中,对于数据构造采用的是随机数据与极端数据相结合的方式来进行数据构造。

对于随机数据,使用自动化测试机进行随机数据生成,注意确保生成数据和指令的合理性和逻辑性,同时保证少量数据和大量数据相结合,覆盖较多的范围。

而对于随机数据难以覆盖的极端数据,采取了手动构建的方法。如压力测试数据的构建,在一次测试中指令和人数达到上限且全是对某一查询方法的测试数据,来确保不会出现TLE的错误。同时针对一些特殊数据也需要手动构建,如全连通图和全不连通图等数据,来保证不会因特殊情况而产生错误。

架构设计

在本单元作业中,JML已经给出了架构设计,我们要做的只需根据JML规格来进行具体的实现。接下来将具体阐述每一次作业中对JML所给规格的实现方法,以及出现的性能问题和解决方案。

第一次作业

实现方案

规格与设计:JML中Network类中的persons数组中的每一个人都有独立的id,同时为了方便后续对person的查询和删除操作,我采用了HashMap来存储;同时Person类中acquaintance数组也是每个独立id对应一个值,因此也采用HashMap存储该数组。增加了Couter、Tri和Tuple三个类,前者用于异常的技术,后两者用于计算TripleSum。

算法选择:本次作业中MyNetwork类中的isCircle、queryBlockSum和queryTripleSum是耗时较多的方法,因此对于这三种方法的实现的算法选择较为重要。根据JML中给出的规格描述,可以知道这三个方法想要实现的功能分别是判断图中两点是否联通、查询有几个不同的好友链以及查询有几对三角关系,接下来就是具体算法的实现。

isCircle:采用的是建立并查集加动态维护的方法,在addRelation的时候更新并查集,在modifyRelation的时候判断是否删除了边,如果删除了边就将脏位置为true。在查询isCircle的时候如果脏位为true则证明产生了删边操作,进行并查集的重建,否则直接进行查询即可。在并查集查询和合并更新中采用了路径压缩和按秩合并的方法来进行优化,提高了查询效率。

queryBlockSum:采用动态维护的方法,设置blockSum值,在addPerson操作后即图中加入新的点后该值加1,在addRelation操作时判断两点之前是否连通,如果不连通则该值减1。查询时如果脏位为true则重建并查集重新计算blockSum值,否则直接返回blockSum值。

queryTripleSum:采用动态维护的方法,设置tripleSum值,在addRelation即图中加边时和modifyRelation删边进行判断是否有新增或减少的三角关系并更新tripleSum值,并在查询时直接返回ripleSum值。

产生bug及修复策略

在queryTripleSum实现中需要在addRelation和modifyRelation方法中进行动态维护即tripleSum值计算,因此增减边操作数量较多且不进行三角关系数量查询时会产生运行时间的浪费,从而出现了CTLE问题。

修复策略:增加了Tri和Tuple两个类,动态维护改成在addRelation和modifyRelation方法中不直接计算三角关系的增减,而是将增减边的操作存储在Tuple中。在queryTripleSum命令产生时根据Tuple中的删减边操作的顺序进行tripleSum值的更新。

第二次作业

实现方案

规格与设计:Person类中新增的tags数组也是每个独立id对应一个tag,因此也采用HashMap存储该数组。对于新增的Tag类中的persons数组与NetWork类中的相同,因此采用一样的实现方法。

算法选择:新增的queryBestAcquaintance、queryCoupleSum和queryShortestPath是耗时较多的方法,因此主要阐述这些方法中的算法选择。

queryBestAcquaintance:查询person中最大的acquaintance值所对应的id,记为bestId。采用的是动态维护的方法,在Person类中每次新增acquaintance时判断是否大于bestId所对应的acquaintance值,如果是则更新bestId。同时在对bestId进行modifyRelation将其对应的值减少时将脏位置true,并在查询时如果脏位为true则遍历更新bestId,否则直接返回bestId。

queryCoupleSum:查询Network中互为bestId的person对数。采用的方法是按照JML所给的规格进行遍历,复杂度为O(n),n为persons数组的长度,没进行优化在测试中也没有TLE。

queryShortestPath:查询link图中两点的最短途径,采用的是双向bfs方法,实现较为容易。

针对新增的Tag类中的AgeMean、AgeVar和ValueSum等值采用的是脏位标记的方法,在addPerson和delPerson时将脏位置true。查询这些值时如果脏位为true则重新计算值,否则直接返回对应的值。

产生bug及修复策略

在queryTagValueSum的压力测试中产生了CTLE问题。

修复策略:更改了ValueSum的计算策略,之前是每次重新计算时都遍历计算,复杂度较高。更改策略为相对于更新前的值判断改变了那些person,只对改变的部分进行增减计算。

第三次作业

实现方案

第三次作业新加入了Message,Message有socialValue,会改变发信人、收信人的socialValue;在Message的基础增删、发送之外,network新增了queryPopularity、clearNoticeMessage、deleteColdEmoji、storeEmojiId。

规格与设计:Network类中新增的messages数组是每个独立id对应一个tag,因此采用HashMap存储该数组;而对于emojiId和emojiHeat,由于是一一对应的关系且emojiId唯一,因此将这两个数组合并为一个HashMap来存储。

本次作业没有太多需要进行算法选择和优化的地方,有关消息的部分大多数方法都是增删消息和直接查询,按照JML所给规格直接实现即可。

产生bug及修复策略

未产生bug。

规格与实现的分离

在本单元针对JML的学习中,其规格的本质是提出需求者将自然语言转化为形式语言描述;我们实现的过程中并不直接对照JML进行编程,而是先理解整个模块的JML,再进行模块的编写,将自然语言转化为代码。这样能有效防止因自然语言的语义不清晰的问题,在实现中也可以增强系统的灵活性、可维护性和可扩展性。

而在本单元作业规格实现的过程中,我通过了以下几种方法来进行了优化实现:首先是采用合适的数据结构,如HashMap等;其次采用合适的算法来减少实现方法的时间复杂度;最后则是采用动态维护、延迟维护等方法进行进一步的优化实现。这几种方法帮助我更好的完成了规格与实现的分离,在满足规格的同时提升了程序的性能。

JUnit测试

JUnit测试的重点则是数据的构建和对结果正确性的验证。

数据构建部分在之前部分已经有所提及 ,需要注意的是JUnit测试的数据因为不能太多,因此要尽可能选择覆盖面较广的数据。同时针对测试的方法只需准备方法所需的数据,对类中存在但所测试的方法所不涉及的数据可以忽略。

对于正确性验证,则需要根据JML所给出的规格去逐条判断,完全按照JML进行,根据规格信息中的assignable,ensures的要求来对比,覆盖到所有ensures的要求。

学习体会

在本单元学习中首先体会到的就是规格化编程的特点,JML能够帮助我们更清晰的理解程序实现的功能以及行为,让开发者能够有更好的理解和维护代码,从而设计出更可靠的程序。

同时,本单元着重强调了规格与实现分离的重要性。JML规格只是告诉我们要做什么,却没告诉我们具体要怎么做,因此我们要去思考怎样在实现功能的同时,提高程序的性能并保证程序的灵活性、可扩展性等。这让我们更多的去思考细节,不断优化,从而完成高质量的代码。结合后续的测试也能让我们形成良好的开发和测试习惯,帮助我们在今后的代码开发中更加规范和高效。

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

301

社区成员

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

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