BUAA_OO_Unit3博客总结

魏东霖-22371041 学生 2024-05-17 00:40:44

Unit3 博客总结

目录

  • Unit3 博客总结
  • 前言
  • 测试方面
  • 架构设计
  • 性能问题及其修复
  • 本单元学习体会

前言

本单元的学习内容主要是面向JML规格的程序实现。实现依赖于一张无向带权图的增删改查等,需要考虑各操作的时间复杂度问题。这一单元比较特殊的一点是只需要根据规格完成对应的实现即可,不需要从零开始设计架构。

初次接触JML语言时有点手足无措,形式化的语言阅读起来一方面很费劲,另一方面新增的Junit测试方法在此前虽有所接触,但是也还是略显生疏,写起来并不顺手,这是我对本单元的初步感受。但是规格的确把程序的行为规定清楚了,抛出的异常等有非常明确的条件,从某种角度上也算是减小了思维量。

最后是关于算法和数据结构的设计。看了前几届的作业,完整完成这三次作业以后我虽略感庆幸没有出现非常复杂的图论问题,但是在整个实现过程中还是略感费力,尤其是第二次作业queryTagValueSum的动态维护处理上出了不少问题,但是好在都在交作业前改过来了,所以总的来说还是很幸运的能顺利完成这三次迭代。

下面我将从测试、规格、性能等几个角度来分析整体分析下这三次作业。

测试方面

  1. 几个概念
  • 黑箱测试:指测试人员不考虑被测试软件的内部结构或实现细节,而是仅仅关注软件的输入和输出行为。比如Junit结果测试部分 只关注生成的数据和黑箱运行的结果,而不关心几个被测试函数的具体实现。

  • 白箱测试:指测试人员了解被测试软件的内部结构和实现细节,以此设计测试用例,并基于代码结构、逻辑等内部特性编写测试用例,以检查代码的各个部分是否正确执行。比如Junit测试中测试几个ensures保证的内容是否有更改,或者一个pure方法是否偷偷修改了什么内容。

  1. 数据构造策略:关于Test中的测试,考虑到课程组表示“不会需要过于刁钻的数据才可以测试出错误”,于是我使用的是纯随机的数据生成策略 + 测试多组的策略。

    • 在第一次作业中,我生成指定数量的MyPerson再加上随机的Relation(下面简称为边),在Test类中记录一下对应的加边、删边和改边的结果,再根据JML手动模拟一下运算最后再对比目标函数的结果,再逐个检查每一个ensures的内容就可以。
      public void randomlyOperations(int operationNumber) {
        Random random = new Random();
        ArrayList<Person> hasBeenAdded = new ArrayList<>();
        for (int i = 0; i < operationNumber; i++) {
            int randomOp = random.nextInt(2000);
            switch (randomOp % 3) {
                case 0:
                    // add one person
                    break;
                case 1:
                    // add relationship
                    break;
                case 2:
                    // modify relationship
                    break;
                default:
                    break;
            }
        }
      }
      
    • 第二次的数据构造原理基本同上。
    • 第三次作业中,由于被测试函数的规格中涉及Person类的内容较少,更不涉及修改其中的内容,所以我选择从头到尾只用两个Person,尽量避免生成数据中出现问题。每次生成一个Message,有50%的概率sendMessage(),这样保证了最终的集合中既有没有发送的消息,同时有一定数量的消息被发送,可以更加全面的测试程序的功能。
      public void dataGenerate(int testData,
                             HashMap<Integer, Message> messagesRecord,
                             HashMap<Integer, Integer> emojiMapRecord) {
        Person p1 = new MyPerson(1, "1", 1);
        Person p2 = new MyPerson(2, "2", 2);
        try {
            network.addPerson(p1);
            network.addPerson(p2);
            network.addRelation(1, 2, 100);
        } catch (Exception ignore) {}
        HashSet<Integer> existId = new HashSet<>();
        HashSet<Integer> existEmojiId = new HashSet<>();
        for (int i = 0; i < testData; i++) {
            Random r = new Random();
            switch (r.nextInt(500) % 4) {
                case 0: { // generate Message
                    break;
                }
                case 1: { // generate RedEnvelopMessage
                    break;
                }
                case 2: { // generate NoticeMessage
                    break;
                }
                case 3: { // generate EmojiMessage
                    break;
                }
                default:
                    break;
            }
        }
      }
      
  2. 几个重要的测试概念:

    • 单元测试(Unit Testing):针对软件中最小的可测试单元进行的测试,通常是一个函数、方法或类的功能,旨在验证单元是否按照预期工作。

    • 功能测试(Functional Testing):功能测试是验证软件的功能是否按照规格、用户需求或设计规范正常工作的测试。

    • 集成测试(Integration Testing):集成测试是将单个单元或组件集成到一个完整的系统中,测试它们的交互和协作,旨在验证组件之间的接口和交互是否正确,确保系统各部分之间的集成是有效的。

    • 压力测试(Stress Testing):压力测试是测试软件在高负载或极端条件下的性能和稳定性。

    • 回归测试(Regression Testing):回归测试是在对软件进行修改或更新后,重新运行既有的测试用例,以确保修改不会影响系统原有的功能和行为。

    从网上找到了这些概念的简单定义,谈一谈这次用到的几个点:首先是单元测试,在Junit中编写的就是单元测试,对 一个 函数进行测试,验证它是否执行正确,而是否按照规格的要求编写则是功能测试的范畴。压力测试是本次自测中的一个重点,通过模拟最大指令数的操作来测试程序的性能(当然最后发现还是测试不完全的,比如全是modify relation指令却不查询这样的情况就没考虑到)。

架构设计

  1. 使用了路径压缩的并查集来提供高效的连通性查询,但是遇到的问题是并查集不支持低复杂度删边,所以选择了在出现修改边的权或删边时重建并查集(最多O(n)),同时使用一个LazyTag来优化只改不查的情况。查询三元环的方法使用了动态维护的思想,每一次加边或者删边时处理一下该边两个顶点的所有共同邻居即可。

  2. 第二次作业中的一个关键点在于怎么高效的维护一个Tag中的ValueSum,我采用的是在每次删边、加边、修改边权时动态的修改这个值,这样可以做到查询时O(1)复杂度。最短路径比较好处理,因为是无向图同时不考虑带权的最短路,直接使用bfs就可以解决。

  3. 第三次作业中直接按照JML的规格书写即可,没有需要特别需要主要复杂度的问题。

性能问题及其修复

正如上面提到的,在第一次作业中出现了这样的情况:只有mr指令却没有查询指令。可能是重建并查集的常数比较大导致最后的强测中超时,对应的修改方法是只在查询时才重建,同时设置懒标记,在没有修改时不会重建并查集。在其他的地方上没有出现性能问题。

与此同时,我的另一个感受是规格只规定了程序做什么,却没有说明怎么做,所以实现的功能与具体的实现实际上是分离的。算法和数据结构的选择实际上还是编写程序的人来根据需要选择的。

本单元学习体会

相比前两个单元,可以感受到这个单元的思维量还是有所下降的。根据JML规格写代码确实是一次新鲜的体验,尽管可能这次体验的感受并不算好,可能主要原因是JML的阅读很困难,没有高亮、没有格式化,借用了Javadoc的模子,但是在各个IDE中的支持并不好。

但是仔细想想,又好像学到了不少东西,比如规格的撰写和定义等,也算是初步了解和掌握了JML这种工具。另外的,在算法与数据结构的选择上也有了不少经验,算是蛮有收获吧。

希望第四单元能够顺利。

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

301

社区成员

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

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