BUAA OO 第三单元总结

第三单元总结

测试过程

黑箱测试白箱测试

黑盒测试为功能测试,主要目的是程序的错误。或者说,只要程序能够正确处理我给的输入给出期望的输出,那么就通过了黑箱测试,而我们不关心其内部的运行细节。 白箱测试为结构测试,它关心的是程序的具体实现逻辑。例如一个程序尽管输出正确,但耗费了比预期时间长的时间,这时候就需要对其内部的条件判断,循环结构等代码进行分析。

对测试的理解

单元测试: 测试单一组件或模块的功能。例如:我们完成一个方法后,可以构造测试样例对这一个方法进行单元测试。我们完成几个互有依赖关系的方法后,构造测试样例对这几个方法进行单元测试。

功能测试: 测试程序的各项功能,确定他们能够满足需求正常运行。例如我们的程序必须完成每次作业迭代中新增的方法,如果有方法漏写了,那么我们的程序就不满足指导书的需求。我们必须使得我们的程序可以对指导书中所有的指令做出反应以满足最基本的功能需求。

集成测试: 在单元测试完成后,把一些单元集成在一起测试。例如我们有会改变某容器的方法A和方法B以及不改变容器的查询方法C。我们可以将他们集合起来一起测试,看看会不会产生预期之外的结果(例如期望改变的没有改变,不期望改变的被改变了)。

压力测试: 确定程序在高负载下的稳定性和性能。在我看来强测就是压力测试,即给程序输入大量的包括极端情况的数据来测试程序。在极端的数据下,程序的某个方法实现的最坏时间复杂度的差别造成的性能差异就会显现,我们可以以此来确定效率不够高的方法的位置并进行修改。例如hw10中的qtvs。而稳定性指的应该是程序在不同的数据下应该保持正确,性能也不应有太大差别。例如第二单元电梯作业中多线程”概率“出现的问题可以说明该程序不够稳定。

回归测试: 在程序修改后进行,以保证在模块A正确,模块B分别正确的情况下,在已有模块A的程序中引入模块B后,程序的运行仍然正确。

测试工具

使用了junit进行单元测试以及通过数据生成器生成数据并与同学对拍来进行测试。

数据构造有何策略

数据构造要注意构造数据的有效性以及覆盖率。有效性指数据不能纯随机生成,看起来很多然而绝大部分是需要异常处理的数据。因此实际上可以先生成ap,at等基本的指令,把添加过的人和Tag的ID放入peopleIdSet和TagIdSet,随后再生成ar,mr,att等指令时可以查看是否添加了这个人或者Tag,避免生成无效的数据。覆盖率指的是生成的数据要覆盖JML所描述的所有情况以及可能的错误。例如hw10中addPersonToTag方法中的getPerson(personId2).getTag(tagId).persons.length<=1111的约束,若注意到了这点便会自然地想到由此可能带来的时间复杂度的问题。而这需要我们生成相应的极端数据来检测。此外还有hw11的”发红包“方法

@          (\exists int i; i == ((RedEnvelopeMessage)\old(getMessage(id))).getMoney()/\old(getMessage(id)).getTag().getSize();
@           \old(getMessage(id)).getPerson1().getMoney() ==
@           \old(getMessage(id).getPerson1().getMoney()) - i*\old(getMessage(id)).getTag().getSize() &&
@           (\forall Person p; \old(getMessage(id)).getTag().hasPerson(p);
@           p.getMoney() == \old(p.getMoney()) + i));

这里要求的对发红包者的钱数改变为:(MessageMoney/群组人数)*群组人数。我曾经没有严格按照JML而是按照自己的理解使得:Person1.money-=MessageMoney。但是,若MessageMoney与群组人数不可整除的时候,就会发现该代码与JML的要求是不符的。构造一个 MessageMoney与群组人数不可整除的数据并与同学对拍便会发现问题。

单元架构设计

第三单元架构大致如下:

通过MyNetwork管理MyPerson以实现一个社交网络的模拟。第二次作业新增需实现Tag接口的MyTag类以用来实现类似“群组”的功能。第三次作业新增了一些Message类,包括MyMessage(普通消息),MyNoticeMessage(通知),MyRedEnvelopMessage(红包),MyEmojiMessage(表情)。此外,MyCounter类满足对异常的计数,对需要实现的每种异常存储其发生的总数以及一个id->count的hashMap。

图模型构建和维护策略

在hw9中,query_circle方法需要查找Person1是否可以通过某些acquaintance找到Person2。(例如A认识B,B认识C,C认识D,那么A和D就在同一个社交圈里)。显然如果仅仅遍历查询的话有超时的风险,参照往年学长学姐博客后发现使用并查集可以优化这个问题。这样一来,A,B是否处于同一社交圈的问题就转换为A,B是否处于同一个根节点下。

queryBlockSum求连通子图个数可以通过在并查集中维护一个count的方法实现。每次addPerson的时候就count++,每次merge的时候就count--

queryTripleSum同样是采取维护的方法。当添加/删除两人间的关系的时候,查找这两人有多少个“共同认识的人”并对tripleSum进行增减。

此外,在删除关系的时候对于并查集我选择了重建,对应的这需要我们选择重建并查集的时机。考虑需求后发现只当查询query_circle的时候,我们才需要使用到并查集的性质,因此应当在query_circle时重建而不是在addRelation或者modifyRelation的时候重建。

在hw10中,对于qtvs我们应该选择维护。每当ar或者mr的时候我们对于person1和person2应查找他们是否在同一个tag中,若是的话则相应地维护这个tag的valueSum。这样的维护要求我们增加一个全局的tags容器。此外,因为全局tagId可能不唯一,所以不能用id->Tag的HashMap,我选择了HashSet存储Tag对象。

在hw11中,JML新增了一些需求。其中EmojiIdList和EmojiHeatList实际上是描述了id->heat的对应关系,而并不是规定我们必须使用数组或者ArrayList,LinkedList等容器实现它。使用一个HashMap表示id->heat的对应或许对后续的编写有所帮助。

bug修复

hw9尽管在重测后原本CTLE的点通过了,但是暴露出了并查集重建时机的问题。因此需要查询时重建而非关系修改时重建。

hw10在尝试维护BestAcquaintance的时候,对于Integer value的比较既没有使用<也没有使用compareTo而是相减后比较,造成了错误。在查询qtvs的时候没有维护而是直接采用了二重循环造成超时。在qcs的时候我非常愚蠢地写了个对全局persons的二重循环导致超时。修复时我选择了维护couplesum:在ar,mr前后需要判断此时person1和person2的情况。先"解除"他们可能拥有的couple关系,ar,mr后再判断一下是否要加回来。

    public void beforeModify(MyPerson person1, MyPerson person2) {
        if (person1.hasCouple() && person1.getBestAcquaintance() == person2.getId()
                &&
                person2.hasCouple() && person2.getBestAcquaintance() == person1.getId()
        ) {
            network.addGlobalCoupleSum(1);
        }
        if (person1.hasCouple()) {
            network.addGlobalCoupleSum(-1);
        }
        if (person2.hasCouple()) {
            network.addGlobalCoupleSum(-1);
        }
    }
​
    public void afterModify(MyPerson person1, MyPerson person2) {
        if (person1.hasCouple()) {
            network.addGlobalCoupleSum(1);
        }
        if (person2.hasCouple()) {
            network.addGlobalCoupleSum(1);
        }
        if (person1.hasCouple() && person1.getBestAcquaintance() == person2.getId() &&
                person2.hasCouple() && person2.getBestAcquaintance() == person1.getId()
        ) {
            network.addGlobalCoupleSum(-1);
        }
    }

后来发现,实际上只需要对personi以及personi的acquaintance遍历就不会超时。

关于规格与实现分离,我认为JML规格只是通过类似伪代码的形式提供对问题的描述, 它并没有限定我们要使用什么容器或者使用什么算法。因此我们需要基于规格,思考如何提高方法的效率避免出现效率问题。规格为方法提供了比较精确的描述,这对我们的测试以及方法也是有帮助的,及时基于规格编写测试程序可以避免我们在迭代的过程中对方法的理解出现误差。

Junit检测代码实现和规格的一致性

Junit测试代码首先需要按照JML朴素实现并且保证正确性。我实现的测试代码与JML基本保持一致。此外还需要通过assert来保证规格中ensure的实现。通过官方Junit测试的思路是将自己实现的方法与课程组提供的方法运行结果比对,这首先需要我们在测试代码中新建两个network并且给他们输入相同的数据。对于生成数据,我前两次作业使用了随机生成合法数据的方式,最后一次作业直接手动添加数据。为了通过所有测试点我们需要注意:

  • 生成的测试数据要覆盖所有情况,例如deleteColdEmoji是否删除了冷门表情。

  • 对于ensure需要逐一检查

  • pure方法在执行前后不应使得容器发生变化。

学习体会

本单元主题是JML的规格化设计,十分感谢助教ggjj提供的手册和推送让我能够较为快速地对JML语言有一个基本的认识和理解。如果没有重要的前人的经验我可能仅仅在了解JML的过程中就会绕了很多弯路。本单元锻炼了我阅读JML的能力,因为我需要把JML翻译成我能理解的自然语言后,才能根据自然语言编写代码。此外,我还深刻体会到了规格与实现分离的思想以及测试的重要性,同时学习了一些图论算法,算是很有收获。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值