BUAA OO 2024第三单元总结

BUAA 第三单元JML总结

第一次作业

在这里插入图片描述

第一次作业主题内容是初步建立人物联系网络MyNetWork,初始化人物属性和方法MyPerson,异常类的编写统计输出,和查询构建的图中两点连通和权值等情况。
首先讨论一下异常类计数和输出的写法:
1.单Id触发异常:static修饰总次数和每种Id触发异常的统计量

public class MyPersonIdNotFoundException extends PersonIdNotFoundException {
    private int id;
    private static int allTime = 0;//总次数
    private static HashMap<Integer,Integer> personTime = new HashMap<>();//记录每种Id对应的次数

    public MyPersonIdNotFoundException(int id) {
        this.id = id;
        allTime++;
        if (personTime.containsKey(id))
        {
            int time = personTime.get(id) + 1;
            personTime.remove(id);
            personTime.put(id,time);
        } //更新次数
        else {
            int time = 1;
            personTime.put(id,time);
        } //新Id,记录到容器中
    }

    @Override
    public void print() {
        System.out.println("pinf-" + allTime + ", " + id + "-" + personTime.get(id));
    }
}

双Id触发异常类似,需判断Id1,Id2的大小决定输出顺序。

性能问题方面,为了提高MyNetWork在查询和获得Person的效率,采用Hashmap存储结点,以人物Id为key,MyPerson为value,其他类里的查询与获取也尽量使用Hashmap。此外,在任何query的方法中可以接受O(n)的时间复杂度,即对于两次及以上循环的暴力遍历很可能会在强测和互测时被打爆。

调查MyNetWork中两个节点的连通性,根据往届博客的建议和实现方式,采用并查集是一种高效的方式。
并查集的思想是在同一个集合的元素,选取其中一个元素作为该集合所有元素的代表元。对于一个连通块而言,同样可以选取一个节点作为代表,其他所有元素的根节点都是它。在判断是否连通的时候只需判断两个节点的根节点是否相同即可。

	private HashMap<Integer, Integer> pre;//每个元素的根节点
    private HashMap<Integer, Integer> rank;//树的高度
    
	public int find(int id) {
        int rep = id; //代表元
        while (rep != pre.get(rep)) {
            rep = pre.get(rep);
        }

        int now = id;
        while (now != rep) {
            int fa = pre.get(now);
            pre.put(now, rep);
            now = fa;
        } //修改一路上的节点的代表元
        return rep;
    } //路径压缩,并返回根节点
    public int merge(int id1, int id2) {
        int fa1 = find(id1);
        int fa2 = find(id2);
        if (fa1 == fa2) {
            return -1;
        }
        int rank1 = rank.get(fa1);
        int rank2 = rank.get(fa2);
        if (rank1 < rank2) {
            pre.put(fa1, fa2);
        } else {
            if (rank1 == rank2) {
                rank.put(fa1, rank1 + 1);
            }
            pre.put(fa2, fa1);
        }
        return 0;
    } //将两个不同连通块相接

但是ModifyRelation中删除边会导致并查集需要修改,因此此时需要dfs一下把该连通块所有的根节点修改一下。
BlockSum的数量还是很容易动态维护的;对于TripleSum的动态维护,可以在每次addRelation和ModifyRelation删边的时候计数两个节点Person共同的熟人即可。

第二次作业

在这里插入图片描述
第二单元的主要任务是增加Tag,每个Person里可以有多个Tag,一个Tag里可以有多个Person。在MyNetWork里实现addTag(),addPersonToTag()等函数,这些都较为简单。
同时还有关于Tag相关量的统计维护:年龄平均数和方差请关注JML具体算法,否则会因为int取整导致算出来的数据不正确。另外是对Tag的valueSum的动态维护,在每次往Tag里addPerson和delPerson的时候都会导致valueSum的变化;除此之外ModifyRelation中改变两人连线的value也会对valueSum产生影响,因此需要一个单独的processValueSum(int valueDiff)来对value前后的差值进行操作

public void processValueSum(int valueDiff) {
        valueSum += valueDiff * 2;
}

第三次作业

在这里插入图片描述

第三次作业增加了MyMessage类,包括红包类消息、通知类消息和表情消息。type为0是单发消息,type为1是群发消息;单发消息和群发消息在sendmessage时都会改变Person的socialValue值。同时,每种表情都有自己的emojiId,每种消息都有自己唯一的Id,因此我选择用两个Hashmap分别存储Id和emojiId的映射关系以及EmojiId和EmojiHeat的映射关系。

	private HashMap<Integer, Integer> emojiHeatMap;
    private HashMap<Integer, Integer> emojiIdMap;
    public int deleteColdEmoji(int limit) {
        HashMap<Integer, Integer> newMap1 = new HashMap<>();
        HashMap<Integer, Integer> newMap2 = new HashMap<>();
        for (int id1 : emojiHeatMap.keySet()) {
            if (emojiHeatMap.get(id1) >= limit) {
                newMap1.put(id1, emojiHeatMap.get(id1));
            } else {
                for (int t : emojiIdMap.keySet()) {
                    if (emojiIdMap.get(t) == id1) {
                        messages.remove(t);
                        newMap2.put(t,emojiIdMap.get(t));
                    }
                }
            }
        }
        emojiHeatMap = newMap1;
        for (int t : newMap2.keySet()) {
            emojiIdMap.remove(t);
        }
        return emojiHeatMap.size();
    }

Junit测试

前两次的Junit测试任务主要与Person连通图相关。我的思路是构造50个Person(点)然后一次连接成完全图,每一次连接进行一次全面assert判断,包括query数量是否正确,容器对象是否前后一致等。
JML方法中给出了assignable以及ensure内容,因此测试方法需严格按照assignable以及ensure内容进行测试,对不该修改的变量和容器进行比对来判断是否发生变化。
如下代码是一段删除热度从70到0过程中的assert代码

		for (int k = 70; k >=0; k--) {
            int[] emojiIdList = myNetwork1.getEmojiIdList();
            int[] emojiHeatList = myNetwork1.getEmojiHeatList();
            Message[] messages = myNetwork1.getMessages();
            ArrayList<Integer> newIdList = new ArrayList<>();
            ArrayList<Integer> newHeatList = new ArrayList<>();
            ArrayList<Message> newMessages = new ArrayList<>();
            for (int i = 0; i < emojiIdList.length; i++) {
                if (emojiHeatList[i] >= k) {
                    newIdList.add(emojiIdList[i]);
                    newHeatList.add(emojiHeatList[i]);
                }
            }
            for (Message message : messages) {
                if (message instanceof EmojiMessage) {
                    if (newIdList.contains(((EmojiMessage) message).getEmojiId())) {
                        newMessages.add(message);
                    }
                } else {
                    newMessages.add(message);
                }
            }
            int ret = myNetwork1.deleteColdEmoji(k);
            int[] nowIdList = myNetwork1.getEmojiIdList();
            int[] nowHeatList = myNetwork1.getEmojiHeatList();
            Message[] nowMessages = myNetwork1.getMessages();
            assertEquals(ret, newIdList.size());
            assertEquals(newIdList.size(), nowIdList.length);
            assertEquals(newHeatList.size(), nowHeatList.length);
            assertEquals(nowIdList.length, nowHeatList.length);
            for (int i = 0; i < newIdList.size(); i++) {
                assertEquals(nowIdList[i], (int) newIdList.get(i));
            }
            assertEquals(newMessages.size(), nowMessages.length);
            for (int i = 0; i < newMessages.size(); i++) {
                assertEquals(nowMessages[i], newMessages.get(i));
            }
        }

几种类型测试

  • 黑箱测试是软件测试的一种方法,它不关心被测试的软件内部结构,而是将软件看作一个黑盒子,只关注输入和输出之间的关系。在黑箱测试中,测试人员不需要了解软件的内部实现细节,而是根据软件的规格说明或功能要求来设计测试用例。

  • 黑箱测试通常包括以下几个步骤:
    1.确定测试目标: 首先确定要测试的软件的功能或模块。这些功能或模块可以是整个系统、单个功能模块或子系统等。
    2.编写测试用例: 根据软件的规格说明或功能要求,设计测试用例,包括输入数据、预期输出和执行步骤等。测试用例应该覆盖各种可能的输入情况和边界条件。
    3.执行测试用例: 使用设计好的测试用例来执行测试。在执行测试用例的过程中,记录测试结果,包括实际输出和执行过程中的任何问题或异常情况。
    4.分析测试结果: 分析测试结果,检查实际输出是否与预期输出一致,以及是否存在任何问题或异常情况。如果发现问题,需要及时记录并报告给开发团队。
    5.修复问题: 如果测试发现了问题或异常情况,开发团队需要及时修复这些问题,并重新执行测试,直到问题得到解决。
    6.重复测试: 在软件开发的不同阶段,需要重复进行黑箱测试,以确保软件的质量和稳定性。测试人员可以根据需要修改测试用例,并持续监控软件的性能和功能。

  • 黑箱测试的优点是可以从用户的角度来验证软件的功能和性能,而不需要了解软件的内部实现细节。然而,缺点是无法发现软件内部的逻辑错误或代码缺陷,需要结合其他测试方法来进行综合测试。

  • 白箱测试是软件测试的一种方法,与黑箱测试相对。与黑箱测试关注软件的功能和用户界面不同,白箱测试侧重于检查软件的内部结构、逻辑和代码覆盖率。在白箱测试中,测试人员需要了解软件的内部实现细节,以设计测试用例和验证代码的正确性。

  • 白箱测试通常包括以下几个步骤:
    1.代码审查: 对软件的源代码进行审查,检查代码是否符合编码标准、是否存在潜在的逻辑错误或漏洞等。代码审查可以通过手动审查或使用静态分析工具来完成。
    2.单元测试: 编写单元测试用例,对软件的各个单元(如函数、方法、类等)进行测试。单元测试通常由开发人员编写,并且在代码编写的同时进行。单元测试旨在验证单元的功能是否按预期工作,并且覆盖各种可能的输入和边界条件。
    3.集成测试: 将各个单元组合在一起,进行集成测试。集成测试验证单元之间的交互和集成是否正常工作,以及系统整体功能是否符合预期。
    4.代码覆盖率分析: 使用代码覆盖率工具来分析测试用例覆盖代码的程度。代码覆盖率分析可以帮助测试人员确定哪些代码未被测试覆盖,从而编写更全面的测试用例。
    5.调试和修复: 如果在测试过程中发现了问题或异常情况,测试人员需要对问题进行调试,并与开发人员合作进行修复。修复后,需要重新运行测试用例来验证问题是否得到解决。
    6.性能测试和负载测试: 对软件进行性能测试和负载测试,以评估软件在不同负载下的性能和稳定性。性能测试和负载测试可以帮助确定软件的性能瓶颈和优化空间。
    7.安全测试: 对软件进行安全测试,检查是否存在安全漏洞和潜在的攻击面。安全测试旨在确保软件对各种恶意攻击和威胁具有足够的防御能力。

  • 白箱测试的优点是可以深入检查软件的内部逻辑和代码质量,发现潜在的编码错误和逻辑漏洞。然而,白箱测试需要测试人员具有较强的编程和调试能力,并且测试覆盖范围可能受到时间和资源的限制。因此,通常需要结合黑箱测试等其他测试方法来进行综合测试,以确保软件的质量和稳定性。

  • 单元测试(Unit Testing)是软件测试的一种方法,用于验证软件的各个独立单元(如函数、方法、类等)是否按预期工作。单元测试通常由开发人员编写,并且在代码编写的同时进行。其主要目的是在开发过程中尽早地发现和修复代码中的错误,提高代码的质量和稳定性。单元测试通过针对每个单元编写测试用例,并对其进行自动化测试来实现。它通常不涉及外部依赖,而是通过模拟或替代这些依赖项来隔离单元的测试。

  • 功能测试(Functional Testing)是一种测试方法,用于验证软件的功能是否符合规格说明或用户需求。功能测试通常从用户的角度出发,通过输入不同的数据和操作软件的各种功能来验证软件的行为是否符合预期。功能测试关注软件的输入和输出之间的关系,以及软件是否满足预期的功能需求。它可以通过手动测试或自动化测试来实现,并且通常在软件开发的后期阶段进行。

  • 集成测试(Integration Testing)是一种测试方法,用于验证软件的各个单元之间的集成和交互是否正常工作。集成测试旨在检查单元之间的接口和通信,以及集成后系统整体的功能和性能是否符合预期。它可以分为逐步集成和一次性集成两种方式进行。逐步集成是逐步地将各个单元组合在一起,进行测试和调试;而一次性集成则是将所有单元一次性集成,并进行整体的测试。集成测试通常在单元测试之后,系统测试之前进行。

  • 压力测试(Stress Testing)是一种测试方法,用于评估软件在不同负载下的性能和稳定性。压力测试通过增加系统的负载、并发用户数或数据量等来模拟真实环境中的高负载情况,以评估软件在这些条件下的性能表现和稳定性。压力测试旨在发现系统的性能瓶颈、资源耗尽或系统崩溃等问题,并提供优化建议和改进方案。

  • 回归测试(Regression Testing)是一种测试方法,用于确保在对软件进行修改、更新或优化后,之前已经正常工作的功能仍然保持正常。回归测试通过重新执行之前的测试用例,验证软件的新版本是否引入了新的错误或导致了已有功能的退化。回归测试通常包括自动化测试和手动测试两种方式,并且可以在软件开发周期的不同阶段进行,以确保软件的稳定性和质量。

学习体会

本单元学习JML规格,它提供了一种在Java代码中嵌入形式规约的方法,以便于对代码进行静态分析、测试和验证。JML规约可以描述程序的前置条件、后置条件、不变式以及类的不变式等信息,从而帮助开发者更好地理解和验证程序的行为。在前一个电梯单元中题目以自然语言的形式进行解读,因而我们在电梯开关门和运动实现的细节上各有不同;但是在本单元中无论我们选用什么样的容器或者算法,必须满足JML规格中的内容。对于JML注释较长不好理解的,可以先按着JML最本初的意思编写代码,再进行优化,就能明白该JML方法实现了什么内容

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值