面向对象设计与构造第三单元 --JML

22371328曹玮琳 2024-05-19 19:53:25

1. 测试过程分析

在软件测试的过程中,一个全面而细致的分析是确保测试质量的关键。首先,我们会对测试目标进行明确的需求分析,深入解读待测产品的需求文档、Use Case、界面设计等相关资料,确保对测试范围有清晰的认识。同时,我们还会与客户或系统分析员进行密切沟通,了解业务背景资料,以便更好地理解测试需求。
在测试设计阶段,我们会根据需求分析的结果,制定详细的测试计划。测试计划包括测试策略、测试方法、测试范围、测试环境搭建、测试执行的顺序和时间表等。通过制定5W+1H(原因、对象、地点、时间、人员、方法)的测试计划,我们可以确保测试活动的有序进行。
在测试执行阶段,我们会按照测试计划执行测试用例,并详细记录测试结果。如果发现缺陷或问题,我们会及时记录到缺陷管理系统中,并跟踪其修复情况。修复后,我们会重新执行相关测试用例,确保问题得到妥善解决。
测试完成后,我们会编写测试报告,对测试结果进行总结和分析。测试报告将详细记录测试过程中发现的问题、缺陷的修复情况以及测试活动的整体效果。通过测试报告,我们可以评估测试的有效性和充分性,并为后续的开发和测试提供参考。

2. 黑箱测试与白箱测试的理解

黑箱测试和白箱测试是软件测试中两种重要的测试方法。
黑箱测试,也被称为功能测试或外部测试,主要关注系统的输入和输出。测试人员根据需求和规格说明书设计测试用例,模拟用户的使用场景,检查系统是否能够按照预期产生正确的结果。黑箱测试不需要了解系统的内部结构和实现细节,因此适用于对系统功能的验证和评估。
白箱测试,也被称为结构测试、透明盒测试或内部测试,则关注系统内部的逻辑结构和代码实现。测试人员需要了解系统的内部结构和实现逻辑,基于这些信息设计和执行测试用例。白箱测试有助于发现系统中的代码错误、逻辑错误和性能问题等。通过白箱测试,我们可以更深入地了解系统的内部机制和实现方式,为后续的优化和改进提供参考。

3. 单元测试、功能测试、集成测试、压力测试、回归测试的理解

单元测试:针对程序中的最小可测试单元(如模块、类、方法等)进行测试。确保每个单元都能正常工作,并且满足设计要求。
功能测试:基于黑箱测试的方法,验证系统是否满足功能需求。通过模拟用户的使用场景来检查系统的功能是否完善。
集成测试:将多个单元或模块组合在一起进行测试。检查它们之间的接口和交互是否正常,以及组合后的系统是否能够正常工作。
压力测试:测试系统在异常或极限条件下的性能表现。如高并发、大数据量等,以评估系统的稳定性和可靠性。
回归测试:在修改系统后重新执行之前通过的测试用例。确保修改没有引入新的错误或导致已有功能失效。

4. 数据构造策略

在软件测试中,数据构造策略对于设计有效的测试用例至关重要。有效的数据构造策略可以确保测试用例的全面性和代表性。

常用的数据构造策略包括:

边界值测试:测试输入值的边界情况,以发现可能的边界错误。
等价类划分:将输入数据划分为若干个等价类,并从每个等价类中选取一个或少量数据进行测试。
因果图:分析输入条件和输出结果之间的因果关系,设计测试用例。
正交试验设计:通过正交表来安排测试数据,以覆盖尽可能多的输入组合。

5. 架构设计梳理与图模型构建维护策略

鉴于本次作业架构几乎没有独一无二的地方,因此不做架构图展示。
但是作业中,为了减小queryBlockSum和isCirCle的复杂度,使用了并查集的数据结构,但是稍加了改进。
定义两个容器

private final HashMap<Integer, ArrayList<Integer>> block;
private final HashMap<Integer, Integer> record;

block以组号(代表人物的ID)分块,将所有persons的id分组,record用于记录每个ID对应的人所在的组号,二者结合使用,既可以方便查询某个人所在的组号,又方便将组与组进行合并。

add:

block.put(person.getId(), new ArrayList<>(person.getId));
record.put(person.getId(), person.getId());

即,刚开始加入时,每个人都单独成组。

addrelation中,需要merge时:

int key1 = record.get(id1);
int key2 = record.get(id2);
if (key1 != key2) {
    block.get(key1).addAll(block.get(key2));  
    for (Integer id : block.get(key2)) {
        record.replace(id, key1);
    }          
    block.remove(key2);
}  

先从record获得当前两个人的组号,再将两组合并,选择两个人其一作为代表元素合并为一组,同时不要忘记更新group2中的person在record之中的组号。

删除操作
在modifyRelation时,如果需要删除某个relation,首先可以确定的是,其仅仅有可能会对边所在的极大联通子图产生影响。可能会导致这一子图的分裂,最多分裂为两个互不连通的部分,也可能不会分裂。这取决于要删除的relation是不是一个割边。
删除边后,选取输入的两个id中的一个进行dfs查找其所在的极大联通子图,将旧的极大联通子图的点集合与新的作差,如果结果不为空,则分裂出两个组,更新block和record的值。否则还是一个组。
事实上,在软件测试中,对系统架构的深入理解对于发现潜在问题和优化测试策略至关重要。通过梳理系统架构,我们可以更清晰地了解系统的整体结构和模块划分,以及它们之间的依赖关系和交互方式。
图模型是描述系统结构和交互关系的重要工具。我们可以使用UML图、数据流图等图模型来构建系统的结构图、类图、时序图等。在构建图模型时,我们需要确保图模型的准确性和一致性,以便为后续的测试活动提供准确的参考。
为了维护图模型的有效性,我们需要定期对图模型进行更新和维护。当系统发生变更或新增功能时,我们需要及时更新图模型以反映这些变化。同时,我们还需要对图模型进行验证和测试,确保其能够正确地描述系统的结构和交互关系。

6. 性能问题分析及修复

当出现时间复杂度大于等于O(n^2)复杂度的方法,比如常规实现可能达到O(n^3)的queryTripleSum,一定会出现性能问题,对于dfs这种复杂度为O(n^2)的,可以用,但一定要注意提前退出。当然也可以使用双向bfs,可以大大的减少复杂度,对于二维的图,每增加一层遍历,理论上需要访问的节点数是当前的平方,因此双向bfs使原来的复杂度呈根号级别减小,这里我在第九次作业使用了类似双向bfs的提前退出的dfs,后面作业使用了双向bfs。附上代码。

private void dfs(MyPerson person1, int id1) {
    if (vis.containsKey(id1) || vis.containsKey(desPerson.getId())) {
        return;
    }
    for (Person p : ((MyPerson) desPerson).getAcquaintance()) {
        if (vis.containsKey(p.getId())) {
            return;
        }
    }
    vis.put(id1, id1);
    for (Person person : person1.getAcquaintance()) {
        int id = person.getId();
        if (!vis.containsKey(id)) {
            dfs((MyPerson) person, id);
        }
    }
}

这里的dfs只要遇到和目标节点有链接的点就停止,类似于双向bfs,作者试过和目标节点有连接的节点有链接的作为目标集合,即两层的,但是对于第九次作业的数据量,效果不如一层好。

int twoDirectionBfs(int id, int des) {
    if (id == des) {
        return 0;
    }
    LinkedList<Pair<Integer, Integer>> queue1 = new LinkedList<>();
    HashMap<Integer, Integer> vis = new HashMap<>();
    HashMap<Integer, Integer> record1 = new HashMap<>();
    queue1.offerLast(new Pair<>(id, 0));
    vis.put(id, 1);
    record1.put(id, 0);
    LinkedList<Pair<Integer, Integer>> queue2 = new LinkedList<>();
    HashMap<Integer, Integer> record2 = new HashMap<>();
    queue2.offerLast(new Pair<>(des, 0));
    vis.put(des, 2);
    record2.put(des, 0);
    int value = 0;
    while (true) {
        while (queue1.getFirst().getValue() == value) {
            Pair<Integer, Integer> p = queue1.pollFirst();
            assert p != null;
            MyPerson person1 = (MyPerson) network.getPerson(p.getKey());
            for (Person person : person1.getAcquaintance()) {
                if (vis.containsKey(person.getId()) && vis.get(person.getId()) == 2) {
                    return p.getValue() + record2.get(person.getId());
                }
                if (!vis.containsKey(person.getId())) {
                    vis.put(person.getId(), 1);
                    queue1.add(new Pair<>(person.getId(), p.getValue() + 1));
                    record1.put(person.getId(), p.getValue() + 1);
                }
            }
        }
        while (queue2.getFirst().getValue() == value) {
            Pair<Integer, Integer> q = queue2.pollFirst();
            assert q != null;
            MyPerson person2 = (MyPerson) network.getPerson(q.getKey());
            for (Person person : person2.getAcquaintance()) {
                if (vis.containsKey(person.getId()) && vis.get(person.getId()) == 1) {
                    return q.getValue() + record1.get(person.getId());
                }
                if (!vis.containsKey(person.getId())) {
                    vis.put(person.getId(), 2);
                    queue2.add(new Pair<>(person.getId(), q.getValue() + 1));
                    record2.put(person.getId(), q.getValue() + 1);
                }
            }
        }
        value++;
    }
}

在查找最短路径的时候使用了双向bfs,这里双向bfs尤为需要注意的是,每次优先队列之中不能只进入一个节点,而是需要直接加入一层,这样才能保证得到的一定是最短路径。
在性能测试过程中,当发现系统存在性能问题时,我们需要进行深入的分析和定位。这通常涉及对系统的响应时间、吞吐量、资源利用率等关键指标进行监控和分析。通过分析系统的日志和监控数据,我们可以找到导致性能问题的根本原因。
针对性能问题,我们可以采取一系列修复措施。例如,优化数据库查询、改进算法和数据结构、增加缓存和负载均衡等。在修复过程中,我们需要确保修复措施不会引入新的错误或问题。修复完成后,我们需要重新进行性能测试以验证。

7.规格与实现分离

规格与实现分离是一种软件设计原则,它在软件开发过程中起到了至关重要的作用。以下是对这一原则的详细理解:

概念解析:

规格:规格是对于软件需求的详细描述和定义,它规定了系统应该做什么,但并不直接涉及系统如何做。规格的作用在于明确系统的功 能和性能要求,为开发人员提供一个清晰的目标。
实现:实现则是根据规格进行的具体编程工作,它关注于如何达成规格所规定的目标。实现过程需要考虑具体的算法、数据结构、编程 语言和工具等。
分离:规格与实现分离意味着在软件开发过程中,规格的制定和实现的工作是分开进行的。开发人员不需要完全按照规格的字面意思去编写代码,而是可以根据实际情况选择一个合适的算法或方法来完成规格所要求的功能。

优点分析:

灵活性:

规格与实现分离使得系统更加灵活。当规格发生变化时,只需要修改相关的规格描述,而不需要对已经实现的代码进行大规模修改。这大大减少了软件开发的风险和成本。

可维护性:

由于规格和实现是分离的,因此可以更容易地对系统进行维护和升级。当需要修改某个功能时,只需要修改相应的规格描述,并重新实现该功能即可。这避免了因修改代码而导致的潜在问题和错误。

可重用性:

通过规格与实现分离,可以更容易地实现代码的重用。不同的系统或模块可以使用相同的规格描述,但采用不同的实现方式。这提高了代码的可重用性和系统的可扩展性。

实际应用:

在设计架构时,我们需要为每个类的属性和方法编写规格。这些规格描述了类应该具有的功能和行为,但并不限制具体的实现方式。开发人员可以根据实际情况选择一个合适的算法或方法来实现这些功能。
在编写规格时,我们往往会忽略问题的具体细节,而是将问题抽象出来用某种语言(如JML)进行描述。这使得规格更加简洁和清晰,同时也为开发人员提供了更多的自由度来选择实现方式。
在实现过程中,我们需要关注问题的具体细节并找到一种合适的方法来实现规格所要求的功能。这一过程可能需要考虑多种因素如性能、安全性、可维护性等。

总结:

规格与实现分离是一种重要的软件设计原则,它使得软件开发过程更加灵活、可维护和可重用。通过明确规格和实现的界限并允许它们之间存在差异,我们可以更好地应对软件开发中的变化和挑战。

8. 利用规格信息进行Junit测试设计

在利用规格信息进行Junit测试设计时,我们可以采取一系列精心设计的策略来确保测试的全面性和有效性。

1. 接口测试

首先,我们会针对规格中定义的每一个接口或方法进行测试设计。这包括检查接口的正确性、参数的有效性以及异常处理机制。我们会编写测试用例来验证接口在正常情况下的行为,以及当输入参数不符合要求时,系统是否能够抛出正确的异常或错误信息。

2. 边界值测试

规格中往往定义了参数的取值范围或边界条件。我们会特别关注这些边界值,并设计测试用例来验证系统在这些边界值上的行为。这是因为在实际应用中,边界值往往容易出现问题,因此进行边界值测试是确保系统健壮性的重要手段。

3. 场景测试

规格中通常还包含了一些具体的业务场景或使用案例。我们会根据这些场景设计测试用例,模拟用户在实际使用中的行为,并验证系统是否能够正确处理这些场景。场景测试有助于我们发现那些在实际使用中可能遇到的问题,从而提高系统的可用性。

4. 性能测试

如果规格中包含了性能要求(如响应时间、吞吐量等),我们会编写相应的Junit性能测试用例来验证系统是否满足这些要求。这可以通过模拟高并发、大数据量等场景来实现。性能测试是确保系统能够满足用户期望的关键步骤,也是评估系统性能瓶颈和优化方向的重要依据。

5. 自动化测试

在利用规格信息进行Junit测试设计时,我们还会考虑如何实现测试的自动化。通过编写可重用的测试代码和脚本,我们可以提高测试的效率并减少人为错误。自动化测试不仅可以降低测试成本,还可以提高测试的准确性和可靠性。

9. Junit测试检验代码实现与规格一致性的效果

通过编写全面的Junit测试用例并基于规格信息进行设计,我们可以有效地检验代码实现与规格的一致性。当测试用例能够覆盖规格中定义的所有功能和场景,并且测试结果符合预期时,我们可以认为代码实现与规格是一致的。
Junit测试不仅可以验证代码的正确性,还可以帮助我们发现潜在的问题和缺陷。当测试用例失败时,我们可以根据测试结果进行调试和修复,从而确保代码的质量和稳定性。此外,Junit测试还可以作为代码重构和优化的参考依据,帮助我们改进代码结构和提高代码性能。

10. 本单元学习体会

通过本单元的学习,我深刻体会到了软件测试在软件开发过程中的重要性。测试不仅是确保软件质量和稳定性的重要手段,也是提高代码质量和可维护性的关键步骤。
在编写Junit测试用例的过程中,我不仅学会了如何根据规格信息进行测试设计,还深入理解了规格与实现分离的思想。这让我更加清晰地认识到代码的可读性、可维护性和可测试性对于软件项目成功的重要性。同时,我也学会了如何分析性能问题并进行修复,这使我更加关注代码的性能和效率。
此外,本单元的学习还让我认识到了自动化测试在软件开发中的价值。通过编写可重用的测试代码和脚本,我们可以提高测试的效率并减少人为错误。这不仅可以降低测试成本,还可以提高测试的准确性和可靠性。
总的来说,本单元的学习让我受益匪浅。我不仅掌握了软件测试的基本知识和技能,还学会了如何运用这些知识和技能来改进我的编程实践。我相信这些经验和知识将对我未来的学习和工作产生积极的影响。

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

301

社区成员

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

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