BUAA_OO 第三单元总结

在这里插入图片描述

一、测试过程

黑箱测试

定义: 黑箱测试是一种不考虑被测软件内部结构和实现的测试方法,它将被测对象视为一个“黑盒子”,仅通过其接口输入数据并观察输出结果来验证系统的行为是否符合预期。测试的重点在于验证软件功能是否正确,以及用户需求是否得到满足。

目的: 主要是为了验证软件的外在行为,确保软件功能的正确性,以及用户界面和业务流程的准确性。

方法: 常用的黑箱测试技术包括等价类划分、边界值分析、因果图法、错误推测法等。这些方法帮助设计测试用例,以高效地覆盖各种可能的输入和输出情况。

适用阶段: 通常应用于系统测试和验收测试阶段。

白箱测试

定义: 白箱测试则恰恰相反,它要求测试者对软件的内部结构和代码有深入的了解。这种测试方法通过直接查看和分析源代码来设计测试用例,旨在检查程序的逻辑、路径、分支、循环等内部结构是否正确。

目的: 目的是发现代码中的逻辑错误、安全漏洞、性能问题以及任何不符合编程规范的地方。

方法: 实施白箱测试时,可以采用不同的覆盖标准,如语句覆盖、分支覆盖、路径覆盖等,来确保代码的每个部分都经过测试。

适用阶段: 通常在单元测试和集成测试阶段最为常用。

单元测试:

单元测试是对软件中的最小可测试单元(如函数、方法或类)进行的测试。它的目的是验证这些基本构建块按预期工作,独立于其余的代码。单元测试通常由开发人员编写,并在代码编写过程中或之后立即执行,使用白盒测试方法来确保代码的各个部分正确实现了它们的设计意图。

功能测试:

功能测试关注的是软件的整体功能,检验软件是否满足用户需求规格说明书的要求。它从最终用户的角度出发,验证软件的各项功能是否正常工作,而不关心内部实现细节。功能测试确保软件在特定场景下提供正确的输出,处理正确的输入,并且能够响应用户的各种操作。

集成测试:

集成测试是在单元测试之后进行的,目的是验证各个模块或组件在组合起来之后能否协同工作。它关注模块间的接口和交互,确保当多个已经过单元测试的模块被集成到一起时,它们之间能够正确通信,没有冲突或数据丢失等问题。集成测试可以是自底向上、自顶向下或基于接口的等多种策略。

压力测试:

压力测试,也称为负载测试或强度测试,是一种性能测试类型,用于评估系统在超出正常或预期工作量条件下的行为。通过模拟大量用户同时访问或高负载的情况,压力测试旨在发现系统性能瓶颈、稳定性问题和失败点,比如响应时间延长、系统崩溃或数据丢失等情况。

回归测试:

回归测试是在软件修改后(例如修复了一个错误或添加了一个新功能)重新执行之前已通过的测试,以确保原有的功能仍然按预期工作,没有因新变更引入新的错误。回归测试可以是手动的也可以是自动化的,其目的是确保软件的持续稳定性和可靠性,减少因修改而引入的问题。

每种测试类型都有其独特的价值和应用场景,在软件开发生命周期的不同阶段扮演着重要角色,共同确保软件质量。

二、架构设计

本单元作业基于JML规格迭代开发,因此总的架构与官方包的要求差异不大,仅仅只是为了避免一个类长度不要过长,增添了一些方法类。

以第三单元第一次作业为例:
类图
在这里插入图片描述
类分析
在这里插入图片描述
方法分析(部分)
在这里插入图片描述

三、部分算法

BFS算法

BFS(Breadth-First Search,广度优先搜索)是一种用于遍历或搜索树或图的算法。该算法从起始顶点开始,逐步探索所有邻近的节点,然后扩展到下一层邻近节点,以此类推,直到找到目标或遍历完整个图。

                ArrayList<Integer> perArr = new ArrayList<>();
                perArr.add(id1);
                HashMap<Integer, Boolean> perIfVisited = new HashMap<>();
                perIfVisited.put(id1, true);
                for (int i : persons.keySet()) {
                    if (i != id1) {
                        perIfVisited.put(i, false);
                    }
                }
                do {
                    int startId = perArr.get(0);
                    HashMap<Integer, Person> startAcquaints =
                            ((MyPerson) getPerson(startId)).getAcquaintances();
                    perArr.remove(0);
                    for (int id : startAcquaints.keySet()) {
                        if (!perIfVisited.get(id)) {
                            if (id == id2) {
                                return true;
                            } else {
                                perArr.add(id);
                                perIfVisited.put(id, true);
                            }
                        }
                    }
                } while (!perArr.isEmpty());
                return false;

图的遍历与计数法(处理三元环问题)

  • 首先对所有节点进行排序,确保边的方向是从度数小的节点指向度数大的节点,相同度数则按照编号顺序。
  • 枚举每个节点 x,标记其所有邻接节点 y。
  • 再枚举 y 的邻接节点 z,如果 z 已经被标记且 z 不等于 x,则找到了一个三元环 (x, y, z)。
  • 此方法避免了直接的立方运算,但依然需要遍历所有可能的环,复杂度较高。
        int result = 0;
        HashMap<Integer, Person> newPersons = new HashMap<>();
        for (int i : persons.keySet()) {
            String perName = persons.get(i).getName();
            int perAge = persons.get(i).getAge();
            MyPerson myPerson = new MyPerson(i, perName, perAge);
            newPersons.put(i, myPerson);
        }
        for (int i : persons.keySet()) {
            HashMap<Integer, Person> acquaintances =
                    ((MyPerson) persons.get(i)).getAcquaintances();
            for (int j : acquaintances.keySet()) {
                if (i < j) {
                    MyPerson pi = (MyPerson) newPersons.get(i);
                    MyPerson pj = (MyPerson) newPersons.get(j);
                    pi.getAcquaintances().put(j, pj);
                }
            }
        }
        for (int i : newPersons.keySet()) {
            HashMap<Integer, Person> acquaintances =
                    ((MyPerson) newPersons.get(i)).getAcquaintances();
            for (int j : acquaintances.keySet()) {
                Person p = acquaintances.get(j);
                for (int k : ((MyPerson) p).getAcquaintances().keySet()) {
                    if (acquaintances.containsKey(k)) {
                        result++;
                    }
                }
            }
        }
        return result;

四、Junit测试

1. 理解并分解规格要求

  • 详细阅读规格文档:首先,彻底理解软件的需求规格说明书,明确每个功能的输入、输出、边界条件及异常处理要求。
  • 功能分解:将复杂的规格要求分解为更小、更易于管理的测试案例集合,确保每个功能点和约束条件都有对应的测试用例。

2. 设计针对性的测试用例

  • 使用等价类划分:根据规格说明,识别有效和无效的输入值,设计等价类用以覆盖所有可能的情况。
  • 边界值分析:针对规格中提到的边界条件,设计测试用例,确保边界上的行为符合预期。
  • 异常情况测试:根据规格文档中描述的异常处理规则,设计测试用例来验证程序在异常情况下的行为是否正确。
  • 状态转换测试:如果应用涉及多个状态,基于规格说明设计测试用例来验证状态之间的转换逻辑是否准确。

3. 利用JUnit框架特性

  • 利用注解:使用@Test, @Before, @After, @BeforeClass, @AfterClass等注解组织测试代码,确保清晰的测试结构和高效的资源管理。
  • 参数化测试:对于需要多种输入值验证的场景,使用JUnit的参数化测试功能,一次性为同一测试方法提供多组测试数据。
  • 断言:充分利用JUnit提供的各种断言方法(如assertEquals, assertTrue, assertNull等)来验证实际输出与预期结果的一致性。

4. 自动化与持续集成

  • 自动化测试:确保所有测试用例可以通过自动化脚本运行,提高测试效率和频率。
  • 持续集成:将JUnit测试集成到CI/CD流程中,每次代码提交后自动执行测试,快速反馈问题,保证代码质量。

5. 效果检验

  • 及时反馈:JUnit测试能即时反馈代码更改是否破坏了现有功能,有助于快速定位问题。
  • 覆盖率报告:通过工具(如JaCoCo)生成测试覆盖率报告,评估测试用例对代码的覆盖程度,确保规格相关代码得到充分测试。
  • 一致性验证:精心设计的测试用例能够确保代码实现严格遵循规格要求,减少遗漏和误解,提升软件质量。
  • 维护性增强:随着项目的进展,良好的JUnit测试集成为代码库提供了安全网,便于后续开发和维护,减少回归错误。

五、学习体会

本单元的重点还是在于JML规格的学习与实践,以下是我学习JML规格后产生的心得体会:

1. 规格先行的思维转变

JML促使开发者从“编写代码”转变为“定义规格”,强调在编码之前明确接口和类的行为规范。这种规格先行的方法帮助团队成员在早期阶段就达成共识,减少了后期因为需求理解不一致导致的返工。

2. 强化合同编程

JML的核心是合同编程(Design by Contract, DbC),它要求为每个方法定义前置条件、后置条件和不变量。这一过程迫使开发者深入思考函数的输入输出关系、类的状态保持规则,从而提升代码的健壮性和可维护性。

3. 提高代码自文档化能力

JML注释直接嵌入到Java源码中,作为一种形式化的注释,它们不仅对人友好,也能被一些工具解析,生成正式的规格说明文档或用于静态验证。这意味着代码本身成为了最直接的规格说明书,降低了沟通成本。

4. 静态验证与动态测试的结合

学习JML后,可以利用工具(如OpenJML)进行静态验证,自动检测代码是否违反了规格说明,这在编译期就能发现潜在错误。同时,JML规格也可以辅助动态测试的编写,使得单元测试更加有针对性,确保实现与规格的一致性。

5. 推动形式化方法的应用

JML是一种轻量级的形式化方法,它让开发者无需成为形式化方法专家就能利用其优势。通过实践JML,可以逐步掌握并欣赏形式化规格描述的价值,为进一步探索更复杂的形式化验证技术打下基础。

总的来说,JML的学习不仅是一项技术技能的提升,更是一种软件工程思想的深化。它促使开发者更加严谨地思考软件设计,提高了软件的质量和可靠性。然而我并不喜欢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值