2021年春季学期
计算学部《软件构造》课程
**Lab 2
目录
实验目标概述
本次实验训练抽象数据类型(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 并据此设计测试用例。
实验环境配置
IDEA自带代码覆盖率工具,只需右键单击测试文件,选择Run with Coverage即可:
在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号):
https://github.com/ComputerScienceHIT/HIT-Lab2-1190200828
实验过程
Poetic Walks
此实验的目的是练习设计、测试和执行ADT。该阶段给出了一个图的接口,要求建立一个边图类一个点图类来分别继承自该图接口,在里面实现一系列方法并实现抽象数据型以完成poet。
Get the code and prepare Git repository
从GitHub获取该任务的代码:
git clone https://github.com/rainywang/Spring2021_HITCS_SC_Lab2/
在本地创建git仓库:
在自己创建的本地仓库中,打开git bash输入git init然后与git远端仓库进行关联即可。
Problem 1: Test Graph <String>
思路:
这是针对Graph<String>设计相应的测试策略,即对Graph<String>的每个方法进行测试。
过程:
下面的截图为接口Graph中的部分被方法测试策略,其中vertices()方法在add、set等方法的测试中已经测试过,因此不再单独测试:
其他测试策略不再一一展示。
Problem 2: Implement Graph <String>
Implement ConcreteEdgesGraph
-
Edge类的实现
-
初始化构造方法:
定义边起点source和边终点target,以及边权值weight。
-
AF、RI、Safety:
-
Edge中需要实现的方法:
-
Edge:初始化构造方法,即初始化边的两个顶点和边权值;
-
checkRep:检查表示不变性;
-
getSource:返回边起点;
-
getTarget:返回边终点;
-
getWeight:返回边权值;
-
toString:字符串形式描述一条边,形式为”Weight of the edge from source to
target is weight.\n” -
Edge类中各方法的测试策略:
其他测试策略不再一一展示。
-
测试结果:
-
ConcreteEdgesGraph类的实现
-
初始化构造方法:
定义顶点集vertices和边表edges。
-
AF、RI、Safety:
-
ConcreteEdgesGraph中需要实现的方法(结合已设计的Graph测试用例):
-
ConcreteEdgesGraph:构造方法,不需要初始化变量;
-
checkRep:检查表示不变性;
-
add:添加一个顶点到顶点集vertices,不能重复加入;
-
set:输入起点source、终点target和边权值weight,若起点或终点不在vertices内,则调用add将其加入。若weight
< 0,则返回-1;当weight = 0时,若边存在,则删去该边;当weight >
0时,若该边不存在,则将该边直接加入edges,否则应删去原边后再将新的边和权值加入edges。改变了原边权值时,返回原边权值,边不存在则返回0; -
remote:去除某一点以及与它有关的所有边;
-
vertices:返回顶点集vertices;
-
sources:输入终点,返回所有指向该点的起点及边的权值;
-
targets:输入起点,返回该点指向的所有终点及边的权值;
-
toString:字符串形式输出图,每一条边的输出格式都与Edge类中toString方法中的返回形式相同。
-
测试结果(包含Graph测试用例测试结果和toString的测试结果):
Implement ConcreteVerticesGraph
-
Vertex类的实现
-
初始化构造方法:
定义图中点的名字vertex,Map
sources用于存储指向点vertex的所有起点以及它们之间的边的权值,Map
targets用于存储点vertex指向的所有终点以及它们之间边的权值。 -
AF、RI、Safety:
-
Vertex类中需要实现的方法:
-
Vertex:初始化构造方法;
-
checkRep:检查表示不变性;
-
getVertex:返回点的名字;
-
addSource:向sources中添加指向该点的起点及它们间边的权值。若weight <
0,则返回-1;当weight = 0时,若该起点存在,则在sources中删去该起点;当weight
>
0时,直接sources.put进行添加或者替换。改变了原边权值时,返回原边权值,边不存在则返回0; -
addTarget:向targets中添加该点指向的终点及它们间边的权值。操作与addSource相类似;
-
getSources:返回sources;
-
getTargets:返回targets;
-
removeSource:从sources中删去一个指向该点的起点;
-
removeTarget:从targets中删去一个该点指向的终点;
-
toString:返回该点以及sources、targets。
-
测试结果:
-
ConcreteVerticesGraph类的实现
-
初始化构造方法:
定义顶点列表。
-
AF、RI、Safety:
-
ConcreteVerticesGraph类中需要实现的方法:
-
ConcreteVerticesGraph:构造方法,不需要初始化变量;
-
checkRep:检查表示不变性;
-
add:初始化一个新的顶点并将其添加到到顶点列表vertices,若该点已经存在于图中,不能重复加入;
-
set:输入起点source、终点target和边权值weight,若起点或终点不在vertices()内,则调用add方法将其加入。关于weight不同取值的操作于Vertice类的addSource、addTarget方法中完成;
-
remote:去除某一点以及与它有关的所有边;
-
vertices:返回顶点集vertices;
-
sources:输入终点,返回所有指向该点的起点及边的权值;
-
targets:输入起点,返回该点指向的所有终点及边的权值;
-
toString:字符串形式输出图,每一个顶点的sources和targets的格式都与Vertex类中toString方法中的返回形式相同。
-
测试结果:
Problem 3: Implement generic Graph<L>
Make the implementations generic
将之前实现的类和方法均使用泛型实现即可,即将各类中一开始用String型定义的变量,如起点source、终点target等相关的String类型变量均换为L。
接下来,按照IDEA的报错,将代码的其他位置的String替换为泛型L即可。
Implement Graph.empty()
返回一个Graph的具体实现即可。
补充其他类型的标签:
测试结果:
Problem 4: Poetic walks
Test GraphPoet
GraphPoet类中GraphPoet与poem方法的测试策略及测试用例:
【注】因为GraphPoet方法没有任何输出,图变量graph为private,所以无法直接测试该方法,所以直接测试两个方法;
1_empty.txt:空白文件,图为空, b必定不存在,所以返回值与input一致;
2_easy.txt:简单测试用文件,要插入的b均唯一
3_difficult.txt:比较复杂的测试用文件,要插入的b不唯一
GraphPoet类中toString方法的测试策略:
【注】考虑到该方法的测试,将Graph的具体实现更改为边图类,因为边图类的toString输出比点图类输出简单,更易书写测试用例。
Implement GraphPoet
-
初始化构造方法:
定义字符串构成的图graph。
-
AF、RI、Safety:
-
需要实现的方法:
-
GraphPoet:先将文件corpus中的所有点读入,考虑到其中存在相同的点,集合Set并不适用,因此将所有点都读入列表。循环遍历列表,每次读入一前一后两个点作为起点和终点,建成一条边,其权值为该边在corpus中出现的次数;
-
poem:生成poem,设计如下:
-
toString:调用Graph接口中的toString方法,将整个图中所有点的指向转化为一条字符串输出。因为Graph接口的具体实现在设计测试文件时改为了边图类,所以输出格式即为边图类的toString方法的输出。
Graph poetry slam
在main函数中加入一个输出调用toString方法,运行结果如下:
使用Eclemma检查测试的代码覆盖度
Before you’re done
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
git init
git add .
git commit -m “First commit:Poetic Walks has been done”
git remote add origin
https://github.com/ComputerScienceHIT/HIT-Lab2-1190200828
git push origin master -u -m
在这里给出你的项目的目录结构树状示意图:
Re-implement the Social Network in Lab1
用在Poetic
Walks已经实现的Graph<L>来实现Lab1中的FriendshipGraph类的各个方法,使之能通过测试文件,并且运行main函数的结果与Lab1一致。
FriendshipGraph类
-
初始化构造方法:
生成空图graph。
-
AF、RI、Safety:
-
FriendshipGraph类中要实现的方法:
-
main:与Lab1一致;
-
addVertex:向图中添加点,调用Graph类中的add即可;
-
addEdge:向图中添加边,因为是无向图,调用两次Graph类中的set即可;
-
getDistance:得到两点间的距离,利用DFS算法,通过调用Graph类中的sources等方法即可;
-
getGraph:因为构造方法graph设置为为private,为方便测试,故设置该方法以返回graph。
Person类
-
初始化构造方法:
定义人的名字name
-
AF、RI、Safety:
-
Person类中要实现的方法:
Person:初始化构造方法,即初始化name;
客户端main()
将Lab1中的main直接复制过来即可,运行结果如下:
该结果与Lab1中main的运行结果一致。
测试用例
测试用例与Lab1一致,新增方法getGraph在其他测试中已测试完毕,不再进行单独测试;
测试结果(main无法参与测试,所以代码覆盖度较低):
提交至Git仓库
提交过程同Poetic Walks;
在这里给出你的项目的目录结构树状示意图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EHvUECld-1625645261706)(media/21c74acf10e1062c719496a9d029fc77.png)]
实验过程中收获的经验、教训、感想
实验过程中收获的经验和教训
-
经过学习,对于Java的一些概念更加了解;
-
对于Java中自带的一些方法还是很不熟悉,很多方法都要靠百度现场搜,以后会多加积累,多加练习;
-
总是因为一些小错误而debug很长时间,以后要避免循环溢出等小错误;
-
因为IDEA太聪明了,导致手写代码基本不可能,以后要多加记忆代码,加强手写代码能力。
针对以下方面的感受
-
面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
ADT的实现过程比较困难,但是一旦实现了ADT,后续工作就会变得十分简单;
-
使用泛型和不使用泛型的编程,对你来说有何差异?
本次实验来讲并没有什么差异,因为开始时未用泛型时,也没有用到与特定数据类型相关的方法;
-
在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
优势:有目的的编程,有助于保证代码的正确性;
暂时不适应;
-
P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
提高代码效率,降低实现难度;
-
P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
首先,P3不存在;其次,肯定不适应,因为P1都只是勉强能写;
-
为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep
exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?意义:防止内部变量被外部修改,提高代码安全性;
我会尽量坚持,虽然繁琐,但可以提高代码安全性;
-
关于本实验的工作量、难度、deadline。
工作量比较大,主要体现在写代码、代码注释、测试策略以及实验报告的撰写;难度对我来说比较高,因为对Java还是不算熟悉,很多方法需要现查;ddl比较合理,毕竟三周时间,但很容易造成所有工作堆在最后完成,建议学习计算机系统实验,将整个实验分为几个具有联系的小型实验发布,每周一交;
-
《软件构造》课程进展到目前,你对该课程有何体会和建议?
Java是一个很聪明的语言,而IDEA也是一个很聪明的编程工具;
实验中给出代码注释基本上都是英文,本次实验中,我的函数注释基本上也是用的英文,但是测试策略、代码注释还是中文,以后会慢慢改进。
设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?首先,P3不存在;其次,肯定不适应,因为P1都只是勉强能写;
-
为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep
exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?意义:防止内部变量被外部修改,提高代码安全性;
我会尽量坚持,虽然繁琐,但可以提高代码安全性;
-
关于本实验的工作量、难度、deadline。
工作量比较大,主要体现在写代码、代码注释、测试策略以及实验报告的撰写;难度对我来说比较高,因为对Java还是不算熟悉,很多方法需要现查;ddl比较合理,毕竟三周时间,但很容易造成所有工作堆在最后完成,建议学习计算机系统实验,将整个实验分为几个具有联系的小型实验发布,每周一交;
-
《软件构造》课程进展到目前,你对该课程有何体会和建议?
Java是一个很聪明的语言,而IDEA也是一个很聪明的编程工具;
实验中给出代码注释基本上都是英文,本次实验中,我的函数注释基本上也是用的英文,但是测试策略、代码注释还是中文,以后会慢慢改进。