BUAA_OO_第三单元总结

BUAA OO 第三单元总结

一、测试过程分析

1.1 黑箱测试 与 白箱测试

  • 黑箱测试
    • 黑箱测试是一种功能测试方法,测试人员不需要了解软件的内部结构或工作原理。
    • 测试人员只关注输入和输出,通过给定的输入来验证软件的输出是否符合预期的规格和功能。
    • 黑箱测试关注于软件的功能是否按照需求规格说明书或者用户需求进行操作和输出。
    • 测试人员通常会根据软件的规格说明书、需求文档或者用户文档来设计测试用例,从而验证软件的功能是否符合需求。
  • 白箱测试
    • 白箱测试是一种结构测试方法,测试人员需要了解软件的内部结构和实现细节。
    • 测试人员通过检查软件的代码逻辑路径控制流程等内部结构来设计测试用例,以验证软件的内部逻辑是否正确。
    • 白箱测试通常关注于代码覆盖率、路径覆盖率、条件覆盖率等指标,以确保测试覆盖了软件的各个逻辑路径和条件。
    • 白箱测试也被称为结构测试或逻辑驱动测试,因为测试用例的设计是基于软件的内部逻辑结构。

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

  • 单元测试
    • 单元测试是针对软件中最小的可测试单元进行的测试,通常是函数、方法或类
    • 单元测试旨在验证每个单元的功能是否按预期工作。
    • 开发人员通常编写单元测试来确保其代码的各个部分都能正确地执行其预期功能,同时也有助于提高代码的可维护性和可重用性。
  • 功能测试
    • 功能测试是对软件的功能进行测试,以确保软件的功能符合用户需求和规格说明书。
    • 功能测试通常是黑箱测试的一部分,测试人员根据功能需求设计测试用例,验证软件的功能是否按照预期工作。
  • 集成测试
    • 集成测试是将软件的各个模块或组件结合在一起,测试它们之间的交互和集成是否正常。
    • 集成测试旨在验证各个模块之间的接口是否正确、数据传递是否准确,并确保整个系统的功能完整性
  • 压力测试
    • 压力测试是对软件在极端条件下的性能进行测试,例如高并发、大数据量或长时间运行等。
    • 压力测试旨在评估软件在压力下的稳定性和可靠性,以及其性能指标如响应时间、吞吐量等是否满足要求。
  • 回归测试
    • 回归测试是在软件进行修改、更新或添加新功能后进行的测试,以确保修改后的软件仍然能够正常运行,并且未引入新的错误或破坏现有功能
    • 回归测试通常是自动化的,并且涉及重新运行之前的测试用例,以验证软件的行为是否与之前版本一致。

1.3 数据构造策略

  • 使用数据生成器进行随机生成:
    • 首先对network进行初始化,包括
      • 第一次作业:
        • 随机生成一定数量的person,并在部分person间建立关系
        • 使用load_network指令(可通过改变value的生成策略控制图的稠密程度)
      • 第二次作业:
        • 在第一次作业的基础上,为部分person添加tag,并向tag中添加一定数量的person
        • 使用addTagaddToTag指令实现
      • 第三次作业
        • 在前两次作业的基础上,向network中存入一些emojiId,然后向network中添加一定数量的message,并发送其中的一部分
        • 使用storEmojiIdaddMessagesendMessage指令,其中addMessage包括 红包、表情、通知与普通消息 四种类型与 person2personperson2tag两种类型
    • 之后便是随机生成一定数量的指令
      • 大部分的id均是在一定范围内随机生成的,可覆盖id不存在id相同等异常情况
  • 效果分析:
    • 由于数据生成的随机性,加上自动化测试的便利性,在大批量数据的测试下,可以很快发现代码中不符合jml规范的bug
  • 可改进之处:
    • 在随机生成指令时,未考虑部分指令间的关联关系,而是采用纯随机的方式,可能导致无效数据过多
      • 考虑通过指令组的形式来更好的生成数据
    • 生成指令时可以重点关注本次作业的指令,以便快速发
    • 现迭代所产生的bug
      • 上调新增指令比例
    • 由于未考虑部分极端情况,如大量的query操作,导致无法测出程序的性能问题
      • 考虑更深一步的手动构造,来测试可能出现的超时问题,进而发现设计逻辑上的性能问题

二、架构设计

  • 第一次作业

    • 示意图

      在这里插入图片描述

    • 本次作业仅涉及networkpersons的维护

    • network中新增属性blockstripleCount用于维护连通块与三元环数目(此处添加person2Block(Map)实现id->block的快速访问)

    • 采用连通块block将相互连通的person放入同一个block中,以便快速得到person间的连通关系与block的数量

    • 维护:

      • addPerson:新建block
      • addRelationblock合并(person1person2不属于同一block)、维护tripleCount
      • modifyRelationblock分离(removeperson1person2不在同一block)、维护tripleCount
        • 由于无法直接判断删边后是否在同一块,需考虑此处判断条件
        • 思路1:无论是否在同一块,均根据remove前该blockpersons,新建block,若一次新建后有剩余person,则再进行一次新建操作
          • 不足:稠密图删边后在同一块中的可能性较大,重复建块可能会增加时间开销
        • 思路2:先判断二者是否在同一块(通过DSTBST或其他算法),然后决定是否新建block
          • 不足:稀疏图删边后在同一块的可能性较小,重复判断可能会导致时间的浪费
        • 折衷:可根据network的稀疏程度(取决于人数和边数)决定是否要进行判断
  • 第二次作业

    • 示意图

      在这里插入图片描述

    • 本次作业新增对于persontags的维护

    • person中新增属性bestIdmaxValue用于对bestAcquaintance进行维护,新增belongToTags(Set)用于实现person->所属tag的快速访问,Tag中新增属性ageSumage2SumvalueSumvalueSumModified实现对ageSumageVar的快速计算以及对于valueSum的维护(age2Sum可用于快速计算方差

    • addToTag:维护belongToTagsageSumsetValueSumModified

    • delFromTag:维护belongToTagsageSumsetValueSumModified

    • addRelation:维护所属共同tagsvalueSumModified,维护bestIdmaxValue

    • modifyRelation:减边时 维护所属共同tagsvalueSumModified,维护bestIdmaxValue

      • 虽然此处进行维护,但是重新计算时仍需遍历所有acquaintance,可通过优先队列SortedMapacquaintancevalue进行维护
    • 最短路径可使用dijkstra算法解决

  • 第三次作业

    • 示意图

      在这里插入图片描述

    • 本次作业新增对于messages的维护

    • 本次作业新增对于personnetworkmessages的维护以及networkemojiList(id->popularity)的维护

    • Message类相关示意图

      在这里插入图片描述

    • 使用数组存储person内部messages(保证收到message顺序不变)
      使用Map存储networkmessagesemojiList(便于快速访问)

    • 注意避免messageIdemojiId的混淆,以及不同种类message的不同处理方式和类型转换

  • 异常类实现:

    • 构造内部static计数器,用于对该类型异常的计数

    • 使用Map保存不同id触发该异常的次数

    • id需根据类名辅助理解

    • 示例代码如下:

    • public class MyPersonIdNotFoundException extends PersonIdNotFoundException {
          private int id;
          private static int count = 0;
          private static HashMap<Integer, Integer> id2exceptionTimes = new HashMap<>();
      
          public MyPersonIdNotFoundException(int id) {
              this.id = id;
              count++;
              int time = id2exceptionTimes.getOrDefault(id, 0) + 1;
              id2exceptionTimes.put(id, time);
          }
      
          @Override
          public void print() {
              System.out.println("pinf-" + count + ", " + this.id + "-" + id2exceptionTimes.get(id));
          }
      }
      
  • 维护思路:

    • 对所有query方法对应的数据均进行维护!
    • 直接维护:如ageSum,在加减person时直接得出结果
    • 懒惰优化:对相应数据设置脏位,当执行query操作时,再进行计算(适用于计算时间复杂度在O(n2)及以上不便于直接维护的数据如tagValueSum

三、bug与性能问题分析

  • 由于实现严格按照JML约束进行,bug较少且均可通过生成数据快速测出(多为变量名或方法名错误)
  • 性能问题
    • 第一次作业:
      • 最初blocks使用**ArrayList**存储,无法实现person->block的快速访问,添加person2Block(Map)实现id->block的快速访问
      • 当删边时,未判断删边后是否在同一块中而直接新建block,导致modifyRelation用时过长,在压力测试下会导致超时
    • 第二次作业:
      • 对于bestId的维护,需在加减边时进行,可通过脏位记录变化,直到访问时根据脏位判断是否需要更新
      • 对于tagValueSum的维护,attdft时记录person所属tag,若加减边时,对二者共同tag内的valueSum进行维护,同时valueSum的计算可由n2降至n2/2。
    • 第三次作业:
      • 需考虑发红包时tagSize为0的情况
  • 对于规格与实现分离的理解:
    • 规格只是对于类中属性与方法的功能性定义,在具体实现中若完全按照规格进行实现,则可能会出现较大的性能问题,我们需要在规格约束的基础上,寻求更好的方式与算法对接口中的方法进行实现,如 利用Map代替数组实现对于数组中元素的快速访问、利用优化后的算法代替规格中的二重或三重循环 以及 通过对结果进行缓存以避免重复计算等问题。

四、Junit测试

  • 注意:Junit测试时assert应使用Assert中的方法(如assertTrue…)而不能直接用assert进行判断!

  • 本单元Junit测试主要利用JML规格信息来设计(qts、qcs、dce),包括以下几个方面

    • 方法规格:
      • 前置条件:requires 分类讨论(作业不含)
        • 正常行为
          • 使用try catch判断是否出现异常
        • 异常行为
          • 通过assertThrows方法判断抛出的异常类型
      • 后置条件:ensures 根据规格约束确定方法调用后满足所有后置条件约束
      • 副作用:assignable 确保只改变副作用范围内的对象状态
      • pure 方法:确保对象的状态未发生变化(使用strictEquals方法)
    • 类型规格:
      • 不变式:invariant 确保不变式恒成立
    • 除此之外,\not_assigned(对象)状态保证不发生改变
  • \old对象进行克隆以防访问同一个数组的引用而导致判断出错

  • 使用ParametersprepareData()实现自动化 批处理

    • 参考实验代码,自定义generate相关逻辑以初始化network(可参考 数据构造策略 部分)

    • 自定义测试组数进行测试,随机数据保证较高覆盖率(注意考虑稀疏图与稠密图)

    • 示例代码如下:

    • public MyNetworkTest(Network network) {
              MyNetworkTest.network = network;
          }
      
      @Parameterized.Parameters
          public static Collection prepareData() {
              int testNum = 100; //测试次数
              Object[][] object = new Object[testNum][];
              for (int i = 0; i < testNum; i++) {
                  Network network = generateNetwork();
                  object[i] = new Object[]{network};
              }
              return Arrays.asList(object);
          }
      

五、学习体会

  • 通过本单元对于JML的学习,对于规格和契约式编程有了更深的理解;通过对于JML代码的阅读,对方法所要实现的功能有了初步了解,然后对于复杂的方法设计算法进行具体实现。

  • 然而本单元的前两次强测结果却不尽如人意,按照规格实现会存在较大的超时问题,虽然进行了一定程度的优化,但是仍然存在可优化之处,导致面对大量数据时运行时间过长,究其原因在于

    1:在实现时,未充分考虑算法的优化,导致在进行大量重复操作时,运行超时

    2:在测试时,只考虑了随机数据,而未对特殊情况进行考虑,未构造出较强数据以至于无法发现自己的性能问题

    这也提醒我对于实现算法以及维护方式的设计等思考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值