1. 实验目标概述
本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象
编程(OOP)技术实现 ADT。具体来说:
-
针对给定的应用问题,从问题描述中识别所需的 ADT;
-
设计 ADT 规约(pre-condition、post-condition)并评估规约的质量;
-
根据 ADT 的规约设计测试用例;
-
ADT 的泛型化;
-
根据规约设计 ADT 的多种不同的实现;针对每种实现,设计其表示(representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
-
使用 OOP 实现 ADT,并判定表示不变性是否违反、各实现是否存在表示泄露(rep exposure);
-
测试 ADT 的实现并评估测试的覆盖度;
-
使用 ADT 及其实现,为应用问题开发程序;
-
在测试代码中,能够写出 testing strategy 并据此设计测试用例。
2. 实验环境配置
安装EclEmma。在eclipse中的install new software中访问EclEmma的更新站点安装。
3. 实验过程
3.1. Poetic Walks
根据要求完成接口Graph<L> 的两个具体实现:ConcreteEdgesGraph和ConcreteVerticesGraph。Graph<L> 接口要求实现如下几个方法:
add:添加新节点;
set:添加新边;
remove:移除节点;
vertices:获得所有的节点集合;
sources(target):获得以target为目标节点的边的起始节点;
targes(source):获得以source为起始节点的边的目标节点。
Poet:给定一组单词作为语料库,对于两个相邻的单词a和b,认为存在一条由a到b的有向边,通过Graph接口构造有向图。再给定一条由单词组成的句子,如果句子中两个相邻单词之间在Graph图中有一个桥接词则将桥接词插入到两单词之间。
3.1.1. Get the code and prepare Git repository
访问实验手册给出的URL地址,下载实验P1所需的代码。
在本地创建文件夹git,在文件夹下创建目录Lab2,在Lab2中打开git Bash:
使用命令git init创建Lab2的仓库。
3.1.2. Problem 1: Test Graph
根据Graph的spec,利用等价类划分的思想确定测试策略并编写测试用例
测试策略:
-
Graph.add方法测试:
点在图中,点不在图中
-
Graph.set方法测试:
按起点分:点在图中,点不在图中
按终点分:点在图中,点不在图中
按权重分:权不等于0,权等于0
-
Graph.remove方法测试:
点在图中,点不在图中
-
Graph.vertices方法测试:
图中没有点,图中有多个点
-
Graph.sources方法、Graph.targets方法测试:
给定点不在图中
给定点在图中:图中没有相应的点,图中有相应的点
3.1.3. Problem 2: Implement Graph<String>
完成接口的两个实现,实现接口所声明的六大方法。
3.1.3.1. Implement ConcreteEdgesGraph
3.1.3.1.1. Edge类的实现
- 数据域
在Edge类中设置保存边的起点、终点和权重的边的数据域,由于要求边是不可变的,所以把所有的数据域都使用final做了限定。声明如下:
private final int weight;
private final String source, target;
在一个Edge类的变量中,source和target不能为空,权重必须是正整数。
- 内部方法
方法 | 方法功能 |
---|---|
Edge(String source, String target, int weight) | 构造方法,创建一条边 |
private void checkRep() | 检查不变量 |
public String getSource() | 获得边的起点source |
public String getTarget() | 获得边的终点target |
public int getWeight() | 获得边的权重weight |
public String toString() | 把边按照期望的形式输出 |
3.1.3.1.2. ConcreteEdgesGraph类的实现
- 数据域
Set类的vertices变量用来保存所有的顶点,List类的edges变量用来保存所有的边。声明如下:
private final Set<String> vertices = new HashSet<>();
private final List<Edge> edges = new ArrayList<>();
- 内部方法
(1)checkRep():判断所有的点都不为空,所有的边都不为空,边数和点数满足条件:边数 <= 点数 * (点数 - 1)。
(2)add(String vertex):如果vertex不空且vertices域中不包含vertex,把vertex加入到vertices中,返回true,否则返回false。
(3)set(String source, String target, int weight):先检查是否在edges中有以source为起点,target为终点的边。
如果有,把原来的边的权重记录下来并移除这条边,再判断weight是否为0,如果不为0,新建一条以source为起点,target为终点,weight为权重的边,将其加入到edges中,否则不做任何操作,此时相当于移除这条边,最后返回记录下来的原来的边的权重。
如果edges中不存在这样的边,那么先判断这两个顶点是否在vertices中,如果某个顶点不在vertices中,那么先把它加入到vertices中,然后再判断weight是否为0,如果不为0,新建一条以source为起点,target为终点,weight为权重的边,将其加入到edges中,否则不做任何操作。然后检查不变量,返回表示原来不存在边的0。
(4)remove(String vertex):先判断vertex是否在vertices中,如果不在直接返回false,如果在其中则从vertices中删除vertex,然后遍历edges中的所有边,把与vertex有关(以其为起点或终点)的所有的边都删除。检查不变量然后返回true。
(5)vertices():使用Collections.unmodifiableSet(vertices)方法把vertices封装成一个不可修改的Set类型返回给外部。
(6)sources(String target):新建一个Map<String, Integer>类型的变量sources用来保存所有以target为终点的边的起点及权重,然后遍历edges,如果某个边的终点是target,那么把这条边的起点和权重加入到sources中,最后返回使用Collections.unmodifiableMap(sources)封装过的不可修改的Map类型的sources。
(7)targets(String source):新建一个Map<String, Integer>类型的变量targets用来保存所有以source为起点的边的终点及权重,然后遍历edges,如果某个边的起点是source,那么把这条边的终点和权重加入到targets中,最后返回使用Collections.unmodifiableMap(targets)封装过的不可修改的Map类型的targets。
(8)toString():如果vertices是空的,返回"",也就是什么也没有。否则就把所有的顶点都加入到要返回的字符串中。然后再判断edges是不是空的,如果不是就把所有的边都加入到要返回的字符串中。最后把字符串返回。
3.1.3.1.3. ConcreteEdgeGraphTest实现
由于ConcreteEdgeGraphTest是由GraphInstanceTest继承而得的,所以只需要测试添加的ConcreteEdgeGraph.toString方法和Edge类中的方法即可。
ConcreteEdgeGraph.toString方法测试策略:空图,图内有点但没有边,图内既有点又有边。
Edge类的测试策略:
getter方法:测试对于一条边是否能够正常获得它的各个属性
toString方法:由于Edge的数据域不可能为空,只需要测试对于一条边能够按照预期的形式输出。
3.1.3.2. Implement ConcreteVerticesGraph
3.1.3.2.1. Vertex类的实现
- 数据域
vertex变量用来保存顶点的label,Map类型的targets用来保存以vertex为起点的所有的边的终点和权重。声明如下:
private final String vertex;
private final Map<String, Integer> targets;
在一个Vertex变量中,vertex变量不能为空,targets中所有的终点不能为空,所有的权重必须是正整数。
- 内部方法
方法 | 方法功能 |
---|---|
public Vertex(String vertex) | 构造方法,创建一个顶点 |
private void checkRep() | 检查不变量 |
public String getVertex() | 获得顶点的名称vertex |
public Map<String, Integer> getTargets() | 以vertex为起点的边集targets |
public int add(String target, int weight) | 添加以vertex作为起点的边,其终点为target,权重为weight |
public int remove (String target) | 删除到target这个终点的边,即从targets中移除target |
public String toString() | 把边按照期望的形式输出 |
3.1.3.2.2. ConcreteVerticesGraph类的实现
- 数据域
保存所有顶点的一个List变量vertices。声明如下:
private final List<Vertex> vertices = new ArrayList<>();
- 内部方法
(1)checkRep():判断所有的点都不为空,且不包含重复点。
(2)add(String vertex):如果vertex不空且vertices域中不包含vertex,把vertex加入到vertices中,返回true,否则返回false。
(3)set(String source, String target, int weight):先检查是否在vertices中有source和target这两个点,如果没有target则先把这个点加入到vertices中。
如果没有source这个点,则把source也加入到vertices中,再判断权重是否为0,如果不为0,则把边加入到source的边集中,否则直接返回0。
如果存在source这个点,那么判断weight是否为0,如果不为0, 把边加入到source的边集中,返回原先边的权重;如果为0,则把以target为终点的这条边删除,返回原先边的权重。
(4)remove(String vertex):先遍历vertices中所有的顶点,把以vertex为终点的边都删除,然后在vertices中找到名为vertex的点,将其删除并返回true,如果没有返回,说明不存在名为vertex的点,返回false即可。
(5)vertices():遍历所有的顶点,取出顶点的名字加入到名字集合中,然后将这个集合用Collections.unmodifiableSet封装后返回。
(6)sources(String target):新建一个Map<String, Integer>类型的变量sources用来保存所有以target为终点的边的起点及权重,然后遍历vertices,如果某个顶点vertex.getTargets包含到target的边,那么把这个点和这条边的权重加入到targets中。
最后返回使用Collections.unmodifiableMap(sources)封装过的不可修改的Map类型的sources。
(7)targets(String source):遍历vertices找到source这个点,使用getTargets方法得到终点和对应边的集和,最后返回使用Collections.unmodifiableMap()封装过的不可修改的边集。
如果没找到,先创建new HashMap<L, Integer>(),然后返回它。
(8)toString():遍历vertices中所有的顶点,调用Vextex中的toString方法输出以每个顶点为起点的所有边,把这个字符串加入到要返回的字符串中,最后把字符串返回。
3.1.3.2.3. ConcreteVerticesGraphTest的实现
由于ConcreteVerticesGraphTest是由GraphInstanceTest继承而得的,所以只需要测试添加的ConcreteVerticesGraph.toString方法和Vertex类中的方法即可。
ConcreteVertexGraph.toString方法测试策略:空图,图内有点但没有边,图内既有点又有边。
Vertex类的测试策略:
getter方法:测试对于一个点是否能够正常获得它的点的名称和以它为起点的边集
add方法:按加入的边分:原先不存在这条边,原先存在这条边
remove方法:按被删除的边分:原先不存在这条边,原先存在这条边
toString方法:按边分:只有顶点没有以之为起点的边,有以之为起点的边
3.1.4. Problem 3: Implement generic Graph<L>
更改为泛型实现
3.1.4.1. Make the implementations generic
把所有的用到的String都替换成泛型实现
ConcreteEdgesGraph和Edge:
public class ConcreteEdgesGraph<L> implements Graph<L> {
private final Set<L> vertices = new HashSet<>()