BUAA_OO_Unit3 总结

BUAA_2022_Unit3总结

一、如何利用JML规格来准备测试数据

根据是否触发异常来准备测试数据

JML规格一般包含normal情况的输入数据和会触发异常的数据,构造和输入normal情况的数据要保证不会触发异常,而构造和输入异常数据则要保证异常的顺利触发。

@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < people.length; people[i].equals(person));
@ assignable people;
@ ensures people.length == \old(people.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(people.length);
@          (\exists int j; 0 <= j && j < people.length; people[j] == (\old(people[i]))));
@ ensures (\exists int i; 0 <= i && i < people.length; people[i] == person);
@ also
@ public exceptional_behavior
@ signals (EqualPersonIdException e) (\exists int i; 0 <= i && i < people.length;
@                                     people[i].equals(person));

根据normal情况的边界来准备测试数据

JML规格中的正常情况根据边界条件分为多种,如本单元中Network类的addToGroup方法,当人数超过1111时,虽然不会触发异常,但也无法顺利添加。

@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < groups.length; groups[i].getId() == id2) &&
@           (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1) &&
@            getGroup(id2).hasPerson(getPerson(id1)) == false &&
@             getGroup(id2).people.length < 1111;
@ assignable getGroup(id2).people;
@ ensures (\forall Person i; \old(getGroup(id2).hasPerson(i));
@          getGroup(id2).hasPerson(i));
@ ensures \old(getGroup(id2).people.length) == getGroup(id2).people.length - 1;
@ ensures getGroup(id2).hasPerson(getPerson(id1));
@ also
@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < groups.length; groups[i].getId() == id2) &&
@           (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1) &&
@            getGroup(id2).hasPerson(getPerson(id1)) == false && 
@             getGroup(id2).people.length >= 1111;
@ assignable \nothing;

根据JML规格的前提构造覆盖所有真值情况的数据

JML规格的前提字段(requires)一般包含一条或多条真值语句,构造数据应尽可能覆盖所有真值组合。

@ requires (\exists int i; 0 <= i && i < groups.length; groups[i].getId() == id2) &&
@           (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1) &&
@            getGroup(id2).hasPerson(getPerson(id1)) == true;

白盒测试

除了构造数据进行黑盒测试,白盒测试则是将实现的方法与JML规格进行细致比对,从而找出错误。(特别是在对拍后有分歧时)

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

为了满足基本的JML规格要求,图模型只需要:

  • 为每个Person创建一个对象
  • 将Relation和对应value保存在对应Person对象里
  • 为每个Group创建一个对象
  • 将Group中的Person对应的对象存在Group对象中
  • 按照JML规格说明设置各个对象的属性

但为了顺利通过测试,我还做了如下维护:

  • 对person的id和对应对象进行映射,从而不必遍历查找
  • 对Relation按value进行排序,从而更好的实现Dijkstra等算法
  • 对Person使用并查集,降低isCircle方法的复杂度

三、分析代码实现出现的问题和修复情况

作业一:

若queryBlockSum方法按照JML规格采用双循环实现,复杂度最高为O(n*n),但可能由于java的各种封装的类的实现原因,在实际使用中时间大于O(n*n),于是最终被hack。

修复时:参考了同组一个人的代码,发现queryBlockSum()的本质是图中分支的总数,只要维护一个初始值为0的变量,每次addPerson时增一,addRelation且不增加闭环时减一,即为所求总数。

作业二:

queryLeastConnection方法实际为求最小生成树的问题,可以按评论区里的用prim算法加堆优化或者kruskal算法加并查集来取得最佳效果。我选择了后者,且在测试中没有出现问题。

作业三:

sendIndirectMessage方法为求最短路径的算法,使用dijkstra算法的时间复杂度为O(n*n),我没有吸取第一次作业的教训去很好地优化他,结果测试又挂了。一种比较简单粗暴的方法是在每次使用dijkstra算法时会计算出除目标两点外的其他最短路径,将他们统统存在Map里,即可达到很好地时间复杂度(但空间复杂度高)。另一种优化方法是对路径进行堆排序。

四、请针对以下内容对Network进行扩展,并给出相应的JML规格

假设出现了几种不同的Person

  • Advertiser:持续向外发送产品广告
  • Producer:产品生产商,通过Advertiser来销售产品
  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
  • Person:吃瓜群众,不发广告,不买东西,不卖东西

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)

public interface Network {

    /*@ public instance model non_null Producer[] producers;
      @ public instance model non_null Customer[] customers;
      @ public instance model non_null Advertiser[] advertiser;
      @ public instance model non_null Advert[] adverts;
      @*/


    /*@ public normal_behavior
      @ requires !(\exists int i; 0 <= i && i < adverts.length; adverts[i].equals(advert));
      @ assignable adverts;
      @ ensures adverts.length == \old(adverts.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(adverts.length);
      @          (\exists int j; 0 <= j && j < adverts.length; adverts[j].equals(\old(adverts[i]))));
      @ ensures (\exists int i; 0 <= i && i < adverts.length; adverts[i].equals(advert));
      @*/
    //由Advertiser调用,投放广告.
    public void sendAdvert(Advert advert);

    /*@ public normal_behavior
      @ requires advertisers.contains(advertiser);
      @ assignable advertiser.getAdvert();
      @ ensures \old(advertiser.getAdvert().size()) == advertiser.getAdvert().size() - 1;
      @ ensures (\forall int i; 0 <= i && i < \old(advertiser.getAdvert().size());
      @         (\exists int j; 0 <= j && j < advertiser.getAdvert().size();
      @             advertiser.getAdvert().get(j) == \old(advertiser.getAdvert()).get(i)));
      @ ensures (\exists int i; 0 <= i && i < advertiser.getAdvert().size();
      @             advertiser.getAdvert().get(i) == advert)
      @*/
    //由Producer调用,传给Advertiser,让Advertiser投放广告.
    public void sendAdvert(Advertiser advertiser, Advert advert);

    /*@ public normal_behavior
      @ requires customers.contains(customer);
      @ assignable customer.getBag();
      @ ensures \old(customer.getBag().size()) == customer.getBag().size() - 1;
      @ ensures (\forall int i; 0 <= i && i < \old(customer.getBag().size());
      @         (\exists int j; 0 <= j && j < customer.getBag().size();
      @             customer.getBag().get(j) == \old(customer.getBag()).get(i)));
      @ ensures (\exists int i; 0 <= i && i < customer.getBag().size();
      @             customer.getBag().get(i) == product)
      @*/
    //由Producer调用,完成购买信息。
    public void sendProduct(Customer customer, Product product);

    /*@ public normal_behavior
      @ requires advertisers.contains(advertiser);
      @ assignable advertiser.getOffer();
      @ ensures \old(advertiser.getOffer().size()) == advertiser.getOffer().size() - 1;
      @ ensures (\forall int i; 0 <= i && i < \old(advertiser.getOffer().size());
      @         (\exists int j; 0 <= j && j < advertiser.getOffer().size();
      @             advertiser.getOffer().get(j) == \old(advertiser.getOffer()).get(i)));
      @ ensures (\exists int i; 0 <= i && i < advertiser.getOffer().size();
      @             advertiser.getOffer().get(i) == offer)
      @*/
    //由Customer调用,发送给Advertiser购买消息.
    public void sendOffer(Advertiser advertiser, Offer offer);

    /*@ public normal_behavior
      @ requires producers.contains(producer);
      @ assignable producer.getOffer();
      @ ensures \old(producer.getOffer().size()) == producer.getOffer().size() - 1;
      @ ensures (\forall int i; 0 <= i && i < \old(producer.getOffer().size());
      @         (\exists int j; 0 <= j && j < producer.getOffer().size();
      @             advertiser.getOffer().get(j) == \old(producer.getOffer()).get(i)));
      @ ensures (\exists int i; 0 <= i && i < producer.getOffer().size();
      @             producer.getOffer().get(i) == offer)
      @*/
    //由Advertiser调用,发送给Producer购买信息.
    public void sendOffer(Producer producer, Offer offer);
}

五、感想

​ 任务相对轻松的一个单元,但JML规格设计思想以及规范,Junit单元化测试,都是重点难点。

​ 但纸上得来终觉浅,没有一个庞大的工程来做印证,我始终无法切身体会到这份重要。

​ 另外由于Junit的使用比较麻烦,我只在第一次作业时进行了尝试,后来还是改用手撸评测机进行对拍测试,因为比较方便,看到别人能对JML进行几乎100%覆盖率的测试,自愧不如,也是本单元最大的遗憾了。

posted @ 2022-06-05 11:53  Disorientation  阅读(22)  评论(0编辑  收藏  举报