本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象编程(OOP)技术实现ADT。具体来说:
l 针对给定的应用问题,从问题描述中识别所需的ADT;
l 设计ADT 规约(pre-condition、post-condition)并评估规约的质量;
l 根据ADT 的规约设计测试用例;
l ADT的泛型化;
l 根据规约设计ADT 的多种不同的实现;针对每种实现,设计其表示(representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
l 使用OOP 实现ADT,并判定表示不变性是否违反、各实现是否存在表示泄露(rep exposure);
l 测试ADT 的实现并评估测试的覆盖度;
l 使用ADT 及其实现,为应用问题开发程序;
l 在测试代码中,能够写出testing strategy 并据此设计测试用例。
本实验需要用到Eclipse中的EclEmma来测试测试文件的覆盖度,我们可以直接在Eclipse中搜索安装。
本次实验给出了一个抽象的图接口,要求我们用边和点两种方法来继承实现这个抽象接口,并且在子类里面实现一系列方法。之后用这个图的抽象数据型完成poet的工作。主要考察ADT的规约设计和ADT的实现。
3.实验过程
3.1Problem 1: Test Graph <String>
本问题针对Graph<String>设计相应的测试策略,主要针对里面的每个方法进行等价类划分的测试。
Testing strategy:
(1)对于add方法,我们使用空图加点和重复加点两种方式测试。
(2)对于set方法,我们使用空集加权重为0的边和空集加权重不为0的边,非空集有未相连两点加权重不为0的边和加权重为0的边,非空集有相连的两点加权重为0和不为0的边进行测试。
(3)对于remove方法,我们可以对移除已存在点和未存在点两种情况测试,同时测试移除点之后的源点集判断是否成功去掉边。
(4)测试vertices方法,使用空图和非空图测试。
(5)测试返回源点集,使用空源点和有源点两种测试。
(6)测试返回目标点集,使用空目标点和有目标点两种测试。
3.2Problem 2: Implement Graph <String>
实现Edge类
(1)类中的字段应该包括边的长度,边的起点和终点,并且定义为私有类型。这些变量如下图:
(2)类中需要实现的方法如下图所示:
Edge | 初始化构造方法,初始化新边的两个点和边的权值 |
checkRep | 检查表示不变性,边不为空且权值大于0 |
getsource | 返回边的一个点的源点 |
gettarget | 返回边的另外点的目标点 |
getweight | 返回边的权重 |
toString | 返回一条边的字符串,形式为“起点->终点 权重:xx” |
(3)AF,RI和Safety from rep exposure如下图:
(4)测试策略:
实现ConcreteEdgesGraph类
(1)类的字段中应该包括顶点set表和边list表,定义私有类型的表如下图所示:
(2)在ConcreteEdgesGraph中需要实现的方法如下图所示:
checkRep | 检查表示不变性,类中边的权重必须大于0 |
add | 顶点集中没有该点时,添加该顶点进入点表中 |
set | 输入source,target,weight,分别为边的起点、终点和权值。若权值为正且边已经存在,则修改边的权值。若权值为正且边不存在,则直接添加新边。若权值为0且边已经存在,则删去原边。若权值小于0,则返回0。只要改变了原边权值,都返回原边权值,没有权值则返回0 |
remove | 除去某个点及与它相邻的所有边。只需要遍历edges,寻找是否有边的起点或者终点为该点,直接删去即可 |
vertices | 返回所有的点集 |
sources | 输入一个终点,返回与它相连的所有边和起点构成的Map |
targets | 输入一个起点,返回与它相连的所有边和终点构成的Map |
toString | 调用Edge类中的toString方法将整个图中所有点的指向转化为一条字符串输出 |
(3)AF,RI和Safety from rep exposure如下图:
(4)测试策略:
该测试已经继承Graph的测试策略,所以只需要增加toString的测试和对Edge类的测试即可:
测试tostring:使用空图和非空图返回判断字符串是否相等进行测试。
测试Edge集:使用加点后判断返回的source,target,weight是否相等测试。
测试ConcreteEdgesGraphTest.java
设计Vertex类
(1)类中的字段应该包括点的名字和以该点为源点的目标点集,并且定义为私有类型。这些变量如下图:
(2)类中需要实现的方法如下图所示:
checkRep | 检查表示不变性,每个边的权值应该大于0 |
getname | 返回点的名字name |
getsource | 返回该点能到达的所有点和边权值构成的Map |
getweight | 输入一个目标点,返回该点到目标点的边的权值 |
remove | 输入一个目标点,若该点的目标点集中有该点则删去,否则返回false |
change | 输入一个目标点和权重,类似于set |
toString | 得到以该点为源点的包含所有它的目标点的字符串表示 |
Vertex | 初始化构造方法,用点的名字创建 |
(3)AF,RI和Safety from rep exposure如下图:
(4)该测试已经继承Graph的测试策略,所以只需要增加toString的测试和对Vertex类的测试即可:
测试tostring:使用空图和非空图返回判断字符串是否相等进行测试。
测试Vertex集:使用加点后判断未修改就返回的目标点集,和对点的边进行修改后返回的目标点集进行测试。
测试ConcreteVerticesGraphTest.java
3.3 Implement generic Graph<L>
要求我们使用泛型修改之前编写的String:将之前我们使用String实例的地方全部改为L,可借助Eclipse的报错辅助修改:
Implement Graph.empty()
需要在Graph类接口empty方法中返回一个具体的实现:
测试结果:
测试覆盖率为:
该问题要求我们实现一个类,必须利用之前实现的图结构,将一个文件输入的语料库转化为图结构,对输入的句子在图中搜索节点,完成扩充。
Test GraphPoet
测试结果:
测试覆盖率为:
Implement GraphPoet
(1)GraphPoet的字段为String构成的Graph,定义私有类型的表如下图所示:
(2). AF,RI和Safety from rep exposure如下图:
(3).在GraphPoet中需要实现的方法如下图所示:
GraphPoet | 输入文件的路径,按行读取,按照空格划分为一个个元素,之后将这些元素加入图作为点,然后每次取相邻的元素,在图中添加边。 |
checkRep | 检查不变性,必须保存从语料库文件生成的图 |
poem | 输入字符串,使用一个StringBuilder保存我们扩充后的字符串,每次读取一个词,加入StringBuilder,然后以该词作为source,下一个词作为target,然后在Garph中寻找source的终点表中是否有与target的源点表中相同的元素,之后比较权值大小找到权值最大的和的点加入StringBuilder中,待到完成搜索,返回扩充后的字符串 |
toString | 调用ConcreteEdgesGraph中的toString方法,将整个图中所有点的指向转化为一条字符串输出 |
Graph poetry slam
我们在输出返回的扩充的边之后再返回语料库生成的图:
使用Eclemma检查测试的代码覆盖度
测试结果:
测试覆盖率为:
我们在LAB1中使用Person类作为点,设计了很多方法来完成存储点集,存储边集,完成点的加入,边的加入,以及求取点的连接的边。在本次实验中,我们已经完成了图结构的编写,所以可以直接用图结构中的方法完成要求,例如:直接使用Graph<L>作为图结构,我在项目中使用的是ConcreteEdgesGraph<L>的实现。使用add完成添加点,set完成添加边,sources和targets可以获取点与边的关系,vertices可以获取点集。
FriendshipGraph类
(1)该类中的字段为以Person为点类型的图Graph:
(2)类中的AF,RI和Safety from rep exposure如下图:
(3)在FriendshipGraph需要实现的方法如下图所示:
FriendshipGraph | 构造方法 |
addVertex | 在图中增加新Person,调用ConcreteEdgesGraph中的add即可 |
addEdge | 输入两点加入他们之间的边,直接调用ConcreteEdgesGraph中的set即可: |
getDistance | 得到两个人之间的最短距离。与Lab1类似,我们首先使用一个Map<Person, Integer>visited表示每个点的状态,0为未搜索,1为已搜索,再将第一个点入队,改变状态开始广度优先搜索,我们对于每个点调用他们的targets方法获取他们的目标节点集,然后入队,改变他们距离第一个节点的距离,直到我们找到第二个节点,直接返回我们存储再distance中的距离值即可。 |
main | 与LAB1一致 |
Person类
(1)类中的字段包括点的名字:
private final String name;
(2)Person类只是作为一个点的存在,它相当于L,不需要有其他太多功能。其方法为:
Person | 输入名字,构造方法 |
getname | 返回本人名字,直接返回即可 |
(3)类中的AF,RI和Safety from rep exposure如下图:
运行结果:
测试用例
与LAB1思路一致,但更简洁:
测试结果如下:
测试用例覆盖度: