OO第三单元总结:开展软件测试

OO第三单元总结:开展软件测试

一、测试思路

本单元采用测试的方法为白盒静态测试+黑盒动态测试

本单元并没有采取单元测试,主要原因为一方面个人认为在本单元中更适合对集成模块开展测试(单元测试并不适合测时间复杂度以及数据流),另一方面在互测阶段单元测试也会带来不必要的时间开销(指为每份代码配置JUnit)

白盒静态测试

白盒静态测试在本次作业主要在个人代码完成后使用,根据JML规格检查自己的代码实现是否正确,并且计算时间复杂度是否符合需要。例如,对无数据限制的指令至多 \(O(n)\) ,对有数据限制的指令至多 \(O(nlogn)\)

黑盒动态测试

本单元作业的黑盒测试主要可分为两个部分:

(一)对所有指令进行通过性测试

首先,由于异常指令实际无意义,需要避免引起异常的指令过多的情况。将输入指令划分有效指令异常指令。这里,将有效指令定义为符合规格中normal_behavior条件的指令,同样,将异常指令定义为符合规格中exceptional_behavior条件的指令。划分后通过控制异常指令数,可以避免异常指令过多的情况。

要实现有效指令异常指令的划分,在数据生成程序中实际模拟了每条指令产生的效果(即生成程序中存储了对应信息),这样同时可以保证数据生成时就得知某条指令为有效指令异常指令

为保证数据覆盖率,每次生成数据后均使用Idea中的“使用覆盖率运行”,如果不能覆盖所有代码行,说明对情况的讨论仍有欠缺,需要继续修改数据生成器。

最终,使用Idea测试三次作业数据生成器覆盖率,均可以覆盖所有代码行。(评价指标)

(注:没有覆盖的方法为equals方法或没有调用的无效方法)

其他处理:

  • 由于覆盖率仅仅体现了代码行覆盖(语句覆盖),并不能体现分支覆盖(路径覆盖),故只能对每种可能的分支路径进行讨论。在本次作业中由于执行顺序不同导致结果极大不同的情况,只有am/ar/sm指令序列(即addMessage时两人并不相连,如果直接sendMessage触发异常,如果先addRelation后sendMessage则可正常发出信息),因此在指令生成时单独考虑了这种情况。
  • 生产代码的数量为(指令类型数)* 10000,这样可以保证每条指令均执行约10000次(除限制数量的指令,10000为指导书中的总指令数),如果运行过慢可以直接认为某条指令的时间复杂度有误,亦可保证数据强度。
  • 为了保证id有效,单独实现了id的管理类(原理和OS实验中asid相关操作相似)

(二)对部分指令进行通过性测试

一方面某些指令如果仅仅按照JML规格显然时间复杂度有误,另一方面,某些指令也需要构造特殊的数据进行测试,故在(一)的基础上对部分指令进行了单独测试。(例如qgvs、qgav、cn、am+sm、qlc、sim等等,具体指令可见数据生成器类图)

实现比较简单,直接继承(一)中的类并配置对应的生成方法即可。此外,所有数据生成器均实现了统一接口,这样只需根据命令行传入的参数即可实现不同的数据生成方案。

反思:测试程序迭代过程中出现了1个问题—由于 BaseTest 类实现了几乎所有的数据生成方法,导致该类过大,可读性和可维护性不强,应当拆分为子生成方法更优(例如单独配置和生成人/组/网络相关指令单独的类)

二、图模型构建和维护策略

本次作业涉及到的最主要的图论算法有最小生成树(qlc)、单源最短路径(sim)。在本次作业中为了维护图模型主要用到的数据结构为并查集

1)图的储存

由于至多有2500个节点且大多数时间复杂度最差的情况下图是稀疏图(例如2500个人7500条关系),个人采用类似邻接表的方式,在结点(Person)中储存边的情况。

2)均摊复杂度策略的应用(加点、加边等)

在本次作业中,大多数指令(除qlc/sim等)要求最差时间复杂度为 \(O(n)\),而根据规格不难看出如果不考虑动态维护某个结果/结构,很容易出现复杂度达到 \(O(n^2)\) 的情况,这就需要我们将这个复杂度高的操作的结果,在一些复杂度低的操作中进行维护,例如:

qgvs:在加边(ar)、向组里加人(atg)、从组里删人的操作中(dfg),维护组的ValueSum。此时,ar/atg/dfg 的复杂度为 \(O(n)\)qgvs的复杂度为 \(O(1)\)

qlc:最小生成树。虽然该操作要求的复杂度可以直接跑prim/kruskal,但也可以通过在加边时使用线段树进行维护(均摊能达到 \(O(logn)\)但在本次作业中显然没有必要这么做)。

3)图论算法

最小生成树(qlc):使用prim/kruskal均可(注意堆优化),个人使用的是prim算法。

单源最短路径(sim):使用堆优化的dijistra算法

4)其他数据结构

并查集:主要用于查询图的联通。使用路径压缩(非递归写法,小心爆栈),插入/查询复杂度 \(O(logn)\)

Pair:由于Java里没有原生的Pair,而在最小生成树、单源最短路中使用Pair进行操作比较方便,所以就写了一个。

三、各次作业Hack情况

个人三次作业在中测、强测、互测均未出现Bug。在互测中Hack到的bug基本上都是一味按照JML实现导致的时间复杂度问题。

第一次作业

1次有效hack:qbs超时(实现了并查集但仍然坚定了按照JML规格进行双重循环)

第二次作业

6次有效hack:qgav超时(2次,在循环中调用了复杂度为O(n)的 getAgeMean)、qgvs超时(3次,按照JML规格进行双重循环)、qrm返回的数量有误(1次)

个人课下测试时在qgvs中未考虑到arvalueSum的影响。

第三次作业

1次有效hack:qgvs超时(错误同第二次作业)。本次作业中测较强。

四、扩展Network

Network.java 接口扩展

public interface Network {
    /* ……其他方法…… */
  
    //@ public instance model non_null int[] advertisements;
    //@ public instance model non_null int[] advertisers;
    
    /*@ public normal_behavior
      @ requires contains(producerId) && (getPerson(producerId) instanceof Producer) &&
      @          !(getPerson(producerId).hasProduct(productId));
      @ assignable getPerson(producerId).products;
      @ ensures getPerson(producerId).products.length ==
      @             \old(getPerson(producerId).products.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(producerId).products.length);
      @          (\exists int j; 0 <= j && j < getPerson(producerId).products.length;
      @           getPerson(producerId).products[j] == (\old(getPerson(producerId).products[i]))));
      @ ensures (\exists int i; 0 <= i && i < getPerson(producerId).products.length;
      @             getPerson(producerId).products[i] == productId);
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) (!contains(producerId) ||
      @                                        !(getPerson(producerId) instanceof Producer));
      @ signals (EqualProductIdException e) !(!contains(producerId) ||
      @                                        !(getPerson(producerId) instanceof Producer)) &&
      @                                         (getPerson(producerId).hasProduct(productId));
      @*/
    public void addProduct(int producerId, int productId) throws
        PersonIdNotFoundException, EqualProductIdException;
    
    /*@ public normal_behavior
      @ requires (contains(advertiserId) && (getPerson(advertiserId) instanceof Advertiser)) &&
      @          (contains(producerId)   && (getPerson(producerId) instanceof Producer)) &&
      @          !(getPerson(advertiserId).hasAdvertisement(advertisementId)) &&
      @          (getPerson(producerId).hasProduct(productId));
      @ assignable getPerson(advertiserId).advertisements, getPerson(advertiserId).producers,
      @            getPerson(advertiserId).products, advertisements, advertisers;
      @ ensures getPerson(advertiserId).advertisements.length ==
      @             \old(getPerson(advertiserId).advertisements.length) + 1;
      @ ensures getPerson(getPerson(advertiserId).producers.length ==
      @             \old(getPerson(advertiserId).producers.length) + 1;
      @ ensures getPerson(advertiserId).products.length ==
      @             \old(getPerson(advertiserId).products.length) + 1;
      @ ensures advertisements.length == \old(advertisements.length) + 1;
      @ ensures advertisers.length == \old(advertisers.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(advertiserId).advertisements.length);
      @          getPerson(advertiserId).advertisements[i] == (\old(getPerson(advertiserId).advertisements[i])));
      @ ensures getPerson(advertiserId).advertisements[getPerson(advertiserId).advertisements.length-1] == advertisementId;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(advertiserId).producers.length);
      @          getPerson(advertiserId).producers[i] == (\old(getPerson(advertiserId).producers[i])));
      @ ensures getPerson(advertiserId).producers[getPerson(advertiserId).producers.length-1] == producerId;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(advertiserId).products.length);
      @          getPerson(advertiserId).products[i] == (\old(getPerson(advertiserId).products[i])));
      @ ensures getPerson(advertiserId).products[getPerson(advertiserId).products.length-1] == productId;
      @ ensures (\forall int i; 0 <= i && i < \old(advertisements.length);
      @          advertisements[i] == (\old(advertisements[i])));
      @ ensures advertisements[advertisements.length-1] == advertisementId;
      @ ensures (\forall int i; 0 <= i && i < \old(advertisements.length);
      @          advertisers[i] == (\old(advertisers[i])));
      @ ensures advertisers[advertisers.length-1] == advertiserId;
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e)  (!contains(advertiserId) ||
      @                                        !(getPerson(advertiserId) instanceof Advertiser)) ||
      @                                        (!contains(producerId) ||
      @                                        !(getPerson(producerId) instanceof Producer));
      @ signals (EqualAdvertisementIdException e) !(!contains(advertiserId) ||
      @                                        !(getPerson(advertiserId) instanceof Advertiser)) ||
      @                                        (!contains(producerId) ||
      @                                        !(getPerson(producerId) instanceof Producer)) &&
      @                                        (getPerson(advertiserId).hasAdvertisement(advertisementId));
      @ signals (ProductIdNotFoundException e) !(!contains(advertiserId) ||
      @                                        !(getPerson(advertiserId) instanceof Advertiser)) ||
      @                                        (!contains(producerId) ||
      @                                        !(getPerson(producerId) instanceof Producer)) &&
      @                                        !(getPerson(advertiserId).hasAdvertisement(advertisementId)) &&
      @                                        !(getPerson(producerId).hasProduct(productId));
      @*/
    public void addAdvertisement(int advertiserId, int advertisementId, int producerId,
                                 int productId) throws PersonIdNotFoundException,
        EqualAdvertisementIdException, ProductIdNotFoundException;
    
    /*@ public normal_behavior
      @ requires contains(customerId) && (getPerson(customerId) instanceof Customer) &&
      @          containsAdvertisement(advertisementId);
      @ assignable getPerson(customerId).customers, getPerson(customerId).purchaseMessages;
      @ ensures getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers.length ==
      @             \old(getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers.length);
      @          getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers[i+1] ==
      @             (\old(getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers[i])));
      @ ensures getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers[0] == customerId;
      @ ensures getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).purchaseMessages.length ==
      @             \old(getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).purchaseMessages.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).customers.length);
      @          getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).purchaseMessages[i+1] ==
      @             (\old(getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).purchaseMessages[i])));
      @ ensures getPerson(getAdvertiser(advertisementId).getProducer(advertisementId)).purchaseMessages[0] ==
      @             getAdvertiser(advertisementId).getProduct(advertisementId);
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) (!contains(customerId) ||
      @                                        !(getPerson(customerId) instanceof Customer));
      @ signals (AdvertisementIdNotFoundException e) !(!contains(customerId) ||
      @                                        !(getPerson(customerId) instanceof Customer)) &&
      @                                         !(containsAdvertisement(advertisementId));
      @*/
    public void sendPurchaseMessage(int customerId, int advertisementId)
        throws PersonIdNotFoundException, AdvertisementIdNotFoundException;
    
    public /* pure */ boolean containsAdvertisement(int advertisementId);
    
    public /* pure */ int getAdvertiser(int advertisementId);
}

备注:这里处理购买信息暂定为给Producer类发送购买信息(包含顾客id和商品id)。

产品生产商类 Producer.java

public interface Producer extends Person {
    //@ public instance model non_null int[] products;
    //@ public instance model non_null int[] customers;
    //@ public instance model non_null int[] purchaseMessages;
    
    public void addProduct(int productId);
    
    public void hasProduct(int productId);
}

广告商类 Advertiser.java

public interface Advertiser extends Person {
    //@ public instance model non_null int[] advertisements;
    //@ public instance model non_null int[] producers;
    //@ public instance model non_null int[] products;
    
    public void addAdvertisement(int producerId, int productId);
    
    public void sendAdvertisement(int productId);
    
    public /* pure */ void hasAdvertisement(int productId);
    
    public /* pure */ int getProducer(int advertisementId);
    
    public /* pure */ int getProduct(int advertisementId);
}

顾客类 Customer.java

public interface Customer extends Person {
    public void purchase(int advertisementId);
}

(异常部分略)

小结

这部分虽然看上去实现了很多方法,但本质上主要还是以建立一一映射关系为主。而映射关系的JML规格写法也比较套路:①长度加1 ②原来元素映射关系不变 ③建立新的映射关系。

五、心得体会

在JML学习过程中,更多的感觉JML是对方法调用者友好,而方法实现者并不太适合参考JML来实现代码。这是因为一方面,JML只能保证方法对正确性,无法保证方法的性能(甚至在性能方面可能具有一定误导);另一方面,在我们一但采用在其他方法维护某个结构,那么JML和个人实现的代码就不能很好的匹配了。

不过,JML规格很适合进行测试程序的编写,基本只需要考虑输入和输出即可。

另一方面,JML规格的写法也具有多样性,例如在最小生成树(qlc)中,课程组提供的规格并没有直接指明我们需要找到的子图具有树的特点,因此在阅读代码时读了很久才明白意思。这里我个人认为如果适当的用一些树的性质(例如边和节点的关系)来写规格可能可读性更强一些。同时可能也是因为JML规格写法的多样性,没有很好的办法去对规格进行测试,也是比较遗憾的一点。

posted @ 2022-06-04 12:45  Blore-lzn  阅读(23)  评论(1编辑  收藏  举报