哈工大软件构造Lab2(2022)

(防扒小助手)

本人CSDN博客:

https://blog.csdn.net/m0_61753302https://blog.csdn.net/m0_61753302

本人博客园博客(同步CSDN):

https://www.cnblogs.com/kalesky/https://www.cnblogs.com/kalesky/

如果对你有用的话欢迎点赞关注哟!

 

目录

 

1、实验目标概述

2、实验环境配置

3、实验过程

3.1 Poetic Walks

3.1.1 Get the code and prepare Git repository

3.1.2 Problem 1: Test Graph

3.1.3 Problem 2: Implement Graph

3.1.4 Problem 3: Implement generic Graph

3.1.5 Problem 4: Poetic walks

​​​​​​​3.1.6  Before you’re done

3.2 ​​​​​​​ Re-implement the Social Network in Lab1

​​​​​​​3.2.1 FriendshipGraph类

​​​​​​​3.2.2  Person类

​​​​​​​3.2.3  客户端main()

3.2.4 测试用例 

3.2.5 提交至Git仓库

4 实验进度记录

5 实验过程中遇到的困难与解决途径

6 实验过程中收获的经验、教训、感想

6.1 实验过程中收获的经验和教训

6.2 针对以下方面的感受


 

1、实验目标概述

本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象

编程(OOP)技术实现 ADT。具体来说:

  1.  针对给定的应用问题,从问题描述中识别所需的 ADT
  2.  设计 ADT 规约(pre-conditionpost-condition)并评估规约的质量;
  3.  根据 ADT 的规约设计测试用例;
  4.  ADT 的泛型化;
  5.  根据规约设计 ADT 的多种不同的实现;针对每种实现,设计其表示 (representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
  6.  使用 OOP 实现 ADT,并判定表示不变性是否违反、各实现是否存在表示泄露(rep exposure)
  7.  测试 ADT 的实现并评估测试的覆盖度;
  8.  使用 ADT 及其实现,为应用问题开发程序;
  9.  在测试代码中,能够写出 testing strategy 并据此设计测试用例。

 

2、实验环境配置

(1)安装测试用例代码覆盖度插件

经过网上查阅资料了解到,在IDEA中已经集成了代码覆盖度插件JaCoCo,切换方式如下:

点击Edit Configuration,弹出如下窗口:

 在Code Coverage一栏点击Modify勾选红框中的选项,则会弹出切换代码覆盖度工具的选项:


 通过咨询软件构造课程老师与助教老师,了解到使用IDEA自带的代码覆盖度工具即可,不需要切换到JaCoCo,因此下文代码覆盖度测试均使用IDEA自带的coverage runner进行测试。

 

2、GitHub Lab2仓库的URL地址(Lab2-学号)

略。 

 

 3、实验过程

3.1 Poetic Walks

该任务主要是通过实现一个图的模块来练习ADT的规约设计和ADT的不同实现。

(1)完善Graph接口类,并运用泛型的思想,将String拓展为泛型L类;

(2)实现Graph接口类:以边和点两种方式实现接口;

(3)利用实现的Graph类,应用图的思想,实现GraphPoet类。如果输入的文本的两个单词之间存在桥接词,则插入该桥接词;若存在多个单一桥接词,则选取边权重较大者。

 

3.1.1 Get the code and prepare Git repository

从要求文件中对应网址下载得到实验代码,建立好project,进入目录,打开Git bush

依次输入:

git init

git remote add origin git@github.com:ComputerScienceHIT/HIT-Lab2-120L022408.git

git pull origin master

git add .

git commit -m “init”

git push origin master

 

 

 

3.1.2 Problem 1: Test Graph <String>

测试Graph的静态方法。

为了方便测试Graph的多种实现,在 GraphInstanceTest 中测试了实例方法。

编写测试用例主要利用等价类划分的思想进行测试,测试策略如下:


 分别编写覆盖以上条件的测试用例。

 

运行GraphStaticTest得到测试结果如下:

 

3.1.3 Problem 2: Implement Graph <String>

3.1.3.1 Implement ConcreteEdgesGraph

(1)Edge类实现

定义两个private String类型的变量source和target存放每个边的起止点

定义一个private int类型的变量weight保存这条边的权重(长度)

private final String source, target;

private final int weight;

关于AF,RI和rep exposure:


① 构造器 constructor

构造方法,使用上述三个数据域声明一个新的边

public Edge(L source_new, L target_new, int weight_new)
{
    this.source = source_new;
    this.target = target_new;
    this.weight = weight_new;
    checkRep();
}

② 检查表示不变量 checkRep

检查表示不变量,其中source和target必须非空,weight必须大于0

public void checkRep()
{
    assert source != null;
    assert target != null;
    assert weight > 0;
}

③ get方法

get_Source:返回source域

get_Target:返回target域

get_Weight:返回weight域

④ toString方法

返回一个字符串表明这条边是从哪个source到哪个target,其weight是多少。

public String toString()
{
    return source.toString() + "->" + target.toString() + "\t权重为" + weight + '\n';
}

 

(2)ConcreteEdgesGraph实现

vertices和edges分别记录当前graph所含有的点和边

private final Set<String> vertices = new HashSet<>();
private final List<Edge<String>> edges = new ArrayList<>();

关于AF,RI和rep exposure:

 


 ① add方法

public boolean add(String vertex)

如果顶点不为空,添加一个顶点。如果在vertices的Set集合中成功添加了vertex,则返回true。

② Set方法

public int set(String source, String target, int weight)

输入source,target,weight,确定一条有向边。

具体做法:如weight!=0,移去可能已经存在的相同起始点的边,然后加入新的边,如weight=0,寻找可能已经存在的相同起始点的边,删去。

③ remove方法

public boolean remove(String vertex)

从vertices中删去给定的vertex点,遍历edges,寻找该vertex是否为某条边的起点或者终点,删去相应的边。在使用迭代器遍历时要使用iterator.remove方法保证安全性。

④ vertices方法

public Set<String> vertices()

返回vertices集合。注意做到safety from rep exposure ,使用Collections.unmodifiableSet()方法。


⑤ sources方法

public Map<String, Integer> sources(String target)

参数:target。根据传入的target参数寻找以targe为终点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Target()和传入参数target相等,则将该边的source和weight存入map中。

⑥ targets方法

public Map<String, Integer> targets(String source)

参数:source。根据传入的source参数寻找以source为起点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Source()和传入参数source相等,则将该边的target和weight存入map中。

⑦ 检查表示不变量 checkRep

思路:n个点,最多构成n*(n-1)条有向边,因此存在这种不可变的数学关系

⑧ toString方法

对每条边调用toString方法,整合起来。

public String toString()
{
    String s = "";
    for (Edge<L> e : edges)
    {
        s = s + e.toString();
    }
    return s;
}

 

(3)ConcreteEdgesGraphTest测试

JUnit测试结果如下:

 

 试覆盖率:

 

 

 3.1.3.2 Implement ConcreteVerticesGraph

(1)Vertex类实现

定义两个private String类型的变量source和target存放每个边的起止点

定义一个private int类型的变量weight保存这条边的权重(长度)

private final String name;
private final Map<String, Integer> sources;
private final Map<String, Integer> targets;

关于AF,RI和rep exposure:


① 构造器 constructor

构造方法,传入参数name创建新的点。

public Vertex(String name)
{
    this.name = name;
    sources = new HashMap<>();
    targets = new HashMap<>();
}

② 检查表示不变量 checkRep

检查表示不变性,各边weight的值应该永远大于0。

 

③ get方法

get_Name:返回source域

get_Sources:返回weight域

get_Target:返回Targets域

④ set方法

set_Target:为当前点新增一个target,

如果weight为0,删去当前点的target,成功返回删去target的weight,不存在返回0;如果weight不为0,为当前点新增一个target,长度为weight,如果该点已存在,返回旧的weight,否则返回0

 

set_Source:为当前点新增一个source,

如果weight为0,删去当前点的source,成功返回删去source的weight,不存在返回0;如果weight不为0,为当前点新增一个source,长度为weight,如果该点已存在,返回旧的weight,否则返回0

 

⑤ remove方法

remove_Source:删去当前点的指定source

public int remove_Source(String source)
{
    Integer weight = sources.remove(source);
    return weight == null ? 0 : weight;
}

 

remove_Target:删去当前点的指定target

public int remove_Target(String target)
{
    Integer weight = targets.remove(target);
    return weight == null ? 0 : weight;
}

 

⑥ toString方法

返回一个字符串表明这个顶点的信息

public String toString()
{
    return String.format("Vertex %s has %d sources and %d targets", this.get_Name().toString(), this.get_Sources().size(), this.get_Targets().size());
}

 

(2)ConcreteVerticesGraph实现

使用如下数据类型保存顶点的数据:

private final List<Vertex<String>> vertices = new ArrayList<>();

关于AF,RI和rep exposure:

 

① 检查表示不变量 checkRep

所有点的标识不能为空

private void checkRep()
{
    assert vertices != null;
}

② add方法

public boolean add(String vertex)

参数:vertex,判断vertices中无重复点就加入

 

③ Set方法

public int set(String source, String target, int weight)

参数:source, target, weight。先将可能不在vertices中的source点和target加入vertices。随后遍历vertices,找到source对它增加一个target,找到target为它增加一个source,并设置距离。

 

④ remove方法

public boolean remove(String vertex)

参数:vertex。遍历vertices,如果当前点是vertex,删去(使用iterator.remove方法),如果不是,检查它的source和target是否包含vertex,如果有删去。

 

⑤ vertices方法

遍历vertices,找到每个点对应的string,添加进set即可。使用防御性拷贝:

public Set<String> vertices()
{
    Set<String> set = new HashSet<>();
    for (Vertex<String> v : vertices)
    {
        set.add(v.get_Name());
    }
    return set;
}

 

⑥ sources方法

public Map<String, Integer> sources(String target)

参数:target。根据传入的target参数寻找以targe为终点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Target()和传入参数target相等,则返回target对应的源点图。

 

 

⑦ targets方法

public Map<String, Integer> targets(String source)

参数:source。根据传入的source参数寻找以source为起点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Source()和传入参数source相等,则返回source对应的目标点图。

 

⑧ toString方法

打印当前顶点图的顶点数量:

public String toString()
{
    return String.format("This graph has %d vertices", this.vertices.size());
}

 

(3)ConcreteEdgesGraphTest测试

JUnit测试结果如下:

 

测试覆盖率:

 

3.1.4 Problem 3: Implement generic Graph<L>

3.1.4.1 Make the implementations generic

将具体类的声明更改为:

public class ConcreteEdgesGraph<L> implements Graph<L> { ... }

 

class Edge<L> { ... }

public class ConcreteVerticesGraph<L> implements Graph<L> { ... }

 

class Vertex<L> { ... }

更新两个实现以支持任何类型的顶点标签,使用占位符L代替String。

充分利用IDEA的智能改错功能快速修改成泛型实现。

 

​​​​​​​3.1.4.2 Implement Graph.empty()

选择ConcreteEdgesGraph来实现Graph.empty()

测试全部通过:

   

3.1.5 Problem 4: Poetic walks

3.1.5.1 Test GraphPoet

关于测试策略:

 

具体测试:

 

​​​​​​​3.1.5.2 Implement GraphPoet

首先声明:

private final Graph<String> graph = new ConcreteEdgesGraph<String>();

关于AF,RI和rep exposure:

 

① 检查表示不变量 checkRep

所有点的标识不能为空

private void checkRep()
{
    assert graph != null;
}

 

② GraphPoet方法

参数:corpus文件路径。打开文件,读取文件输入,识别序列,构建图结构。

具体:利用BufferedReader.readLine方法读取全部输入后用string.split以空格划分,保存在数组中,随后每次取相邻元素,在图中新增边。

 

③ poem方法

参数:input。

利用相同方法分割输入字符串,声明一个StringBuilder保存返回结果。每次读取一个词,然后以当前词为source,下一个词为target,在graph中寻找符合此条件的边,记录权值,结束后选择权值最大的,利用StringBuilder. Append方法,将节点名字加入字符串。

 

④ toString方法

调用ConcreteEdgesGraph的toString方法,输出图结构

public String toString()
{
    return graph.toString();
}

 

​​​​​​​3.1.5.3 Graph poetry slam

语料库为泰戈尔经典名句集锦

输入输出如下:

 

 

​​​​​​​3.1.6 Before you’re done

通过Git提交当前版本到GitHub上你的Lab2仓库。

git add .

git commit -m "P1 Finished"

git push -u origin master

 

项目的目录结构树状示意图。

 

3.2 ​​​​​​​Re-implement the Social Network in Lab1

这个实验是基于在Poetic Walks中定义的Graph及其两种实现,重新实现Lab1中的 FriendshipGraph类。我们需要尽可能复用ConcreteEdgesGraph或 ConcreteVerticesGraph中已经实现的add()和set()方法,而不是从零开始。另外基于所选定的 ConcreteEdgesGraph 或 ConcreteVerticesGraph的rep来实现,而不能修改父类的rep。

 

​​​​​​​3.2.1 FriendshipGraph

(1)设计思路

继承ConcreteEdgesGraph<Person>,并在类中增加一些对非法情况的判断,例如顶点已经存在,边已经存在或找不到对应顶点等。

public class FriendshipGraph extends ConcreteEdgesGraph<Person>

 

(2)方法实现

构造一个ArrayList类型的变量person_list存储顶点列表

private final ArrayList<String> person_list = new ArrayList<String>();

① public boolean addVertex(Person people)

这个函数是为把参数添加到图中,作为图的一个顶点,直接调用父类的this.add()即可。调用过程中检查顶点列表中是否已出现参数对应的顶点,若重复则打印错误信息并返回false,成功添加顶点则返回true

 

② public boolean addEdge(Person people1, Person people2)

构建图的要素,在图中添加边。先调用 this.vertices().contains()方法来判断所添加边的顶点是否存在,再判断两顶点之间是否已有边连接,若条件满足,则调用this.set()方法设置边,权重初始化为1并返回true,其余情况返回false。

 

③ public int getDistance(Person People1, Person People2)

获取两个顶点之间距离的函数,题目要求返回最短距离,因此采用广度遍历的方式,此处需要用到Queue的数据结构,并且设置了一个List来存放已经访问过的person。

 

​​​​​​​3.2.2 Person

Person类根据FriendshipGraph类的需求编写的。它用于描述每个成员的性质,主要是实例化姓名的构造方法,getName()方法,判断姓名是否重复的isSameName方法。

public class Person
{
    private final String Name;

    public Person (String Name)
    {
        this.Name = Name;
    }
    public String getName()
    {
        return this.Name;
    }
    public boolean isSameName(String Name)
    {
        return this.Name.equals(Name);
    }
}

 

​​​​​​​3.2.3 客户端main()

main函数主体内容即为实验指导书给定的内容:先new一个FriendshipGraph类的对象,然后添加顶点,添加边。

输出错误类型在实现FriendshipGraph类时已输出,故此处不需要再次判断是否出现错误。

① 正常输出的测试结果

 

② 注释掉rachel -> ross后的测试结果

 

3.2.4 测试用例

测试策略:

 根据划分的等价类设计测试用例

 

测试结果与覆盖度报告:

   

3.2.5 提交至Git仓库

通过Git提交当前版本到GitHub上的Lab2仓库。

git add .

git commit -m "P1 P2 first finished"

git push -u origin master

 本项目的目录结构树状示意图:

 

 

4 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

日期

时间段

计划任务

实际完成情况

2022-05-23

8:00-11:30

浏览报告,查看MIT相关内容要求

按计划完成

2022-05-23

15:00-19:00

完成P1的GraphStaticTest类测试

按计划完成

2022-05-24

9:00-11:00

完成P1的ConcreteEdgesGraph类编写

按计划完成

2022-05-24

15:00-20:00

完成P1的ConcreteVerticesGraph类

按计划完成

2022-05-25

8:15-11:00

实现泛型Graph<L>的转换

按计划完成

2022-05-25

13:00-14:00

浏览Poetic Walks的编写要求

按计划完成

2022-05-25

21:00-23:00

尝试实现Poetic Walks

遇到困难,延期完成

2022-05-26

9:00-17:00

完成Poetic Walks的test和Implement

按计划完成

2022-05-27

8:00-18:00

完成P2的Social Network的改写

按计划完成

 

5 实验过程中遇到的困难与解决途径

遇到的难点

解决途径

不了解IDEA如何实现代码覆盖率测试

 

 

查阅网上资料后发现相应解决方案因IDE版本迭代已发生改变。自己摸索解决了实现代码覆盖率测试插件切换的问题。

对面向test的编程思想理解不够深入

 

 

复习《软件构造》课程的PPT,上网查阅了相关资料并咨询了同学,完成了对测试代码的编写。

对规约的要求不够理解

 

 

通过学习模仿,尝试自己编写相应规约并实现之。

 

6 实验过程中收获的经验、教训、感想

6.1 实验过程中收获的经验和教训

经验:

加深了自己对于泛型的理解和认识,提高了代码编写、ADT设计的能力。编写test测试文件时,有些方法的测试也能覆盖到其他的方法,避免重复测试增加工作量。

教训:

在设计多个类并使之互相配合的方面做得不好,编写代码的逻辑性有待提高。

6.2 针对以下方面的感受

(1)面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?

我感觉,对于面向ADT的编程,类是其主要特点,程序执行过程中,先由主函数进入,定义一些类,根据需要,执行类的成员函数,过程的概念被淡化了。而直接面向应用场景编程的抽象程度不高,虽然逻辑清晰但是代码思路混乱,不利于实现。

 

(2)使用泛型和不使用泛型的编程,对你来说有何差异?

泛型编程可以使代码被很多不同类型的对象所重用,并使代码具有更好的可读性。

 

(3)在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?

优势是不考虑代码的内部实现,只需考虑是否完成了规约中指定的功能。作为java语言的初学者来说我很不适应这种测试方式。

 

(4)P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?

提高了代码的利用率,减轻编程工作量。

 

(5)为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?

使编写的代码更加安全和可读性更强。愿意这么做。

 

(6)关于本实验的工作量、难度、deadline。

我认为,考虑到实验时间,与其他专业课的复习时间与考试时间有大量冲突,因此显得本实验工作量十分巨大,难度也很高,deadline十分紧张。

 

(7)《软件构造》课程进展到目前,你对该课程有何体会和建议?

希望减少工作量,增加课时安排,增加动手实验分数,减少笔试考试分数,合理安排课程开展时间,可以在刚开学时开课或者在小学期开课,更有利于学生能力的提升。

posted @ 2022-05-30 17:20  何以牵尘  阅读(254)  评论(0编辑  收藏  举报