BUAA OO Unit3总结

BUAA OO Unit3总结

前言

第三单元学习的主题是了解基本的 JML 语法和语义,以及具备根据 JML 给出的规格编写 Java 代码的能力。难点主要在于测试的进行,以及部分方法的性能问题,但总体难度还是低于前几次作业,具体分析如下:

测试过程分析

黑箱测试、白箱测试

黑箱测试是基于功能规格说明书,独立于程序内部逻辑的测试。

  • 目的是验证软件的功能是否按照需求工作。
  • 一般策略为基于输入输出关系设计测试用例,关注于功能完整性和用户视角。

白箱测试是基于程序内部逻辑的测试,也称为结构化测试。

  • 目的是验证程序逻辑是否正确,查找代码中的错误。
  • 一般策略为设计测试用例以验证各个代码路径和逻辑条件,关注程序的执行路径和内部结构。

黑箱测试的优点是:

  • 不需要了解内部代码结构,可以由非开发人员执行。
  • 测试者关注于软件功能和用户需求,更加贴近最终用户的视角。
  • 可以在没有源代码的情况下进行测试,适用于与开发团队分开的测试团队。

缺点是:

  • 测试覆盖面相对较低,无法发现代码中的逻辑错误。
  • 难以设计完整的测试用例覆盖所有可能的情况。
  • 对程序内部结构和算法的理解程度有限,可能会错过一些潜在的问题。

白箱测试的优点是:

  • 可以深入了解代码逻辑和内部结构,能够发现代码层面的错误和逻辑缺陷。
  • 可以设计更加全面和精细的测试用例,提高测试覆盖率。
  • 有助于优化代码结构和性能,提高软件质量。

缺点是:

  • 需要测试人员具备开发技能和对代码的理解,测试成本相对较高。
  • 可能因为过于关注代码实现细节而忽略了用户需求和功能。
  • 难以应对代码结构变化频繁的情况,维护成本较高。

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

  • 单元测试是测试软件的最小单元(如函数或模块),目的是确保每个单元按照预期工作,通常由开发人员编写和执行。一般使用单元测试框架编写测试用例,覆盖所有可能的输入和输出情况。
  • 功能测试是验证系统的功能是否按照需求规格工作。目的是确保软件的功能符合用户的预期。一般是基于用户需求规格书编写测试用例,包括正常和异常情况。
  • 集成测试是测试多个软件模块组合后的集成结果。目的是验证模块之间的接口和交互是否正确。一般是逐步集成各个模块,测试其功能和接口的正确性。
  • 压力测试是测试软件在正常或预期负载下的稳定性和性能。目的是确定软件在压力下的性能极限和资源消耗。一般是模拟高负载条件,观察系统的响应时间和资源使用情况。
  • 回归测试是在修改代码后,确认修改不会影响现有功能的测试。目的是防止在修复bug或添加新功能后引入新问题。一般是重复执行之前的测试用例,确保软件的其他部分没有受到影响。

数据构造策略

  • 一方面是采用评测机自动评测,数据构造就是在符合题目数据要求的前提下随机生成,采用多次测试来寻找bug.
  • 另一方面是Junit测试中采用到的数据构造,主要是结合JML构造能覆盖所有JML要求的数据。

本单元的架构设计

首先通过类图来分析整体结构(以第三次作业的最终架构为例)

请添加图片描述

图模型构建和维护策略

主要是采用了并查集的构建和维护。
1.并查集的构建

	private HashMap<Integer, Integer> bingCha = new HashMap<>();

采用了HashMap这一数据结构,可由当前结点指向父节点。

2.并查集的维护
首先通过对Hashmap的操作实现插入新的映射关系和对两个并查集的合并。

    public void put(int a, int b) {
        bingCha.put(a,b);
    }
    
    public void heBing(int id1, int id2) {
        int a = getFather(id1);
        int b = getFather(id2);
        bingCha.put(a,b);
    }
    
    public int getFather(int id) {
        int a = bingCha.get(id);
   	    int b = id;
        while (a != b) {
            b = a;
            a = bingCha.get(b);
        }
        bingCha.put(id,a);
        return a;
    }

当需要删除关系时,需要遍历并查集,同时这里也实现了对两个后续要查询的量tripleSum和blockSum的维护。

    public void change(int id1,int id2) {
        for (Person person : ((MyPerson)getPerson(id2)).getAc()) {
            if (person.isLinked(getPerson(id1)) && !person.equals(getPerson(id1))) {
                tripleSum--;
            }
        }
        HashSet<Integer> visited = new HashSet<>();
        int sign = bingCha.dfs(id1,id1,visited,id2,(MyPerson)getPerson(id1));
        if (sign == 0) {
            HashSet<Integer> visited1 = new HashSet<>();
            bingCha.dfs(id2,id2,visited1,-1,(MyPerson)getPerson(id2));
            blockSum++;
        }
    }

遍历方法:

    public int dfs(int nb, int id, HashSet<Integer> visited, int sign, MyPerson it) {
        int signal = 0;
        visited.add(id);
        bingCha.put(id,nb);
        signal = (id == sign) ? 1 : 0;
        for (Person person : it.getAc()) {
            if (!visited.contains(person.getId())) {
                if (dfs(nb,person.getId(), visited,sign,(MyPerson) person) == 1) {
                    signal = 1;
                }
            }
        }
        return signal;
    }

性能问题及其修复情况

出现的性能问题

只在第三次作业的强测中发现了一处问题,主要集中在qtvs这个方法的优化没有到位,修复时改变了算法,将复杂度从n^3 降低到 n^2。

    for (int i = 0; i < persons.size(); i++) {
        for (Person it : ((MyPerson)persons.get(i)).getAc()) {
            if (persons.contains(it)) {
                valueSum = valueSum + persons.get(i).queryValue(it);
            }
        }
        /*for (int j = i + 1; j < persons.size(); j++) {
            if (persons.get(i).isLinked(persons.get(j))) {
                valueSum = valueSum + 2 * persons.get(i).queryValue(persons.get(j));
            }
        }*/
    }

其中注释部分为修改前的代码,忽视了Person类中的isLinked()方法同样需要一层循环。

其他优化性能方法
  1. 采用空间换时间的策略,同时构建ArrayList和HashMap的数据结构,前者用于遍历,后者用于查找,下面是一个示例。
    private HashMap<Integer, Tag> tags = new HashMap<>();
    private ArrayList<Tag> ta = new ArrayList<>();
    
    public Tag getTag(int id) {
        if (containsTag(id)) {
            return tags.get(id);
        }
        return null;
    }

    public void removeTagPerson(Person person) {
        for (Tag it : ta) {
            if (it.hasPerson(person)) {
                it.delPerson(person);
            }
        }
        for (Tag it : tags.values()) {
            if (it.hasPerson(person)) {
                it.delPerson(person);
            }
        }
    }
  1. 采用在过程中维护的方法,实际上是将单个方法的时间复杂度均摊到多个方法,例如,之前在并查集中维护的tripleSum和blockSum。

规格与实现分离

规格与实现分离指的是将软件系统中的规格(或者叫做需求)与实现(代码)分开,以便于更好地管理和维护系统。

具体在这次作业中来看,JML提供了规格,但并没有给出实现方法,这也是为什么大家实现后的方法时间复杂度各有不同。

优点是可以提高系统的可维护性、可测试性和可扩展性,缺点是为实现过程提高了难度,并且对规格的准确性有较高的要求,随着开发的深入,规格的描述可能愈加复杂,导致为理解提供较高不必要的难度(第三次作业中部分JML描述就显得较为冗杂)。

Junit测试

最稳妥的方式就是依据提供的JML规格一句一句测试,此外就是结合测试需要编写数据。

我在三次作业中的数据都是自己捏造的,并没有采用产生随机数据生成的方式。因为数据是自己捏造的,很清晰方法运行后各个数据结构和返回值的情况,因此只要一一作比即可。

学习体会

整体而言,这一单元的难度相比之前的几个单元有明显下降,可以说是训练的深度下降但广度提升,对于测试数据的编写,规格的理解和代码性能的提升都有了不小的进步。

但是仍然对学习和应用JML实现的意义有不解之处,当本身项目规模达到一定程度后,实现和理解JML的难度甚至高于实现代码本身,且由于JML过于冗长,有时也不能达到很好的描述规格的目的,容易产生遗漏或误解。

希望能在之后的学习和实践中逐渐更深层次的领会JML的意义。

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值