OO第三单元总结博客

陈思潮-22373112 学生 2024-05-15 22:47:48

OO第三单元总结博客

前言

黑箱测试与白箱测试

  • 黑箱测试:此方法侧重于从用户的角度验证软件功能,关注输出是否正确,而不考虑内部工作原理。它依赖于需求文档和用户界面来设计测试案例,适用于验证软件的外部行为是否满足需求。黑箱测试的关键在于设计全面的测试场景,包括正常流程、边界条件及异常处理,以确保软件在所有预期使用情况下表现正确。
  • 白箱测试:与黑箱测试相反,白箱测试深入到软件的内部结构,利用代码逻辑、控制结构和路径覆盖等信息设计测试案例。这使得测试人员能直接检验代码的每个部分是否按预期执行,有利于发现逻辑错误、冗余代码和不安全的编程习惯。

各类测试的理解

  • 单元测试:这是最基本的测试形式,专注于程序的最小可测试单元,确保每个单元都能独立正确工作。它有助于快速定位问题并促进持续集成和快速反馈循环。
  • 功能测试:着重于验证软件的主要功能是否符合需求规格说明,确保软件的输出与预期一致,涵盖所有功能点和用户场景。
  • 集成测试:在单元测试之后,验证不同模块或服务之间交互的正确性。重点检查接口兼容性、数据传递及错误处理,确保整体系统协同工作无误。
  • 压力测试:通过模拟高负载或极端操作条件来评估系统的稳定性和性能极限。这不仅包括负载测试(模拟正常及高峰期间的用户负载),还有稳定性测试(长时间高负载下的系统表现)和容量测试(确定系统最大处理能力)。
  • 回归测试:任何修改或新增功能后,都需要执行之前通过的测试案例,以确认更改未引入新的错误。自动化回归测试是提高效率的有效手段。

数据构造策略

  1. 基于需求的构造:依据软件的具体需求和规范设计测试数据,确保覆盖所有正常、异常和边界条件。这包括正向测试(正常输入)、负向测试(异常或非法输入)以及边界值测试。
  2. 边界值分析:特别关注数据范围的边界,因为这些往往是错误高发区。选择刚好等于、略高于、略低于边界值的数据进行测试,确保边界条件被正确处理。
  3. 随机数据生成:使用工具自动生成大量随机数据,有助于发现可能被固定测试数据遗漏的问题,尤其是那些在复杂数据组合下才显现的缺陷。
  4. 实际或模拟数据使用:在某些情况下,使用真实的或接近真实的生产数据进行测试,可以更准确地反映系统在实际环境中的表现。
  5. 数据多样性:确保测试数据的多样性,覆盖不同的数据类型、格式、长度和特殊字符等,以验证软件的健壮性和兼容性。

第一次作业分析

UML类图

img

代码架构分析

第一作业两个比较重要的部分,分别是isCircle(int id1, int id2),queryBlockSum()queryTripleSum()这三个方法的实现,这三个方法的实现依赖于我们需要去维护一个连通块的数量,和一个三角形数量,由于我们有:添加人、添加边、删除边的操作。如果我们每次都dfs来查找的话,这样的时间复杂度显然是我们不可以接受的,所以我们采用并查集,每次在加人或添加关系时,去维护这个并查集,而在删除关系时,通过dfs染色的方式对并查集进行重建,下面我们来看具体的过程。

  • 我们添加人物时,调用add方法;添加关系时调用merge(int id, int id),并且把前者视做后者的父亲,find操作是在找“族长”(感觉这个词很贴切),他会不断的根据存储的值去找父节点,直到父节点的值为本身,说明这个人就是族长,然后我们更新这条搜寻链路上的所有人的父节点都为该族长,这个操作是O(n)的
  • 关于删除边的操作我是这样解决的,我们比如我们要删除id1id2之间的关系,那么首先我们将id2的父节点设为自己,然后将id1作为dfs的起始点进行dfs, 在dfs过程中,将与id1相连通的点的父节点设为id1, 之后去看id2的父节点是否为id1,如果是的话,说明两者仍连通,并且并查集已经被重构完成,如果不是的话,说明两者不再连通,我们对id2进行相同的操作,以此来完成并查集的重构。
  • blockSum 添加人的时候++,添加关系的时候,调用find,如果两者的族长相同,那么不变,如果不同,blocksum--,并且merge两个人,删除关系的时候,就按照上面进行并查集的重建,如果仍连通则blocksum不变,否则++
  • triSum 添加或删除关系时,遍历其他所有人,看与这两人是否连接,如果连接,那么trisum++或--
public class DisjointSet {
    private HashMap<Integer, Integer> parentMap;//前键为当前,后键为父

    public DisjointSet() {
        this.parentMap = new HashMap<>();
    }

    public void add(int id) {
        if (!parentMap.containsKey(id)) {
            parentMap.put(id, id);
        }
    }

    public int find(int id) {
        int rep = id;
        while (rep != parentMap.get(rep)) {
            rep = parentMap.get(rep);
        }
        int now = id;
        while (now != rep) {
            int fa = parentMap.get(now);
            parentMap.replace(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;
        }
        parentMap.replace(fa1, fa2);
        return 0;
    }

    public void resetParent(int id, int faId) {
        this.parentMap.replace(id, faId);
    }

    public int askFa(int id) {
        return parentMap.get(id);
    }

}

这次作业中我们还需要对异常进行计数,包括异常的总数与对应ID的异常数,我的实现方法是,先建立一个计数类Counter,如下:

public Counter() {
        this.totalCnt = 0;
        this.idCntMap = new HashMap<>();
    }

totalCnt负责整体的计数,而idCntMap 就负责记录每一个id触发的异常的次数

之后在所有异常类中添加如下内容

private static Counter counter;

    static {
        counter = new Counter();
    }

用来实现静态成员变量的初始化。在Java中,静态成员变量属于类级别,不属于任何特定对象实例,也就是说,所有此类的对象共享同一个静态变量。以此用于统计创建该类实例的次数,这样的初始化方式可以确保这个计数在程序的整个生命周期内都是准确的,并且对所有实例可见。

第二次作业分析

UML类图

img

代码架构分析

第二次作业整体上架构变化不明显,我们多了一个Tag类,我们依然选择在删边与减边的阶段去维护Tag里的内容。第二次作业的难度整体很小,我就写点易错的点吧。

  • queryTagAgeVar,在实现这个方法时我依然选择的是维护AgeSum 和AgeSquareSum的值的,但是我们在写这个公式的时候,

    /*@ ensures \result == (persons.length == 0? 0:
          @          ((\sum int i; 0 <= i && i < persons.length; persons[i].getAge()) / persons.length));
          @*/
        public /*@ pure @*/ int getAgeMean();
    
        /*@ ensures \result == (persons.length == 0? 0 : ((\sum int i; 0 <= i && i < persons.length; 
          @          (persons[i].getAge() - getAgeMean()) * (persons[i].getAge() - getAgeMean())) / 
          @           persons.length));
          @*/
            public /*@ pure @*/ int getAgeVar();
    

    从这个JML可以知道,getAgeVar的描述中的AgeMean已经向下取整过了因此

img


不恒等于

img


正确的做法是将其拆解为

img

第三次作业分析

UML类图

img

代码架构分析

第三次作业我们增加了Message类,和几个特殊的Message类,整体来说没啥难点,只需要严格的遵守JML去写就行喽,那就略!

分析作业中出现的性能问题及其修复情况,谈谈自己对规格与实现分离的理解

  • 本次作业中我未出现任何性能问题
  • 我认为规格与实现分离就是:我们在作业中遇到规格时,我认为我们首先要去理解这个规格旨在需要我们去实现哪一个功能,实现这个功能我们需要利用到哪些已经存在的数据,我们需要对哪些数据进行保护,需要修改哪些数据,在此基础上我能设计哪些算法去尽可能少的时间复杂度内去解决这个功能,在这个算法的基础上还需要哪些数据结构去帮助我实现这一算法

Junit测试方法,总结分析如何利用规格信息来更好的设计实现Junit测

利用规格信息设计JUnit测试

  1. 确保assignable的正确性
    • 设计思路:基于规格中定义的可赋值变量(assignable)范围,编写测试用例时应专门设计测试数据,用于验证除了指定可变的部分外,其他非assignable元素在执行操作前后是否保持不变。这可以通过在测试前记录这些元素的状态,执行操作后再进行比对实现。
    • JUnit实现:可以使用@Before注解设置初始状态,执行操作后使用断言(assertThat)比较预期与实际结果,确保没有非预期变更。注意深克隆一份原始数据作为对照。
  2. 确保ensures的正确性
    • 设计思路:规格中的ensures部分定义了操作后应满足的条件。测试时,需根据这些条件构造测试场景,验证操作完成后系统状态或输出是否满足了这些预期的变化。
    • JUnit实现:针对每项ensures要求,编写测试案例并使用断言验证预期的结果状态,确保所有预期变化都得到满足。
  3. 检查异常处理
    • 设计思路:规格中应明确指出在特定输入或条件下预期抛出的异常。测试中应包括这些异常情况,以确认程序在面对异常数据时能够按预期响应。
    • Junit实现:使用@Test(expected = Exception.class)注解测试方法,确保在给定异常输入时抛出正确的异常类型。
  4. 性能考量
    • 设计思路:虽然小规模数据测试时性能不是首要考虑,但确保测试覆盖性能关键路径依然重要。

学习心得体会

JML单元真的让人很舒适↖(^ω^)↗ ,整体框架已经由规格信息给出,你只管去正确实现就行,感觉JML很适合团队任务(或许在软工这门课可以用到?不懂,大雾),团队可以整体设计好实现一个任务的框架,然后共同写好一个规格,然后再分发给团队的成员去实现,成员实现后先进行Junit测试,再上交代码

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

301

社区成员

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

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