BUAA-OO-Unit3 总结

BUAA-OO-Unit3 总结

一、测试过程总结
黑箱测试与白箱测试的理解
  • 黑箱测试:其核心思想是将被测代码视为一个黑盒子,我们不关心其中的结构和实现细节,直接根据代码的需求和规格说明对其进行测试即可。黑盒测试的重点在于检查代码是否满足功能需求,这种方法的优点是与代码的具体实现分离,即使代码内部结构发生变化,只要外部行为不变,测试用例依然有效。
  • 白箱测试:白箱测试是一种基于代码的测试方法,与黑箱测试相反,白箱测试需要深入代码的内部结构和具体实现。测试者需要查看并理解源代码,设计测试用例来覆盖尽可能多的代码路径、分支、循环和条件,以确保代码的每一部分都经过了验证。它能帮助我们理解代码的执行流程,确保代码逻辑的正确实施。
单元测试、功能测试、集成测试、压力测试、回归测试的理解
  • 单元测试:单元测试是对测试代码的最小可测试单元,通常对是一个函数、一个类或一个模块的测试。
  • 功能测试:功能测试是为保障代码功能满足给定需求的测试,测试时不会考虑软件的内部结构,只关注最终的功能。
  • 集成测试:集成测试是在单元测试之后进行的,用于检查不同模块之间的接口是否能够正确协同工作。它关注的是模块间的数据传递、控制流以及相互依赖关系,目的是发现接口错误、数据一致性问题等。
  • 压力测试:压力测试是为了评估系统在极端条件下的性能和稳定性。它通过模拟大量并发用户访问、大数据量处理或资源限制情况,来检测系统的最大处理能力、响应时间、资源利用情况以及系统何时会达到崩溃点。
  • 回归测试:回归测试是在代码修改或新功能添加后进行的一种测试,目的是确保现有的功能没有因为最近的更改而受到影响。它通常会重新执行之前成功运行的测试用例,以验证软件的已有功能仍然能够正常工作。
数据构造策略

本单元的测试的数据构造我主要遵循了随机原则、全面原则以及多次原则,下面我将对我的数据构造策略进行简单介绍。
随机原则:本单元数据构造中person的id信息、不同person之间的关系信息等均遵循随机原则构造,以确保数据的随机性。
具体实现如下:

private void addPerson(int id) {
        MyPerson p = new MyPerson(id, "1", 1);
        persons.put(id, p);
        try {
            myNetwork.addPerson(p);
        } catch (EqualPersonIdException e) {
            throw new RuntimeException(e);
        }
}

private void addRelation(int id1, int id2) {
        Random random = new Random();
        try {
            int n = random.nextInt();
            while (n >= 0) {
                n = random.nextInt();
            }
            myNetwork.addRelation(id1, id2, n);
        } catch (PersonIdNotFoundException | EqualRelationException e) {
            throw new RuntimeException(e);
        }
 }

全面原则:本单元测试数据测试从零图到完全图全部进行了测试,以满足数据全面性。
具体实现如下:

private void modifyRelation(int id1, int id2) {
        Random random = new Random();
        try {
            int n = random.nextInt();
            while (n >= 0) {
                n = random.nextInt();
            }
            myNetwork.modifyRelation(id1, id2, n);
        } catch (PersonIdNotFoundException | RelationNotFoundException | EqualPersonIdException e) {
            throw new RuntimeException(e);
        }
    }

		for (int i = 0; i < n; i++) {
            addPerson(i);
            idSet.add(i);
        }
        int times = n*(n-1)/2;
        for (int i = 0; i < times; i++) {
            createRelation();
            test();
        }

多次原则:本单元测试均进行了多次测试,以确保测试的多样性。
具体实现如下:

public void testQueryCoupleSum() {
        int n = 30;
        for (int i = 0; i < n; i++) {
            addPerson(i);
            idSet.add(i);
        }
        int times = n*(n-1)/2;
        for (int i = 0; i < times; i++) {
            createRelation();
            test();
        }
        for (int i = 0; i < times; i++) {
            delRelation();
            test();
        }
    }
二、架构设计:图模型构建和维护策略

依据JML的规格描述,我们需要实现一个人际关系网络:MyNetwork,通过分析可以确定使用图的数据结构实现。为了实现更好地查询性能,采用HashMap存储数据。此外,我们还需要根据具体函数JML规格,依据图算法实现特定函数的功能,下面我将对本单元作业的图模型构建和维护策略进行介绍。

并查集与连通块

hw9中需要我们实现isCircle()和queryBlockSum()函数,根据其JML规格可以发现我们需要通过函数判断图中两个点之间是否连通,以及图中连通块的数量。
据此,我设计了一个MyBlock类作为连通块存储在同一连通块中的person,Myblock有一个特定属性blockId,根据blockId是否相同我们可以判断两个person是否在同一连通块中,而在MyNetwork中有一个HashMap存储MyBlock。当执行addRelation时,两个person的连通块使用mergeBlcok进行融合。当使用modifyRelation时,使用dfs重建图中连通块。

public class MyBlock {
    private int id;

    private HashMap<Integer, Person> persons;

    public MyBlock(int id) {
        this.id = id;
        persons = new HashMap<>();
    }

    public int getId() {
        return id;
    }

    public HashMap<Integer, Person> getPersons() {
        return persons;
    }

    public void addPerson(Person person) {
       ...
    }

    public void mergeBlock(MyBlock myBlock) {
       ...
    }

    public boolean isExist(Person person) {
        return persons.containsKey(person.getId());
    }
}
查询最短路径

使用优先队列优化的Dijkstra算法查询。
具体实现:

private int shortest(int id1, int id2) {
        MyPerson p1 = (MyPerson) getPerson(id1);
        MyPerson p2 = (MyPerson) getPerson(id2);
        HashMap<Person, Integer> distance = new HashMap<>();
        HashMap<Integer, Boolean> visited = new HashMap<>();
        for (Person p : persons.values()) {
            distance.put(p, Integer.MAX_VALUE);
            visited.put(p.getId(), false);
        }
        distance.put(p1, -1);
        PriorityQueue<Person> pq =
                new PriorityQueue<>(Comparator.comparingInt(distance::get));
        pq.add(p1);
        while (!pq.isEmpty()) {
            MyPerson current = (MyPerson) pq.poll();
            if (current.equals(p2)) {
                return distance.get(p2);
            }
            if (visited.get(current.getId())) {
                continue;
            }
            visited.put(current.getId(), true);
            for (Person neighbor : current.getAcquaintance().values()) {
                int newDistance = distance.get(current) + 1;
                if (!visited.get(neighbor.getId()) && newDistance < distance.get(neighbor)) {
                    distance.put(neighbor, newDistance);
                    pq.add(neighbor);
                }
            }
        }
        return distance.get(p2);
    }
维护策略
  • 脏位维护:连通块在mr后会改变,需要重置,如果mr就重建会极大影响性能,所以设置一个dirty位用于判断是否使用了mr,再在查询时进行判断,如果dirty为1就重建,再输出结果。
  • 动态维护: queryTagValueSum函数的实现可以在MyTag中设置valueSum,对其进行维护,以防止每次查询就重新计算。
三、性能问题及其修复

对valueSum进行动态维护,防止多次qtvs指令导致的ctle。

四、规格与实现分离

在完成本次作业的时候会发现,如果按照JML规格实现某些函数,可能会出现O(n^2)等时间复杂度较高、性能交叉的算法,这会导致我们在强测中出现ctle的错误,因此我们不能使用JML中的实现方法。实际上,为了规格的清晰和准确,JML表述往往采用比较朴素的方法,因此我们在实现时,需要在理解规格的基础上,找到更高效的实现方法。即: 规格是指定需求,实现是完成指定需求的方法。 我们要做的就是找到完成指定需求的最高效的方法。

五、Junit测试总结
利用JML规格设计

逐级比对JML规格中的每个信息,确保测试能够覆盖JML中每一个变化,同时不变量也保持不变。
具体来说就是:

  • 比对assignable对象
  • 比对ensures内容
  • 比对result内容
  • 比对exception内容
Junit测试检验代码实现与规格的一致性的效果

在测试覆盖JML规格中每一种情况以及较强的测试数据下,Junit测试能够非常好的检验代码实现与规格的一致性的效果。

六、学习体会

面向JML规格编程,让我有一种面向黑箱编程的感觉。我只需要在理解JML规格及其指定需求后,用更加高效的方法实现它即可。比较其他编程形式,JML给了我们编程的方向及其可能的实现方法,在完成后还能够根据JML来编写更加完备的测试方案。相对而言,面向JML规格编程能够让我们的代码bug更少,更加严谨。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值