BUAA-OO-Unit 3 规格化设计

BUAA-OO-Unit 3 规格化设计

第三单元作业围绕JML 规格理解与代码实现,维护一个社交网络,并对其中的用户和他们之间的关系、标签、消息等进行管理;同时为某些方法编写 JUnit 单元测试

分析本单元测试过程

黑箱测试与白箱测试

  1. 黑箱测试(Black-box Testing)

    测试者不需要了解内部实现细节和代码结构,只需要关注输入和输出。它以功能需求为基础进行测试,测试用例设计主要依据软件的功能规格说明书,主要用于验证软件的功能是否满足预期。

    比如我们这次编写的JUnit测试即为黑箱测试。

    黑箱测试的优点在于:可以有效地捕获未实现的功能;测试用例基于用户需求和使用场景,容易发现功能缺陷。
    缺点在于:可能无法覆盖所有代码路径;不容易发现隐藏的逻辑错误。

  2. 白箱测试(White-box Testing)

    测试者则需要了解内部实现细节、代码结构和算法。它以程序内部逻辑为基础进行测试,测试用例设计基于源代码和内部实现,主要用于验证代码的逻辑路径和内部状态。

    比如我们在互测时通过检查对方的代码而实现“精准打击”即为白箱测试。

    白箱测试的优点在于:能够覆盖所有代码路径,发现隐藏的逻辑错误;可以进行细粒度的测试,确保代码的各个部分都正常工作。
    缺点在于:需要了解代码内部实现,测试用例设计复杂;
    可能忽略了功能层面的错误。

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

  1. 单元测试(Unit Testing)

    单元测试是对软件中最小可测试单元(通常是一个方法或类)进行验证。它的测试范围小,针对具体功能,通常由开发者编写和执行,使用Mock和Stub来隔离单元。

    单元测试的优点在于可以快速定位问题,提高代码质量。

    缺点在于不能发现模块之间的集成问题。

  2. 功能测试(Functional Testing)

    功能测试是验证软件功能是否按照需求文档正确工作的一种测试。它关注用户需求和功能规格,属于黑箱测试,测试用例基于功能需求设计。

  3. 集成测试(Integration Testing)

    集成测试是对软件各个模块进行集成后的测试,验证模块之间的交互。

    集成测试的优点在于可以发现模块之间的接口和交互问题。

    缺点在于比单元测试复杂,覆盖面不如系统测试全面。

  4. 压力测试(Stress Testing)

    压力测试是通过增加负载或数据量来测试系统的性能和稳定性。它评估系统在极端条件下的表现,用于识别系统瓶颈和性能极限。

    比如我们的强测往往会引入压力测试来检测代码的性能。

  5. 回归测试(Regression Testing)

    回归测试是在对软件进行修改后,重新执行之前的测试,确保修改没有引入新的错误。

    我们在每一单元作业完成后除了检查是否实现新增要求,还应复查之前的操作能否正常执行。

数据构造策略

  1. 构造边界值

    测试边界值和临界值,发现边界条件下的问题,比如idint范围内,那么可以测试-2147483648和2147483647;比如给自己某一tag内的person发红包时该tag.size为0;比如addPersonToTag时该tag.size已经大于1111的情况。

  2. 随机测试

    生成随机数据进行测试,利用评测机,可以发现意外输入导致的问题。

    注意要尽量保证随机生成的数据可以包括各种情况,比如有些person的关系非常复杂,而有些person是孤家寡人。

  3. 压力测试

    在某一方法的性能比较差的时候,可以一直调用这个方法从而出现tle。

架构设计

UML类图

以下为第11次作业的uml类图:

在这里插入图片描述

本单元作业主要实现课程组提供的接口,主要包含:

  • Person类:代表图中的节点,包含个人ID、连接的其他Person对象(邻接表表示法)等。
  • Network类:代表图的整体结构,包含所有Person对象的集合,以及添加、移除、查询等操作。
  • Message类及其子类:用于模拟不同类型的消息传递,包含不同的操作如红包消息等。
  • Tag类:给Person的相关联节点进行标签管理,包含Tag的ID,管理的Person等。
  • 异常类:如PersonIdNotFoundExceptionPathNotFoundException,用于处理特定错误情况。

图模型的构建与维护

图模型在构建时,使用邻接表存储图。邻接表是一种常用的数据结构,用于表示稀疏图(相对于邻接矩阵)。在邻接表中,每个节点存储一个与之直接相连的其他节点的列表。这种方法节省空间,特别是当图中边的数量远小于节点数的平方时。

图模型通过动态添加和删除节点和边。添加节点时通过在Network类的Person列表中添加新的对象;添加边则通过在两个Person对象的acquaintance列表中互相添加对方;删除节点和边则相应地从NetworkPerson对象的列表中移除。

图模型在维护时,进行了数据完整性检查,在添加节点时,确保每个节点的ID是唯一的;在添加边或查询时,确保涉及的节点存在于图中。并使用自定义异常类处理特定错误情况,确保错误能够及时被捕获和处理。同时进行了性能的优化,使用BFS进行最短路径查找,确保在无权图中找到最短路径;使用HashMap和HashSet等高效数据结构,确保常见操作(如查找、添加、删除)的时间复杂度为O(1)。

性能bug分析

性能问题及其修复情况

本单元作业的性能问题主要出现在第9次作业中,由于没有考虑到性能而选择直译了JML,代码中存在for循环的嵌套,导致大量tle。

修复时主要通过在类中维护属性,比如Network中的tripleSum,在每次执行会改变其值的操作时(比如增删边),则针对性的进行修改,从而避免了多重循环。第10次作业在写的时候也进行了类似的操作。

通过研讨课了解到还可以做一个 cache, 当且仅当脏时重算。

最后,在第11次作业时,将原本的list改为使用HashMap和HashSet等高效数据结构,确保常见操作(如查找、添加、删除)的时间复杂度为O(1)。

规格与实现的分离

规格和实现的分离是软件工程中的一个重要概念,尤其在设计复杂系统时具有重要意义。

  • 提高系统设计的清晰度:规格提供了一个系统的高层次视图,使设计者和开发者能够清晰地理解系统的需求和目标,而不被实现细节干扰。

  • 增强灵活性和可维护性:分离规格和实现使得系统更容易进行修改和扩展。只要实现满足规格,可以随时替换实现细节以提高性能或增加新功能,而不会影响系统的整体设计。

  • 有助于验证和测试:规格为测试和验证提供了明确的标准。可以根据规格设计测试用例,确保实现满足预期的行为。这也有助于发现和修复bug。

  • 促进团队协作:在大团队中,规格和实现的分离使得不同角色(如需求分析师、设计师、开发者、测试人员)可以更好地协同工作。规格提供了共同的语言和理解基础。

JUnit 测试

规格信息明确描述了方法的行为、输入、输出和预期效果。通过阅读和理解规格信息,我们可以清楚地知道方法应该完成什么任务,应该返回什么结果,以及在什么条件下应该抛出哪些异常。可以逐行验证代码是否满足了规格要求,包括/@ pure @/等。

在实现JUnit测试时,要尽可能根据规格信息设计全面且有效的测试用例,包括正常情况测试、边界条件测试和异常情况测试;从而确保方法实现满足功能需求,验证实现的完整性和正确性,促进了规格与实现的分离。

学习体会

通过本单元的作业,让我体会到了规格化设计、以及规格与实现分离的重要性,它可以确保在实现具体代码时,始终有清晰的目标和标准,提升代码的正确性和可维护性。

同时也让我认识到了实现规格时应尽可能地提升性能,从而提高系统瓶颈和性能极限。

呜呜呜如果中测强度向强测再多靠近一些就好了😭

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值