目录
1 实验目标概述 1
2 实验环境配置 1
3 实验过程 2
3.1 Poetic Walks 2
3.1.1 Get the code and prepare Git repository 2
3.1.2 Problem 1: Test Graph 2
3.1.3 Problem 2: Implement Graph 5
3.1.3.1 Implement ConcreteEdgesGraph 5
3.1.3.2 Implement ConcreteVerticesGraph 10
3.1.4 Problem 3: Implement generic Graph 10
3.1.4.1 Make the implementations generic 16
3.1.4.2 Implement Graph.empty() 16
3.1.5 Problem 4: Poetic walks 18
3.1.5.1 Test GraphPoet 18
3.1.5.2 Implement GraphPoet 19
3.1.5.3 Graph poetry slam 21
3.1.6 Before you’re done 21
3.2 Re-implement the Social Network in Lab1 22
3.2.1 FriendshipGraph类 22
3.2.2 Person类 22
3.2.3 客户端main() 24
3.2.4 测试用例 25
3.2.5 提交至Git仓库 263.3 Playing Chess 29
3.3.1 ADT设计/实现方案 29
3.3.2 主程序MyChessAndGoGame设计/实现方案 49
3.3.3 ADT和主程序的测试方案 58
4 实验进度记录 79
5 实验过程中遇到的困难与解决途径 80
6 实验过程中收获的经验、教训、感想 80
6.1 实验过程中收获的经验和教训 80
6.2 针对以下方面的感受 80

1 实验目标概述
本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象编程(OOP)技术实现 ADT。具体来说:
(1)针对给定的应用问题,从问题描述中识别所需的 ADT;
(2)设计 ADT 规约(pre-condition、post-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.配置环境的过程:参考了实验手册
(1) 阅读 http://web.mit.edu/6.031/www/fa18/getting-started/,在本地机器安装了相应的开发环境(JDK、Eclipse、Git)
(2) 阅读https://github.com/junit-team/junit4/wiki/Download-and-Install,并在自 己的 Eclipse IDE 中安装配置 JUnit4。
(3) 阅读 https://github.com/junit-team/junit4/wiki/Getting-started,了解如何使 用 JUnit4 为 Java 程序编写测试代码并执行测试
(4)配置EclEmma(由于在安装eclipse时已经自带,无需再次配置)2.遇到的问题和困难,以及解决方式:本次配置顺利进行,并未出现错误
3.在这里给出你的GitHub Lab2仓库的URL地址(Lab2-1180301002)。https://github.com/ComputerScienceHIT/Lab2-1180301002.git3

实验过程3.1 Poetic Walks
本问题名字是诗歌的行走,其实说的是本实验的Problem 4,先给定一个语料库(诗人的博学多才),由此生成图,然后再给定输入(诗的关键词),输出根据该输入进行扩展(吟诗),在扩展时,在诗中的单词构成的图中中遍历查找,就像是在行走一样。那么生成图该如何实现?这就是Problem 1到Problem 3的内容。Problem 1 是写图的测试用例,Problem 2是实现图,分为以点为重心和以边为重心,Problem 3 是将这个图进行泛化,点的名字可以不单单是String类型
3.1.1 Get the code and prepare Git repository
(1)从GitHub获取该任务的代码:在实验手册中,找到该任务的网站http://web.mit.edu/6.031/www/sp17/psets/ps2/然后点击Clone or download后,再点击Dowload ZIP,然后选择下载到目标位置即可
(2)在本地创建git仓库、使用git管理本地开发
①到要上传的文件夹里面cd /c/Users/10025/Desktop/xxx
②初始化本地git仓库git init
③选择需要存入的文件git add .
④添加修改日志git commit -m “xxx”

3.1.2 Problem 1: Test Graph
实现思路:使GraphStaticTest和GraphInstanceTest这两个测试用例通过过程:
(1)GraphStaticTest测试用例:在Problem 1中,只需要完成testEmptyVerticesEmpty()方法即可:
①Testing strategy:已经给出(测试空图:无输入时,应该无输出)
②完成Graph类中的empty方法:new一个ConcreteVerticesGraph(),并返回。
③完成ConcreteVerticesGraph类中的vertices方法:返回vertices即可
(2)GraphInstanceTest测试用例:Testing strategy:
①testAssertionsEnabled(测试assert是否可用)
②testInitialVerticesEmpty(测试初始时,点集是否为空)
测试空图:无输入时,应该无输出
③testAdd(测试加入点的功能)
第一次加入点,点集中应该包含该点,点集的大小发生正确变化,返回真;
第二次加入与第一次不同的点(测试添加多个点),点集中应该包含该点,点集的大小发生正确变化,返回真
第三次加入与第一次相同的点,点集的大小不变,返回假
④testSet(测试添加、修改、删除边权的功能)
先加入点到点集中第一次添加边权为1,targets方法返回的键值和sources方法返回的键值应该发生正确变化,返回值应该为0(之前没有这条边,故为0)
第二次修改边权为2,targets方法返回的键值和sources方法返回的键值应该发生正确变化,返回值应该为1(第一次中,边权为1)
第三次修改边权为0(即删除),targets()和sources应该发生正确变化,返回值应该为2(第二次中,边权为2)
此外,如果添加的边包含了不在点集中的点,该点应该被加入点集,又分为起点不在点集以及终点不在点集
⑤testRemove(测试删除点集合中的点的功能)
先构造一个图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
第一次删除D,由于不在点集中,图没有变化(验证点集合、targets方法返回的键值和sources方法返回的键值),返回假
第二次删除A,由于点在点集中,图发生变化(验证点集合、targets方法返回的键值和sources方法返回的键值),返回真
⑥testVertices(测试返回点集合的功能)
先new一个临时的集合
向点集合中添加点的时候,也向这个临时集合添加点
两个集合应该相等(验证大小,相互包含)
⑦testSources(测试指向某点的点集合以及边权的功能)
先构造一个图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
验证键值对个数(即边数),包含键,包含值,包含键值
⑧testTargets(测试某点指向的点集合以及边权的功能)
先构造一个图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
验证键值对个数(即边数),包含键,包含值,包含键值
结果:
①对于GraphStaticTest测试用例,测试通过如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
②对于GraphInstanceTest测试用例,由于Problem 1只是编写了测试用例,还没有具体实现(于Problem 2实现 ),因此此时并没有成功截图,在后续的Problem 2中会给出

3.1.3 Problem 2: Implement Graph

3.1.3.1 Implement ConcreteEdgesGraph
实现思路:
(1)完成Edge类的实现
(2)完成ConcreteEdgesGraph类的实现
(3)完成ConcreteEdgesGraphTest测试用例的
实现过程:
(1)Edge类的实现:
①Edge类是不可变的
②AF:AF(weight, source, target) = 一条有向边有权重,有起点,有终点
③RI:source与target不为空,weight非负
④Safety from rep exposure:
数据类型不可变(没有setter方法)且属性私有
每次执行构造函数以及函数返回前,都要checkRep
当获取函数返回前,重新新建了一份(防御拷贝)
⑤包括三个私有属性
权重、起点、终点,只能通过getter方法来得到
⑥包括八个方法以及对应的spec:(hashCode和equals由IDE自动生成)

边的构造函数
@param source 是这条边的起点,不能为空
@param target 是这条边的终点,不能为空
@param weight 是这条边的权重
public Edge(String source,String target,int weight)

private void checkRep()

获得该边的权重
@return 该边的权重
public int getWeight()

获得该边的起点
@return 该边的起点
public String getSource()

获得该边的终点
@return 该边的终点
public String getTarget()

返回该边的权重,起点、终点信息
-()->左端表示起点名字,右端表示终点名字
-()->中的值是这条边的权重;
比如:"A-(1)->B"表示:
边是A指向B,边权是1
@Override public String toString()
⑦ 各个方法的测试用例
getWeight方法的测试用例:
构造一条边,再用getWeight方法来测试返回是否相等
getSource方法的测试用例:
构造一条边,再用getSouece方法来测试返回是否相等
getTarget方法的测试用例:
构造一条边,再用getTarget方法来测试返回是否相等
构造函数的测试用例:
由于其他方法都需要调用到构造函数,其他方法的测试成功就说明这个方法的测试成功
toString方法的测试用例:
先构造一个图:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
再测试输出的信息是否相同即可
(2)完成ConcreteEdgesGraph类的实现
可变的,包括八个方法
①@Override public boolean add(String vertex)
遍历点集中的点,如果要添加的点已经存在了,返回false;如果不存在,就添加该点到点集中,返回true;
②@Override public int set(String source, String target, int weight)
遍历边列表中所有的边,如果找到了相同的起点终点,此时为修改(weight大于0)、删除(weight为0)边权,先用临时变量保存这条边的权重,再移除这条边,然后把要加入的边加入到边列表中,最后返回临时变量(之前的权重);如果没找到,此时为为添加边权,若要添加的边中有点不在点集里面,则先把该点添加进点集里,然后把边加入边列表中,返回0
③@Override public boolean remove(String vertex)
遍历点集中的所有点,如果点集中存在该点,要删除点以及由该点出发、到该点的边(注意删除边列表里多个元素时,应该使用迭代器),返回真;如果点集不存在要删除的点,返回假
④@Override public Set vertices()
新建一个临时变量用于返回(防御拷贝)
⑤@Override public Map<String, Integer> sources(String target)
新建一个空图,遍历所有的边,如果某条边的终点是target且权重不是0,就把这条边的起点(key),权重(value)放入图中,如此反复,最终返回这个图
⑥@Override public Map<String, Integer> targets(String source)
新建一个空图,遍历所有的边,如果某条边的起点是source且权重不是0,就把这条边的终点(key),权重(value)放入图中,如此反复,最终返回这个图
⑦@Override public String toString()
返回点集信息,边列表信息。其中点集中的点的名字直接输出,名字间用逗号分开;对于边列表,-()->左端表示起点名字,右端表示终点名字;-()->中的值是这条边的权重;出现逗号说明,这条边已经输出完毕,后面是下一条边的内容比如:"图中的点是[A, B, C, D],图中的边是[A-(1)->B, A-(2)->C, C-(3)->B, D-(4)->C]"表示:点的名字分别是A、B、C、D,边是A指向B,边权是1;A指向C,边权是2;C指向B边权是3;D指向C,边权是4
⑧private void checkRep()
在每次函数返回前都要利用checkRep()来检查不变量
同时,返回时需要新建一个临时变量用于返回(防御拷贝)
(3)完成ConcreteEdgesGraphTest测试用例的实现
在(1)Edge类的实现中,已经提及了Edge类中方法的测试用例,不在赘述
此外,由于ConcreteEdgesGraphTest继承了GraphInstanceTest,相比之下,ConcreteEdgesGraph类中只多了toString方法,因此,只要测试这个即可
public void ConcreteEdgesGraphToString(测试ConcreteEdgesGraph类中的toString方法)先构造一个图:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
测试输出的信息是否相同
结果:
测试结果如下图所示:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(此结果也验证了Problem 1 中的GraphInstanceTest测试用例的成功)
3.1.3.2 Implement ConcreteVerticesGraph
实现思路:
(1)完成Vertex类的实现
(2)完成ConcreteVerticesGraph类的实现
(3)完成ConcreteVerticesGraphTest测试用例的
实现过程:
(1)Vertex类的实现:
①Vertex类是可变的
②AF:AF(name, targets, sources) = 一个点有指向的点,有指向该点的点,有对应的边
③RI:某点的name不能为空,targets和sources中的权重非负
④Safety from rep exposure:
数据类型虽然可变,但属性私有且用final修饰;
当获取函数返回时,重新新建了一份(防御拷贝),防止意外修改;
每次执行构造函数时,都要checkRep
⑤包括三个私有属性
名字、图<该点指向的点, 对应的边的权重>、图<指向该点的点, 对应的边的权重>,只能通过getter方法来得到,也可以通过setter方法来改变图<该点指向的点, 对应的边的权重>、图<指向该点的点, 对应的边的权重>
⑥包括十个方法以及对应的spec
点的构造函数
@param name是该点的名字
public Vertex(String name) private void checkRep()

获得该点的名字
@return 该点的名字
public String getName()

获得该点指向的点,以及对应的边权
@return 该点指向的点,以及对应的边权
public Map<Vertex, Integer> getTargets()

获得指向该点的点,以及对应的边权
@return 指向该点的点,以及对应的边权
public Map<Vertex, Integer> getSources()

添加该点指向的点,以及对应的边权
@param vertex 是该点指向的点
@param weight 是对应的边权
public void putTargets(Vertex vertex, Integer weight)

添加指向该点的点,以及对应的边权
@param vertex 是指向该点的点
@param weight 是对应的边权
public void putSources(Vertex vertex, Integer weight)

删除该点指向的点,以及对应的边权
@param vertex 是该点
@return 如果删除成功,返回真;如果删除失败,返回假
public boolean removeTargets(Vertex vertex)

删除指向该点的点,以及对应的边权
@param vertex 是该点
@return 如果删除成功,返回真;如果删除失败,返回假
public boolean removeSources(Vertex vertex)

返回该点的名字,其指向和被指向点,以及对应的边权的信息
【】包围的是点的名字,左边是指向它的点,右边是它指向的点;
如果一个点指向多个点或者被多个点指向,则这多个点之间用|分开
-()->中的值是箭头两端的点构成的边权;
比如:“A-(2)->|D-(4)->【C】-(3)->B"表示:
C被A指向,边权是2
C被D指向,边权是4
C指向B,边权是3
@Override public String toString()
⑦ 各个方法的测试用例
getName方法的测试用例:
先构造一个叫A的点,再用 getName来测试返回是否相同
getTargets方法的测试用例:
构造一条边,再用getTargets方法来测试返回是否相同
getSources方法的测试用例:
构造一条边,再用getSources方法来测试返回是否相同
removeTargets方法的测试用例:
构造一条边,再用removeTargets方法来测试返回是否相同
removeSources方法的测试用例:
构造一条边,再用 removeSources方法来测试返回是否相等
构造函数的测试用例:
由于其他方法都需要调用到checkRep方法和构造函数,其他方法的测试成功就说明这两个方法的测试成功
toString方法的测试用例:
先构造一个图:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
再测试输出的信息是否相同即可
(2)完成ConcreteVerticesGraph类的实现
可变的,包括八个方法
①@Override public boolean add(String vertex)
遍历点列表中的点,如果要添加的点已经存在了,返回false;如果不存在,就添加该点到点列表中,返回true;
②@Override public int set(String source, String target, int weight)
遍历点列表中所有的点,如果找到了起点,则遍历该点指向的点,若还能找到终点,说明找到了相同的起点终点,此时为修改(weight大于0)、删除(weight为0)边权,先用临时变量保存权重,再调整边权(注意起点指向的点对应的权重要改,指向终点的点对应的权重也要改),最后返回临时变量(之前的权重);如果起点或终点没找到,此时为为添加边权,若要添加的边中有点不在点列表里面,则先把该点添加进点列表里,此时起点终点都在点列表里,然后两重循环遍历整个点列表,再调整边权(注意起点指向的点对应的权重要改,指向终点的点对应的权重也要改),最后返回0
③@Override public boolean remove(String vertex)
遍历点列表中的所有点,如果点列表中存在该点,先删除该点,此外还要删除点以及由该点出发、到该点的边,最后返回真;如果点集不存在要删除的点,返回假
④@Override public Set vertices()
新建一个临时变量,遍历点列表中的所有点,把名字放入该临时遍历中,最后返回 ⑤@Override public Map<String, Integer> sources(String target)
新建一个空图,双重循环遍历点列表中所有的点,如果某个点指向的点的名字是target且对应的边权不是0,就把该某个点的名字(key),对应的边权(value)放入图中,如此反复,最终返回这个图
⑥@Override public Map<String, Integer> targets(String source)
新建一个空图,双重循环遍历点列表中所有的点,如果名字为source的点指向某个点且对应的边权不是0,就把该某个点的名字(key),对应的边权(value)放入图中,如此反复,最终返回这个图
⑦@Override public String toString()
返回点列表信息。其中,【】包围的是点的名字,左边是指向它的点,右边是它指向的点;如果一个点指向多个点或者被多个点指向,则这多个点之间用|分开;出现逗号说明,某个点指向的或被指向的点已经罗列完毕,后面是下一个点的内容关系;-()->中的值是箭头两端的点构成的边权;比如:”【A】-(1)->B|-(2)->C, A-(1)->|C-(3)->【B】, A-(2)->|D-(4)->【C】-(3)->B, 【D】-(4)->C"表示:A指向B,边权是1,A指向C,边权是2B被A指向,边权是1,B被C指向,边权是3C被A指向,边权是2,C被D指向,边权是4,C指向B,边权是3 D指向C,边权是4
⑧private void checkRep()
在每次函数返回前都要利用checkRep()来检查不变量
同时,返回时需要新建一个临时变量用于返回(防御拷贝)
(3)完成ConcreteVerticesGraphTest测试用例的实现
在(1)Vertex类的实现中,已提及了Vertex类方法的测试用例,不在赘述此外,由于ConcreteVerticesGraphTest继承了GraphInstanceTest,相比之下,ConcreteVerticesGraph类中只多了toString方法,因此,只要测试这个即可
public void ConcreteVerticesGraphToString(测试ConcreteVerticesGraph类中的toString方法)
先构造一个图:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
测试输出的信息是否相同
结果:
测试结果如下图所示:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(此结果也验证了Problem 1 中的GraphInstanceTest测试用例的成功)
3.1.4 Problem 3: Implement generic Graph
3.1.4.1 Make the implementations generic
实现思路:
(1)实现ConcreteEdgesGraph类和ConcreteVerticesGraph类的泛化
(2)依旧满足ConcreteEdgesGraphTest、ConcreteVerticesGraphTest测试用例
过程:
①在工具栏->编辑->查找/替换…中将ConcreteEdgesGraph类和ConcreteVerticesGraph类的所有String替换为L,注意toString方法的返回值依旧为String;但是测试用例中依旧是String不变
②所有的(包括测试用例)Edge改为Edge、Vertex改为Vertex
③所有的构造器都需要发生更改如new ConcreteEdgesGraph()要改为new ConcreteEdgesGraph()
结果:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
测试用例依旧通过(这个实验的最终代码将是L泛化过的)
3.1.4.2 Implement Graph.empty()
实现思路:
(1)完成Graph.empty()方法
(2)为GraphStaticTest添加新的测试用例,使得点的名字可以是几种不同的不可变类型,并且测试用例通过且无警告
过程:
①在Graph中找到empty方法,它需要一个实例类,我选择的是 ConcreteEdgesGraph类,对里面的类型进行参数化后(在类型后面加上),还需要再方法声明的static后面也加上(因为不能对非静态类型 L 进行静态引用)
②将GraphInstanceTest 中的测试用例稍作修改(主要是点的名字),添加到GraphStaticTest中,我采用的几种不可变类型是Integer和Double
结果:
所有的实现和测试用例都没有警告和错误:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
且GraphStaticTest中所有的测试用例均通过:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
实现思路:
添加额外的用于测试的语料库txt文件,在GraphPoetTest 添加新的测试用例
过程:
①public void testGraphPoetPoem() throws IOException (测试GraphPoet类中的poem方法)
第一次测试是已给的样例文件mugar-omni-theater.txt
第二次测试是额外写的文件testGraphPoetPoem.txt
检查包括:两个单词之间没有出现bridge,输出不变;两个单词之间出现一个bridge,输出中有对应的bridge;两个单词之间出现了多个(两个)bridge(又分为最大权重的bridge只有一个,输出中有这一个bridge,最大权重的bridge有多个,输出中有第一个访问的bridge)检查包括:bridge是为小写,单词是为原来的大小写检查包括:有字符串不在图中,会依旧保留为输出检查包括:添加一个bridge,添加多个(两个)bridge
②public void testGraphPoetToString() throws IOException(测试GraphPoet类中的toString方法)
测试额外写的文件testGraphPoetToString.txt,toString方法的输出应该和预期相同
③构造函数的测试用例
由于其他方法都需要调用到构造函数,其他方法的测试成功就说明这个方法的测试成功
④额外的测试文件:testGraphPoetPoem.txt和testGraphPoetToString.txt
前者用于额外测试GraphPoet类中的poem方法,后者用于测试GraphPoet类中的toString方法,更加的完整,测试覆盖面更广
结果:
由于此时还没有实现GraphPoet类,Problem 1只是编写了测试用例,还没有具体实现(在紧接下一个问题3.1.5.2中实现),因此此时并没有成功截图,在下一个问题3.1.5.2中会给出3.1.5.2 Implement GraphPoet
实现思路:
完善GraphPoet 类,添加了新的方法,加强了spec,添加新的测试用例
过程:
①GraphPoet 类是不可变的
②AF:AF(graph) = 这是一个图,顶点是单词;单词之间有边说明这两个单词相邻;边的权重是起点终点单词在一起的次数
③RI: graph中的每一个点都是非空的
④Safety from rep exposure:graph是私有的且是final的每个方法结束后都有checkRep()
⑤包括一个私有属性graph,来记录语料库信息
⑥包括四个方法以及对应的spec
Create a new poet with the graph from corpus (as described above).
@param corpus text file from which to derive the poet’s affinity graph
@throws IOException if the corpus file cannot be found or read
public GraphPoet(File corpus) throws IOException(构造函数,读取文件内容来构造一个图 )
反复按行读取text文件里面的数据,直到读取到末尾。对于每一行,先全部转换为小写字母,再去除所有的空格,元素(单词)以字符串的形式保存在字符串数组中。遍历这一行的每一个单词,第一次遍历时,将第一个单词加入图中,后续遍历时,每次处理当前单词和前一个单词,如果它们间已经有边(前一个单词->当前单词)了,就将这条边的权重加一;如果没有边,就设置边权为1

Generate a poem.
加强规范:如果有多个bridge可供插入,选择其中两条边权重和最大的那个,如果有多个最大值,选择先查找的那个bridge
@param input string from which to create the poem
@return poem (as described above)
public String poem(String input)(利用给定的input,根据图来扩展)
新建一个临时变量用于返回,先去除输入中的所有空格,元素(单词)以字符串的形式保存在字符串数组中。遍历所有的单词,每次处理当前单词和下一个单词,遍历当前单词指向的所有点,如果其中存在某点又指向了下一个单词,则将这点以及这两条边的权重的和加入bridges。遍历bridges中的所有bridge,找到权重最大的那个bridge,然后在临时变量里面加入当前单词 + bridge + 下一个单词。最后返回临时变量

返回图的所有信息
类似Vertex类中的toString方法
【】包围的是点的名字,左边是指向它的点,右边是它指向的点;
-()->中的值是箭头两端的点构成的边权;
出现句号说明,某个点指向的或被指向的点已经罗列完毕,后面是下一个点的内容关系
比如:"c-(1)->【a】-(2)->b。a-(2)->【b】-(2)->c。b-(2)->【c】-(1)->a。"表示:
a被c指向,边权是1;a指向b,边权是2。
b被a指向,边权是2;b指向c,边权是2。
c被b指向,边权是2;c指向a,边权是1
@Override public String toString()

private void checkRep()
在每次函数返回前都要利用checkRep()来检查不变量
同时,返回时需要新建一个临时变量用于返回(防御拷贝)
结果:
(补充上一个问题3.1.5.1最后的结果)测试用例通过如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.1.5.3 Graph poetry slam
实现思路:
添加新的语料库txt文件,在Main中输入一些关键词,来输出一些诗歌
过程:
新的语料库txt文件:updateMain.txt里面是我本人写的诗歌,采用了对偶的手法,具有立意明确、通俗易懂积极正能量的特点结果:通过输入关键词,可以进行扩展补全(吟诗)输入输出如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.1.6 Before you’re done
(1)已按照说明检查程序。
(2)如何通过Git提交当前版本到GitHub上你的Lab2仓库。
①到要上传的文件夹里面cd /c/Users/10025/Desktop/xxx
②初始化本地git仓库git init
③选择上传的文件git add .
④添加修改日志git commit -m “xxx”
⑤添加仓库urlgit remote add origin https://github.com/ComputerScienceHIT/Lab1-1180301002
⑥本地同步git pull origin master
⑦认证git config --global user.name "1180301002"git config --global user.email “[email protected]
⑧上传git push -u origin master
(3)在这里给出你的项目的目录结构树状示意图。
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.2 Re-implement the Social Network in Lab1
本问题要求利用3.1中已经完成的Graph和它的两种实现,来再次完成实验一中的P3,我选择的Graph实现是ConcreteEdgesGraph
3.2.1 FriendshipGraph类
实现思路:
尽可能地复用3.1中已经实现的方法来完成FriendshipGraph类中四个方法
过程:
①FriendshipGraph类是可变的
②AF:AF(graph) = 社交网络里面有人,人有名字,人和人之间有单向关系(也可以双向)
③RI: true
④Safety from rep exposure:属性私有且用final修饰
⑤包括一个私有属性graph,来记录社交网中的人和社交网中的关系
⑥包括四个方法和对应的spec
把person加入社交网中
@param person 是要加入的人
@return 返回若为真,表示成功加入;返回若为假,表示加入失败
public Boolean addVertex(Person person)
加入点之前要先遍历graph.vertices()中的所有点,来判断图中是否有同名的点,如果有,则添加失败,提示错误信息,抛出异常后直接结束程序(实验一手册要求并且老师强调);如果没有就利用graph.add()方法加入图中,返回true,表示添加成功

在社交网中添加从person1到person2的单向边
@param person1是作为起点的人
@param person2是作为终点的人
@return 返回若为真,表示成功加入;返回若为假,表示加入失败
public Boolean addEdge(Person person1, Person person2)
首先判断输入的点是否在图中,如果不在,提示错误信息,返回false,表示添加失败;然后利用graph.set(person1, preson2, 1)的返回值来判断要添加的边是否已经添加过了。如果返回0,说明没有添加过,添加进去即可,返回true,表示添加成功;否则,提示错误信息,返回false,表示添加失败

计算并返回person1和person2在图中的最短路径的距离
@param person1是最短路径上的起点
@param person2是最短路径上的终点
@return person1和person2在社交网中的最短路径的距离;如果没有这样的路径,返回-1;自己和自己的最短路径是0
public int getDistance(Person person1, Person person2)
如果person1和person2其中有一个不在社交网中,直接返回-1;否则,则利用BFS进行找出最短路径, 与实验一不同的是,实验一用邻接矩阵储存点与点的关系(有向边),而实验而直接调用graph.targets()即可。注意需要保存在最短路径上,某个结点的上一个结点,用于回溯求长度

客户端
@param args是main函数的形式参数
public static void main(String[] args)
对于客户端的public static void main(String[] args)方法将在3.2.3中详细说明,此处不再赘述

结果:
符合实验一中指导手册上的要求并且实验一中的测试用例依旧通过(测试通过的图片在3.2.4中给出,此处不再赘述)

3.2.2 Person类实现
思路:
Person类除了实验一中的private的姓名、构造函数以、获取获取这个人姓名的方法,新加了checkRep用于检查不变量(人名字不能为空)
过程:
①Person类是可变的
②AF:AF(name) = 一个人有名字
③RI: 名字不能为空
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)且属性私有
每次执行构造函数以及函数返回返回前,都要checkRep
当获取函数返回时,重新新建了一份(防御拷贝)
⑤包括一个私有属性name,是人的名字
⑥有五个方法以及对应的spec(hashCode和equals由IDE自动生成)
构造函数
@param string是构造出来的人的名字
public Person(String string)

private void checkRep()

获取一个人的名字
@return 这个人的名字
public String getName()
新建一个临时变量用于返回该Person的名字(防御拷贝) 结果:符合实验一中指导手册上的要求并且实验一中的测试用例依旧通过(测试通过的图片在3.2.4中给出,此处不再赘述)

3.2.3 客户端main()
实现思路:
根据实验二的实验报告,要求使用实验一的客户端一样能输出正确的值
过程:
从实验一的P3中直接复制代码到实验二P2代码中,并没有改变
结果:
输出与之前一致,如下图:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.2.4 测试用例
实现思路:
(1)对不同人但同名的异常进行测试
(2)对同人同名的异常进行测试
(3)对添加点的方法的测试
(4)对添加边的方法的测试
(5)对判断两个点的最小路径的方法的测试(测试图如下)
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(6)额外添加了对Person类的测试
过程:
首先要先定义规则,否则测试用例不会通过(因为抛出异常而终止)@Rulepublic ExpectedException thrown = ExpectedException.none();
(1)testSameNameDifferentPerson() throws IllegalArgumentException
①添加a后,添加一次aa(a与aa的名字都是“A”)
②判断抛出异常信息的内容是否相同
(2)testSameNameSametPerson() throws IllegalArgumentException
①添加a后再添加一次a
②判断抛出异常信息的内容是否相同
(3)testAddVertex()
①正确输入未重复的人名
②判断返回的内容是否相同
(4)testAddEdge()
①正确输入未重复的边
②输入重复的边
③输入的边上的点不在图中,分为起始点不在图中,终点不在图中,两个点都不在图中
(5)public void testGetDistance()
先构造一个社交网络如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
①输入相同的两个点。
②输入可达的两个点,分为距离为1(相邻),距离大于1 (不相邻)
③输入不可达的两个点,分为两个点都在图中但不可达,其中有点不在图中导致的不可达
④可达的路径有两条,结果要能选择最短的那条。
(6)public void testPersonGetName()
①先新建一个名字
②再调用getName方法来测试返回是否相等
结果:
之前的以及新添加的测试用例都通过了,结果如下图:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
测试过程中的错误信息输出如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.2.5 提交至Git仓库
(1)如何通过Git提交当前版本到GitHub上你的Lab3仓库。
①到要上传的文件夹里面cd /c/Users/10025/Desktop/xxx
②初始化本地git仓库git init
③选择上传的文件git add .
④添加修改日志git commit -m “xxx”
⑤添加仓库urlgit remote add origin https://github.com/ComputerScienceHIT/Lab1-1180301002
⑥本地同步git pull origin master
⑦认证git config --global user.name "1180301002"git config --global user.email “[email protected]
⑧上传git push -u origin master
(2)在这里给出你的项目的目录结构树状示意图。
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.3 Playing Chess
3.3.1 ADT设计/实现方案
按照实验手册的要求设计了:Game、Player、Board、Piece、Position、Action;
此外还设计了:GoAction、ChessAction;
客户端程序是:MyChessAndGoGame
(1)Game类
(为了方便客户使用以及隐藏其他类:在主函数里面只会调用这个类)
①Game 类是不可变的
②AF(action, player1, player2, board) = 一盘游戏包括玩家、棋盘、下棋行动
③RI:true
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)属性私有且用final修饰
⑤包括一个四个私有属性:
action是下棋的行动
player1是第一个玩家
player2是第二个玩家
board是棋盘
⑥包括十四个方法以及对应的spec
构造函数
@param type 是游戏种类
@param name1 是玩家1的名字
@param name2 是玩家2的名字
public Game(String type,String name1,String name2)
创建空的行动,初始化行动,从Action类中获取玩家1、玩家2、棋盘

获取棋盘大小
@return 棋盘大小
public int getBoardSize()
调用board的方法getSize作为返回值

得到该位置的占用情况
@param x 是该位置的横坐标
@param y 是该位置的纵坐标
@return 如果有棋子,返回真;如果没有棋子,返回假
public boolean getBoardPlace(int x, int y)调用board的方法getPlace来判断

查询该位置的占用信息,并记录在走棋历史中
@param name 是正在进行查询操作的玩家名字
@param x 是该位置的横坐标
@param y 是该位置的纵坐标
@return 该位置的的信息(名字、所属玩家姓名,或没有棋子)
public String getBoardPlaceMessage(String name , int x, int y)
调用board的方法getPlace来判断根据参数name来判断是player1还是player2,也因此两位玩家的名字不能重复(在客户端判断),再调用player1(2)的addStep方法来添加走棋历史记录

得到该位置的棋子的名字
@param x 是该位置的横坐标
@param y 是该位置的纵坐标
@return 该位置的棋子的名字
public String getBoardPieceName(int x, int y)
调用board的方法getPiece来获得棋子调用棋子的getPieceName方法来得到棋子的名字

获取玩家1的姓名
@return 玩家1的姓名
public String getPlayer1Name()调用player1的getName()方法

获取玩家2的姓名
@return 玩家2的姓名
public String getPlayer2Name()
调用player2的getName()方法

得到叫name的玩家的走棋历史
@param name 是玩家的姓名
@return 他的走棋历史
public List getHistory(String name)
根据参数name来判断是player1还是player2,因此两位玩家的名字不能重复(在客户端判断),再调用player1(2)的getHistory方法查看其走棋历史记录

叫name的玩家落子(围棋:游戏过程中落子;国际象棋:在初始化棋盘时落子),并记录在走棋历史中
@param name 是玩家名字
@param x 是落子位置的横坐标
@param y 是落子位置的纵坐标
@return 如果落子成功,返回真;落子失败,返回假
public boolean placePiece(String name, int x, int y)
根据参数name来判断是player1还是player2再调用其placePiece方法。此外为了隐藏Piece这个类,调整了Action中的placePiece类的参数列表(去掉了Piece piece这个参数),围棋的棋子是在这个方法里面新建的(理论无限个棋子)

叫name的玩家移子(国际象棋),并记录在走棋历史中
@param name 是玩家名字
@param sourceX 是棋子的起始位置的横坐标
@param sourceY 是棋子的起始位置的纵坐标
@param targetX 是棋子的目标位置的横坐标
@param targetY 是棋子的目标位置的纵坐标
@return 如果移子成功,返回真;如果移子失败,返回假
public boolean movePiece(String name, int sourceX, int sourceY, int targetX, int targetY)
由参数name来判断是player1还是player2,再调用action的movePiece方法

叫name的玩家提子(围棋),并记录在走棋历史中
@param name 是玩家名字
@param x 是棋子位置的横坐标
@param y 是棋子位置的纵坐标
@return 如果提子成功,返回真;如果提子失败,返回假
public boolean removePiece(String name, int x, int y)
由参数name来判断是player1还是player2,再调用action的removePiece方法

叫name的玩家吃子(国际象棋),并记录在走棋历史中
@param name 是玩家名字
@param sourceX 是棋子的起始位置的横坐标
@param sourceY 是棋子的起始位置的纵坐标
@param targetX 是棋子的目标位置的横坐标
@param targetY 是棋子的目标位置的纵坐标
@return 如果吃子成功,返回真;如果吃子失败,返回假
public boolean eatPiece(String name, int sourceX, int sourceY, int targetX, int targetY)
由参数name来判断是player1还是player2,再调用action的eatPiece方法

计算叫name的玩家在棋盘上的棋子数目,并记录在走棋历史中
@param name 是玩家的姓名
@return 该位玩家在棋盘上的棋子数目
public int getNumber(String name)
由参数name来判断是player1还是player2,再调用其getPieces方法得到其在场上所拥有的所有棋子的集合,再利用集合的size方法判断该玩家场上拥有的的棋子总数

给叫name的玩家的走棋历史添加跳过步骤
@param name 是玩家的姓名
public void addPassStep(String name)
由参数name来判断是player1还是player2,再调用其addStep方法添加进历史记录

给叫name的玩家的走棋历史添加投降步骤
@param name 是玩家的姓名
public void addEndStep(String name)
由参数name来判断是player1还是player2,再调用其addStep方法添加进历史记录

(2)Player类
(这个类对客户端隐藏)
①Player类是可变的
②AF(name, pieces, history) = 一个玩家有名字,下棋时有棋子和走棋历史
③RI:名字不能为空
④Safety from rep exposure:
数据类型虽然可变,但属性私有且用final修饰
当获取函数返回时,重新新建了一份(防御拷贝),防止意外修改
每次方法调用结束时,都要checkRep
⑤包括三个私有属性:
name是玩家名字
pieces是某个玩家在棋盘上拥有的所有棋子的集合
history是某个玩家的走棋历史
⑥包括八个方法以及对应的spec:
构造函数
@param name 是玩家的名字
public Player(String name) 通过名字构造一个人

获取玩家姓名
@return 玩家的姓名
public String getName()
新建一个临时变量用于返回(防御拷贝)

获取玩家在棋盘上拥有的棋子
@return 玩家在棋盘上拥有的棋子
public Set getPieces()
新建一个临时变量用于返回(防御拷贝)

获取玩家走棋历史
@return 玩家的走棋历史
public List getHistory()新建一个临时变量用于返回(防御拷贝)

private void checkRep()

把piece添加给这位玩家(围棋:游戏过程落子时,添加给该位玩家;国际象棋:在初始化棋盘时落子,添加给该位玩家)
@param piece 是将要添加的棋子
@return 返回真,表示成功加入;返回假,表示加入失败
public boolean addPiece(Piece piece)如果pieces里面已经有这个棋子了,加入失败;否则,加入成功

把这位玩家的piece移除(围棋:对方提子;国际象棋:我方被吃)
@param piece 是将要移除的棋子
@return 返回真,表示成功移除;返回假,表示移除失败
public boolean removePiece(Piece piece)如果pieces里面已经有这个棋子了,移除成功;否则,移除成功

把step加入到走棋历史中
@param step 是下棋中的某一步
public void addStep(String step)利用history的add方法加入走棋历史

(3)Board类
(这个类对客户端隐藏)
①Board 类是可变的
②AF( size, type, pieces, place) = 棋盘有大小、种类,记录着某位置是否有棋子、某位置上棋子的类型
③RI:棋盘类型不能为空棋盘大小必须为正整数
④Safety from rep exposure:
数据类型虽然可变,但属性私有且用final修饰
当获取函数返回时,重新新建了一份(防御拷贝),防止意外修改
每次方法调用结束时,都要checkRep
⑤包括一个四个私有属性:
size是棋盘大小。围棋:19;国际象棋:8
type表示棋盘的类型。围棋:go;国际象棋:chess
pieces[][]保存着棋盘上的所有棋子。围棋:0 ~ 18;国际象棋:0 ~ 7
place[][]保存着该位置上是否有棋子。围棋:0 ~ 18;国际象棋:0 ~ 7;值为真:有棋子;值为假:没有棋子
⑥包括九个方法以及对应的spec:
构造函数
@param type 是棋盘的类型
@param size 是棋盘的大小
public Board(String type, int size)
给定棋盘类型、棋盘大小来构造一个棋盘,并把每一个place[][]都设置为false

获取棋盘大小
@return 棋盘的大小
public int getSize()
返回棋盘大小size

private void checkRep()
获取棋盘种类
@return 棋盘种类
public String getType()
新建一个临时变量用于返回(防御拷贝)

获取某位置的棋子
@param x 是位置的横坐标
@param y 是位置的纵坐标
@return 该位置对应的棋子
public Piece getPiece(int x,int y)
在调用时检查x、y的合法性,返回对应位置的棋子pieces[x][y]

获取某位置是否有棋子
@param x 是该位置的横坐标
@param y 是该位置的纵坐标
@return 如果有棋子,返回真;如果没有棋子,返回假
public boolean getPlace(int x,int y)
在调用时检查x、y的合法性,由place[x][y]的真假来返回真假

将piece放置在棋盘的(x,y)位置上,会覆盖之前的棋子
@param piece 是将要放置的棋子
@param x 是将要放置位置的横坐标
@param y 是将要放置位置的纵坐标
public void setPiece(Piece piece,int x,int y)
直接修改pieces[x][y]为piece和place[x][y]为真,此外还要调用piece的setPosition方法保证“棋盘上有棋子,棋子在棋盘上”

将棋盘(x,y)的位置状态改为已经有棋子
@param x 是该位置的横坐标
@param y 是该位置的纵坐标
public void setPlace(int x,int y)修改place[x][y]为true

将棋盘(x,y)的位置状态改为没有棋子
@param x 是该位置的横坐标
@param y 是该位置的纵坐标
public void setUnplace(int x,int y)修改place[x][y]为false

(4)Piece类
(这个类对客户端隐藏)
①Piece类是可变的
②AF(pieceName, playerName, position) = 棋子有名字,有所属的玩家名字、有位置
③RI:棋子姓名、所属玩家姓名不能为空
④Safety from rep exposure:
数据类型虽然可变,但属性私有且用final修饰
当获取函数返回时,重新新建了一份(防御拷贝),防止意外修改
每次方法调用结束时,都要checkRep
⑤包括一个三个私有属性:
pieceName是该棋子的名字(可用于代表种类),围棋:white、black;国际象棋:whiteKing、whiteQueen、blackRook、blackBishop、whiteKnight、whitePawnplayerName表示该棋子的所属玩家的名字
position是该棋子的位置
⑥包括六个方法以及对应的spec:

private void checkRep()

构造函数
@param pieceName 是该棋子的姓名(种类)
@param playerName 是该棋子所属于的玩家的姓名
public Piece(String pieceName, String playerName)
根据棋子的姓名、棋子所属于的玩家的姓名新建一个棋子

获取棋子的名字
@return 该棋子的名字
public String getPieceName()
新建一个临时变量用于返回(防御拷贝)

获取棋子的所属玩家的名字
@return 棋子的所属玩家的名字
public String getPlayerName()新建一个临时变量用于返回

获取棋子的位置
@return 该棋子的位置
public Position getPosition()
由于Positian类是不可变的,因此根据rep中的position新建了一个用于返回

修改棋子的位置
@param x 是棋子的横坐标
@param y 是棋子的纵坐标
public void setPosition(int x, int y)
由于Positian类是不可变的,因此新建了一个赋值给rep中的position

(5)Position类
(这个类对客户端隐藏)
①Position类是不可变的
②AF(x,y) = 棋子在棋盘上的位置是(x,y)
③RI: x与y必须为正整数
④Safety from rep exposure:数据类型不可变(没有修改值的方法)rep不可变且属性私有每次方法调用结束时,都要checkRep
⑤包括一个两个私有属性:x是棋子的横坐标y是棋子的纵坐标
⑥包括六个方法以及对应的spec:(hashCode和equals由IDE自动生成)

构造函数
@param x 是棋子的横坐标
@param y 是棋子的纵坐标
public Position(int x,int y)
给定x和y,构造一个坐标

获取棋子的横坐标
@return 棋子的横坐标
public int getX()
返回x

获取棋子的纵坐标
@return 棋子的横坐标
public int getY()
返回y

private void checkRep()

自动生成的hashCode方法
@Override public int hashCode()

自动生成的equals方法
@Override public boolean equals(Object obj)

(6)Action接口
(这个接口对客户端隐藏)
①Action是接口,对应的实例类是GoAction和ChessAction
②无rep
③包括九个方法以及对应的spec:

静态工厂方法
@param type是游戏的种类
@return 一个新建的空的Action类
public static Action empty(String type)
根据输入的参数,新建一个空的围棋行动或是国际象棋行动并返回

获取玩家1
@return 这个玩家
public Player getPlayer1();

获取玩家2
@return 这个玩家
public Player getPlayer2();

获取棋盘
@return 这个棋盘
public Board getBoard();

根据不同的实例类
围棋:新建棋盘、新建玩家
国际象棋:新建棋盘、新建玩家、新建棋子、分配棋子
@param name1 是玩家1的名字
@param name2 是玩家2的名字
public void initialize(String name1, String name2);

放置棋子(对于围棋,游戏过程中落子;对于国际象棋,在初始化棋盘时)
给定“棋手、一颗棋子、指定位置的横坐标、指定位置的纵坐标”作为输入参数,
将该棋手的该颗棋子放置在棋盘上
需考虑异常情况,例如:该棋子并非属于该棋手、指定的位置超出棋盘的范围
指定位置已有棋子、所指定的棋子已经在棋盘上等等
@param player 是玩家
@param piece 是棋子
@param x 是落子位置的横坐标
@param y 是落子位置的纵坐标
@return 如果落子成功,返回真;如果落子失败,返回假
public boolean placePiece(Player player,Piece piece,int x,int y);

移子(针对国际象棋)
给定“棋手、初始位置和目的位置的横纵坐标”,
将处于初始位置的棋子移动到目的位置。需要考虑处理各种异常情况,
例如:指定的位置超出棋盘的范围、目的地已有其他棋子、初始位置
尚无可移动的棋子、两个位置相同、初始位置的棋子并非该棋手所有等等
@param player 是玩家
@param sourceX 是初始位置的横坐标
@param sourceY 是初始位置的纵坐标
@param targetX 是目标位置的横坐标
@param targetY 是目标位置的纵坐标
@return 如果移子成功,返回真;如果移子失败,返回假
public boolean movePiece(Player player,int sourceX,int sourceY,int targetX,int targetY);

提子(针对围棋)
给定“棋手、一个位置的横纵坐标”,将该位置上的
对手棋子移除。需要考虑处理异常情况,例如:该位置超出棋盘的范围、
该位置无棋子可提、所提棋子不是对方棋子等等
@param player 是玩家
@param x 是一个位置的横坐标
@param y 是一个位置的纵坐标
@return 如果提子成功,返回真;如果提子失败,返回假
public boolean removePiece(Player player,int x,int y);

吃子(针对国际象棋)
给定“棋手、两个位置横纵坐标”,将第一个位置上的棋子移动至第二个位置,
第二个位置上原有的对手棋子从棋盘上移除。需要处理异常情况,
例如:指定的位置超出棋盘的范围、第一个位置上无棋子、第二个位置上无棋子、
两个位置相同、第一个位置上的棋子不是自己的棋子、第二个位置上的棋子不是对方棋子(是己方的)等等
@param player 是玩家
@param sourceX 是要吃其他棋子的棋子的横坐标
@param sourceY 是要吃其他棋子的棋子的纵坐标
@param targetX 是被吃棋子的横坐标
@param targetY 是被吃棋子的纵坐标
@return 如果吃子成功,返回真;如果吃子失败,返回假
public boolean eatPiece(Player player,int sourceX,int sourceY,int targetX,int targetY);

(7)GoAction类(实现Action接口)
(这个类对客户端隐藏)
①GoAction 类是可变的
②AF(player1, player2, board) = 围棋的行动由玩家来做,在棋盘上实现
③RI:true
④Safety from rep exposure:
除了getxx方法,其余方法返回仅为真假
⑤包括三个私有属性:
player1是玩家1
player2是玩家2
board是棋盘
⑥包括八个方法:
@Override public Player getPlayer1()
返回player1

@Override public Player getPlayer2()
返回player2

@Override public Board getBoard()
返回board

@Override public void initialize(String name1, String name2)
由name1新建玩家1,由name2新建玩家2,由于围棋一开始是没有棋子的,因此不必摆棋子

@Override public boolean placePiece(Player player, Piece piece, int x, int y)
先根据spec的要求,对于不合法情况,输出错误提示信息,并返回假;对于合法情况,先调用棋子的setPosition方法设定其位置,还要调用棋盘的setPiece方法和setPlace方法来使得棋盘上有棋子,最后调用玩家的addPiece方法,将该棋子加入到玩家的场上拥有棋子的集合中,返回真

@Override public boolean movePiece(Player player, int sourceX, int sourceY, int targetX, int targetY)
围棋不能移子,直接返回假

@Override public boolean removePiece(Player player, int x, int y)
先根据spec的要求,对于不合法情况,输出错误提示信息,并返回假;对于合法情况,先由参数player判断是player1还是player2(由于两位玩家的名字必须不同,因此这里可以用名字来判断),再调用player1(2)的removePiece方法移除该棋子,最后调用棋盘的setUnplace方法,将该位置设置为没有棋子,返回真

@Override public boolean eatPiece(Player player, int sourceX, int sourceY, int targetX, int targetY)
围棋不能吃子,直接返回假

(8)ChessAction(实现Action接口)
(这个类对客户端隐藏)
①ChessAction类是可变的
②AF(player1, player2, board) = 国际象棋的行动由玩家来做,在棋盘上实现
③RI:true
④Safety from rep exposure:
除了getxx方法,其余方法返回仅为真假
⑤包括一个三个私有属性:
player1是玩家1
player2是玩家2
board是棋盘
⑥包括八个方法:
@Override public Player getPlayer1()
返回player1

@Override public Player getPlayer2()
返回player2

@Override public Board getBoard()
返回board

@Override public void initialize(String name1, String name2)
由name1新建玩家1,由name2新建玩家2,由于国际象棋一开始是有棋子的,因此需要摆棋子,先新建白棋黑棋(共32个),再利用同一个类中的placePiece方法进行摆棋

@Override public boolean placePiece(Player player, Piece piece, int x, int y)
由于国际象棋游戏时不能落子,不必像GoAction中的placePiece输出错误提示信息,对于不合法情况,直接返回假;对于合法情况,先调用棋子的setPosition方法设定其位置,还要调用棋盘的setPiece方法和setPlace方法来使得棋盘上有棋子,最后调用玩家的addPiece方法,将该棋子加入到玩家的场上拥有棋子的集合中,返回真

@Override public boolean movePiece(Player player, int sourceX, int sourceY, int targetX, int targetY)
先根据spec的要求,对于不合法情况,输出错误提示信息,并返回假;对于合法情况,先调用棋盘的getPiece得到位于初始位置的棋子,再调用board的setPiece方法将该棋子放到目标位置,然后调用该棋子setPosition方法改变其位置,最后调用棋盘的setPlace方法把目标位置设置为已有棋子并调用setUnplace方法把初始位置设为没有棋子(处于初始位置的棋子已经移动到了目标位置),返回真

@Override public boolean removePiece(Player player, int x, int y)
国际象棋不能提子,直接返回假

@Override public boolean eatPiece(Player player, int sourceX, int sourceY, int targetX, int targetY)
先根据spec的要求,对于不合法情况,输出错误提示信息,并返回假;对于合法情况,先调用棋盘的getPiece得到位于目标位置的棋子,再由参数player判断是player1还是player2(由于两位玩家的名字必须不同,因此这里可以用名字来判断),调用player1(2)的removePiece方法移除该棋子(被吃掉了),然后调用棋盘的getPiece得到位于初始位置的棋子并调用setPiece将其移动到目标位置,再然后调用棋盘的getPiece得到处于目标位置的棋子并调用该棋子的setPosition方法修改位置坐标,最后调用棋盘的setUnplace把初始位置设置为没有棋子(处于初始位置的棋子已经吃掉了原本在目标位置的棋子)
各ADT间的关系如下(由eclipse自动生成)
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(生成方法见https://blog.csdn.net/qq_28849009/article/details/105385573)
3.3.2 主程序MyChessAndGoGame设计/实现方案
①MyChessAndGoGame类是不可变的,包括main方法,是启动游戏的类,只调用了Game类。因此,用户在MyChessAndGoGame类中的操作,对应的就是Game类中的方法
②AF() = 游戏启动的客户端程序
③RI:true
④Safety from rep exposure:
数据类型不可变(没有修改值的方法)只调用了Game类,对其他的类做了隐藏处理
⑤无rep
⑥包括七个方法以及对应的spec:
开始游戏
@param args 是形式参数

public static void main(String[] args)
这是main方法,调用MyChessAndGoGame().getReady来启动游戏

准备阶段
包括选择游戏类型,选择两位玩家的姓名
public void getReady()
先要求用户输入chess开始一局国际象棋游戏;输入go开始一局围棋游戏,如果输入不是chess或者go,会提示输入错误,并要求重新输入;再要求用户输入两位玩家的姓名(从玩家1开始),若两位玩家名字为空,或者相同,会提示错误,并要求重新输入(还是从从玩家1开始)。确保游戏类型和玩家名字都合法后,用这三个作为参数,调用Game类的构造函数,新建game类,将game作为参数调用本类中的chessGame(国际象棋)或goGame(围棋),开始游戏截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(这里以go为例,只演示合法输入,非法输入在3.3.3中演示)

验证输入的横纵坐标是否是两个正整数
@param xy 是字符串数组,保存着两个数的字符串形式
@return 如果确实是两个正整数,返回真;如果不是两个正整数,返回假
public boolean positionIsNonnegative(String[] xy)
用于验证用户输入的坐标的合法性(两个正整数),如果不合法,会提示错误并返回假,否则返回真(非法输入在3.3.3中演示)

围棋的游玩阶段
@param game 是本局游戏
public void goGame(Game game)
先输出围棋的操作方法:输入1:落子;输入2:提子;输入3:查询棋盘上的某个位置;输入4:计算两位玩家在棋盘上的棋子数目;输入5:跳过(结束当前行动);输入end:投降。然后让玩家依次轮流的选择,如果选择不合法(不是上述的1 2 3 4 5 end),则会提示错误。否则将根据game、正在行动的玩家、缓冲区、选择的行动调用本类的goChoose方法进行该玩家的行动。截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(这里只演示落子的合法输入,其他的合法输入见下,此外非法输入在3.3.3中演示)
但要注意的是:只有成功行动(成功落子、成功提子、成功查询)才会加入到该玩家的走棋历史记录中。此外,goChoose在一个玩家的行动过程中,只会调用一次,说明行动成功后不可逆(不能悔棋等等),并且一旦选定了行动方式,就必须行动(比如确定落子,就必须落子)在调用goChoose方法结束后,若该玩家输入的是end,则结束游戏并输出两位玩家的走棋历史记录,否则轮到下一位玩家进行行动
截图如下:已经提前下好了一盘棋
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
围棋的行动(选择)阶段
@param game 是本局游戏
@param choose 是选择,只能包括输入1落子、输入2提子、输入3查询棋盘上的某个位置、输入4计算两位玩家在棋盘上的棋子数目、输入5跳过(结束当前行动)、输入end投降 @param name 是正在行动的玩家姓名
@param input 是缓冲区
public void goChoose(Game game, String choose, String name, Scanner input)
这是围棋的行动,根据参数choose来调用参数game的不同方法:
若是1(落子),则会先提示玩家选择落子位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择位置进行落子,合法则调用参数game中的placePiece方法进行落子,若落子失败,提示错误信息,重新选择落子位置进行落子;若落子成功,行动结束,轮到下一位玩家
截图如下:(这里只演示合法输入,非法输入在3.3.3中演示)
已经提前确保(0,0)的位置没有棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是2(提子),则会先提示玩家选择提子位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择位置进行提子,合法则调用参数game中的removePiece方法进行提子,若提子失败,提示错误信息,重新选择提子位置进行提子;若提子成功,行动结束,轮到下一位玩家
截图如下:(这里只演示合法输入,非法输入在3.3.3中演示)
已经提前确保(0,0)是对方的棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是3(查询棋盘上的某一个位置),则会先提示玩家选择查询位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择位置进行查询,合法则判断所选位置是否超过棋盘大小(调用参数game中的getBoardSize方法),若超过了,则提示错误信息,重新选择位置进行查询,若没有超过,则调用参数game中的getBoardPlaceMessage方法输出该位置的位置信息,行动结束,轮到下一位玩家
截图如下:(这里只演示合法输入,非法输入在3.3.3中演示)
已经提前确保(0,0)是叫A的玩家的white棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
已经提前确保(0,0)无棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是4(计算两位玩家在棋盘上的棋子数目),则会根据参数game的getNumber方法已经玩家姓名,输出玩家在棋盘上的棋子数目(每次计算,都会分别输出两位玩家在棋盘上的棋子数目),结束该玩家的行动
截图如下:已经提前确保(0,0)是叫A的玩家的棋子,其余地方没有棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是5(跳过本次行动),则会调用参数game的addPassStep方法给这位叫name(参数)的玩家添加跳过的走棋历史,结束该玩家行动截图如下: 若是end(投降并结束游戏),则会调用参数game的addEndStep方法给这位叫name(参数)的玩家添加投降的走棋历史,结束本方法的调用,随后在goGame类(调用本方法的类)中会结束游戏,并输出两位玩家的走棋历史记录
截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告

国际象棋的游玩阶段
@param game 是本局游戏
public void chessGame(Game game) 先输出国际象棋的操作方法:输入1:移子;输入2:吃子;输入3:查询棋盘上的某个位置;输入4:计算两位玩家在棋盘上的棋子数目;输入5:跳过(结束当前行动);输入end:投降。然后让玩家依次轮流的选择,如果选择不合法(不是上述的1 2 3 4 5 end),则会提示错误。否则将根据game、正在行动的玩家、缓冲区、选择的行动调用本类的chessChoose方法进行该玩家的行动。
截图如下:(这里只演示移子的合法输入,其他的合法输入见下,此外非法输入在3.3.3中演示)
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
但要注意的是:只有成功行动(成功移子、成功吃子、成功查询)才会加入到该玩家的走棋历史记录中。此外,chessChoose在一个玩家的行动过程中,只会调用一次,说明行动成功后不可逆(不能悔棋等等),并且一旦选定了行动方式,就必须行动(比如确定移子,就必须移子)在调用chessChoose方法结束后,若该玩家输入的是end,则结束游戏并输出两位玩家的走棋历史记录,否则轮到下一位玩家进行行动
截图如下:已经提前下好了一盘棋
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
国际象棋的行动(选择)阶段
@param game 是本局游戏
@param choose 是选择,包括输入1移子、输入2吃子、输入3查询棋盘上的某个位置、输入4计算两位玩家在棋盘上的棋子数目、输入5跳过(结束当前行动)
@param name 是正在行动的玩家姓名
@param input 是缓冲区
public void chessChoose(Game game, String choose, String name, Scanner input)
这是国际象棋的行动,根据参数choose来调用参数game的不同方法:若是1(移子),则会先提示玩家选择移子的初始位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择初始位置进行移子。再提示玩家选择移子的目标位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择目标位置进行移子。当初始位置、目标位置全部合法,则调用参数game中的movePiece方法进行移子,若移子失败,提示错误信息,重新选择移子的初始位置、目标位置进行移子;若移子成功,行动结束,轮到下一位玩家
截图如下:(这里只演示合法输入,非法输入在3.3.3中演示)
已经提前确保(0,0)的位置有棋子,且(0,2)位置无棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是2(吃子),则会先提示玩家选择吃子的初始位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择初始位置进行吃子。再提示玩家选择吃子的目标位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择目标位置进行吃子。当初始位置、目标位置全部合法,则调用参数game中的eatPiece方法进行吃子,若吃子失败,提示错误信息,重新选择吃子的初始位置、目标位置进行吃子;若吃子成功,行动结束,轮到下一位玩家
截图如下:(这里只演示合法输入,非法输入在3.3.3中演示)
已经提前确保(0,7)是己方的棋子,(0,2)是对方棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是3(查询棋盘上的某一个位置),则会先提示玩家选择查询位置,调用positionIsNonnegative判断输入坐标是否合法,不合法则重新选择位置进行查询,合法则判断所选位置是否超过棋盘大小(调用参数game中的getBoardSize方法),若超过了,则提示错误信息,重新选择位置进行查询,若没有超过,则调用参数game中的getBoardPlaceMessage方法输出该位置的位置信息,行动结束,轮到下一位玩家
截图如下:(这里只演示合法输入,非法输入在3.3.3中演示)
已经提前确保(0,2)是叫B的玩家的blackRook1棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
已经提前确保(0,0)无棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是4(计算两位玩家在棋盘上的棋子数目),则会根据参数game的getNumber方法已经玩家姓名,输出玩家在棋盘上的棋子数目(每次计算,都会分别输出两位玩家在棋盘上的棋子数目),结束该玩家的行动
截图如下:已经提前确保A的玩家的棋子有15个棋子,叫B的玩家有16个棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是5(跳过本次行动),则会调用参数game的addPassStep方法给这位叫name(参数)的玩家添加跳过的走棋历史,结束该玩家行动
截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
若是end(投降并结束游戏),则会调用参数game的addEndStep方法给这位叫name(参数)的玩家添加投降的走棋历史,结束本方法的调用,随后在goGame类(调用本方法的类)中会结束游戏,并输出两位玩家的走棋历史记录
截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
3.3.3 ADT和主程序的测试方案
在3.3.1中提到,Game类是MyChessAndGoGame调用的唯一类,则MyChessAndGoGame就可以作为Game类的测试用例,不必额外写测试用例
(1)主程序MyChessAndGoGame的测试方案
getReady方法的非法输入测试(合法输入已经在3.3.2中给出)
选择游戏种类时:
①测试大小写敏感
②测试空输入
③测试乱输入
测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
选择玩家姓名时:
①测试空输入:玩家1名字空输入、玩家2名字空输入
②测试两个玩家名字相同测试
截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
positionIsNonnegative方法的非法输入测试(合法输入已经在3.3.2中给出)
①第一个不是正的、第二个不是正的、都不是正的
②第一个不是整数、第二个不是整数、都不是整数(包括浮点数、字符串)
测试截图如下:(以围棋的落子时玩家输入的坐标为例)
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
goGame方法的非法输入测试(合法输入已经在3.3.2中给出)
①测试输入为空
②测试输入不是所要求的(包括浮点数、字符串)
测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
goChoose方法的非法输入测试(合法输入已经在3.3.2中给出)由于在调用时已经确保参数正确,就不必再测试参数的正确性
落子时:
①落子的横坐标超出棋盘范围、纵坐标超出棋盘范围、两者都超出棋盘范围 (大数、棋盘临界位置19)
②落子的位置已经有棋子(提前在(0,0)处放了棋子)
测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
提子时:
①提子的横坐标超出棋盘范围、纵坐标超出棋盘范围、两者都超出棋盘范围(大数、棋盘临界位置19)
②提子不是对方的棋子(提前使得(0,0)处的棋子是玩家A 的white棋子)
测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
查子时:
①查子的横坐标超出棋盘范围、纵坐标超出棋盘范围、两者都超出棋盘范围(大数、棋盘临界位置19)测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
chessGame方法的非法输入测试(合法输入已经在3.3.2中给出)
①测试输入为空
②测试输入不是所要求的(包括浮点数、字符串)
测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
chessChoose方法的非法输入测试(合法输入已经在3.3.2中给出)
由于在调用时已经确保参数正确,就不必再测试参数的正确性移子时:
①移子的初始位置横坐标超出棋盘范围、初始位置纵坐标超出棋盘范围、初始位置两者都超出棋盘范围(大数、棋盘临界位置8)
②移子的目标位置横坐标超出棋盘范围、目标位置纵坐标超出棋盘范围、目标位置两者都超出棋盘范围(大数、棋盘临界位置8)
③初始位置与目标位置相同
④移子的目标位置已经有棋子(提前在(0,1)处放了棋子)
⑤移子的初始位置没有棋子
⑥移子的初始位置的棋子不是己方玩家
测试截图如下:(由于测试内容过长,图片是分开截的)
①初始位置坐标异常
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
②目标位置坐标异常
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
③两者位置相同④目标位置有棋子了
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
⑤初始位置没有棋子⑥初始位置不是己方棋子
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
吃子时:
①吃子的初始位置横坐标超出棋盘范围、初始位置纵坐标超出棋盘范围、初始位置两者都超出棋盘范围(大数、棋盘临界位置8)
②吃子的目标位置横坐标超出棋盘范围、目标位置纵坐标超出棋盘范围、目标位置两者都超出棋盘范围(大数、棋盘临界位置8)
③初始位置与目标位置相同
④吃子的初始位置没有棋子
⑤吃子的目标位置没有棋子
⑥吃子的初始位置的棋子不是己方玩家的
⑦吃子的目标位置的棋子不是对方玩家的(是己方玩家的)
测试截图如下:(由于测试内容过长,图片是分开截的)
①初始位置坐标异常
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
②目标位置坐标异常
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
③初始位置与目标位置相同④初始位置没有棋子⑤目标位置没有棋子⑥初始位置的棋子不是己方玩家的⑦目标位置的棋子是己方玩家的
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
查子时:
①查子的横坐标超出棋盘范围、纵坐标超出棋盘范围、两者都超出棋盘范围(大数、棋盘临界位置8)
测试截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
(2)Action接口的测试ActionInstanceTest
@Test public void testActionEmpty()//测试初始时是否为空
Testing strategy
判断get方法的输出是否为null

@Test public void testGetPlayer1()//测试两个实例类中的getPlayer1方法
Testing strategy
①给定名字进行初始化
②通过名字,来判断方法的返回值是否与初始化的相同

@Testpublic void testGetPlayer2()//测试两个实例类中的getPlayer2方法
Testing strategy
①给定名字进行初始化
②通过名字,来判断方法的返回值是否相同

@Test public void testPlacePiece()//测试两个实例类中的placePiece方法
Testing strategy
①给定名字进行初始化
②对返回为真、返回为假的情况进行测试,如果返回为真还要放置前后的状态进行检查。其中,返回为假包括:该棋子并非属于该棋手,指定的位置超出棋盘的范围(负数、棋盘临界),指定位置已有棋子,所指定的棋子已经在棋盘上,其余情况返回为真

(3)ChessAction类的测试ChessActionTest继承ActionInstanceTest
@Override public Action emptyInstance()
重写ActionInstanceTest中的emptyInstance方法,返回一个新建的ChessAction()

@Testpublic void testGetBoard()//测试ChessAction类中的getBoard方法
Testing strategy
①给定名字进行初始化
②通过棋盘大小、棋盘类型,来判断方法的返回值是否相同

@Test public void testInitialize()//测试ChessAction类中的initialize方法
Testing strategy
①给定名字进行初始化
②通过棋盘上的棋子名字、棋盘上的棋子位置、棋盘上是否有棋子、棋盘上的棋子所属玩家姓名、玩家拥有的棋子数量,来判断方法的返回值是否相同

@Test public void testMovePiece()//测试ChessAction类中的movePiece方法
Testing strategy
①给定名字进行初始化
②对返回为真、返回为假的情况进行测试,如果返回为真还要对移动前后的状态进行检查。其中,返回为假包括:指定的位置超出棋盘的范围(负数、棋盘临界),目的地已有其他棋子,初始位置尚无可移动的棋子,两个位置相同,初始位置的棋子并非该棋手所有,其余情况返回为真

@Test public void testRemovePiece()//测试ChessAction类中的removePiece方法
Testing strategy
①给定名字进行初始化
②国际象棋不能提子,永远返回假

@Test public void testEatPiece()//测试ChessAction类中的eatPiece方法Testing strategy
①给定名字进行初始化
②对返回为真、返回为假的情况进行测试,如果返回为真还要对吃子前后的状态进行检查。其中,返回为假包括:指定的位置超出棋盘的范围(负数、棋盘临界),第一个位置上无棋子,第二个位置上无棋子,两个位置相同,第一个位置上的棋子不是自己的棋子,第二个位置上的棋子不是对方棋子(是己方的),其余情况返回为真

(4) GoAction类的测试GoActionTest 继承 ActionInstanceTest
@Override public Action emptyInstance()
重写ActionInstanceTest中的emptyInstance方法,返回一个新建的GoAction

@Test public void testGetBoard()//测试GoAction类中的getBoard方法
Testing strategy
①给定名字进行初始化
②通过棋盘大小、棋盘类型,来判断方法的返回值是否相同

@Test public void testInitialize()//测试GoAction类中的initialize方法
Testing strategy
①给定名字进行初始化
②通过玩家名字、玩家棋子个数、棋盘大小、棋盘种类,来判断方法的返回值是否相同

@Test public void testMovePiece()//测试GoAction类中的movePiece方法
Testing strategy
①给定名字进行初始化
②围棋不能移子,永远返回假

@Test public void testRemovePiece()//测试GoAction类中的removePiece方法
Testing strategy
①给定名字进行初始化
②对返回为真、返回为假的情况进行测试,如果返回为真还要对提子前后的状态进行检查。其中,返回为假包括:该位置超出棋盘的范围(负数、棋盘临界),该位置无棋子可提,所提棋子不是对方棋子,其余情况返回为真

@Test public void testEatPiece()//测试GoAction类中的eatPiece方法
Testing strategy
①给定名字进行初始化
②围棋不能吃子,永远返回假

(5)Board类的测试BoardTest
@Test public void testGetSize()//测试board类中的getSize方法
Testing strategy
①新建一个棋盘
②测试调用方法的返回值是否符合预期

@Test public void testGetType()//测试board类中的getType方法
Testing strategy
①新建一个棋盘
②测试调用方法的返回值是否符合预期

@Test public void testGetPiece()//测试board类中的getPiece方法
Testing strategy
①新建一个棋盘、棋子
②将该棋子放入棋盘
③测试调用方法的返回值的性质(名字、位置、所属玩家名字)是否符合预期

@Test public void testGetPlace()//测试board类中的getPlace方法
Testing strategy
①新建一个棋盘、棋子
②先调用方法,返回假
③将该棋子放入棋盘
④再调用方法,返回真

@Test public void testSetPiece()//测试board类中的setPiece方法
Testing strategy
①新建一个棋盘、棋子
②先判断放置前的情况
③调用方法将该棋子放入棋盘
④判断放置后的情况

@Test public void testSetPlace()//测试board类中的setPlace方法
Testing strategy
①新建一个棋盘
②先判断修改前的情况
③调用方法把该位置改为被占用
④判断修改后的情况

@Test public void testSetUnplace()//测试board类中的setUnplace方法
Testing strategy
①新建一个棋盘
②先判断修改前的情况
③调用方法把该位置改为未被占用
④判断修改后的情况

(6)Piece类的测试PieceTest
@Test public void testGetPieceName()//测试Piece类中的getPieceName方法Testing strategy
①给定名字、所属玩家姓名进行构造新棋子
②测试方法的返回值是否与构造棋子时的相同

@Test public void testGetPlayerName()//测试Piece类中的getPlayerName方法
Testing strategy
①给定名字、所属玩家姓名进行构造新棋子
②测试方法的返回值是否与构造棋子时的相同

@Test public void testGetPosition()//测试Piece类中的getPosition方法Testing strategy
①给定名字、所属玩家姓名进行构造新棋子
②设置该棋子的位置
③测试方法的返回值是否与设置时的相同

@Test public void testSetPosition()//测试Piece类中的setPosition方法
Testing strategy
①给定名字、所属玩家姓名进行构造新棋子
②设置该棋子的位置
③利用getPosition获取该棋子的位置,测试是否与设置时的相同

(7)Player类的测试PlayerTest
@Test public void testGetName()//测试Player类中的getName方法
Testing strategy
①给定名字新建一个玩家
②测试调用方法的返回值是否符合预期

@Test public void testGetPieces()//测试Player类中的getPieces方法
Testing strategy
①新建一个玩家、玩家的棋子
②将棋子加入到玩家棋盘上拥有的棋子的集合中
③新建临时集合,对该集合也加入该棋子
④比较两者是否相同

@Test public void testGetHistory()//测试Player类中的getHistory方法
Testing strategy
①新建一个玩家
②将走棋步骤加入到历史记录中
③新建临时集合,对该集合也加入该步骤
④比较两者是否相同

@Test public void testAddPiece()//测试Player类中的addPiece方法
Testing strategy
①新建一个玩家、棋子
②原本玩家棋盘上拥有的棋子的集合应不含该棋子
③将棋子加入到玩家棋盘上拥有的棋子的集合中
④调用getPieces方法,应该包含该棋子

@Test public void testRemovePiece()//测试Player类中的removePiece方法
Testing strategy
①新建一个玩家、棋子
②将棋子加入到玩家棋盘上拥有的棋子的集合中
③调用getPieces方法,将包含该棋子
④将该棋子从玩家棋盘上拥有的棋子的集合中移除
⑤再调用getPieces方法,将不包含该棋子

@Test public void testAddStep()//测试Player类中的addStep方法
Testing strategy
①新建一个玩家、走棋步骤
②原本历史记录应该不含有该步骤
③将走棋步骤加入到历史记录中
④历史记录中应该有该步骤

(8)Position类的测试PositionTest
@Test public void testGetX()//测试Position类中的getX方法
Testing strategy
①新建一个坐标
②测试调用方法的返回值是否符合预期

@Test public void testGetY()//测试Position类中的getY方法
Testing strategy
①新建一个坐标
②测试调用方法的返回值是否符合预期

项目的目录结构树状示意图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
上述P3中所有的测试均通过,测试用例通过截图如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
测试过程中的错误信息输出如下:
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
哈尔滨工业大学 2020年春 软件构造实验(二)Lab 2实验报告
4 实验进度
2020.3.21 23:51-02:20尝试完成P1,但阅读了英文题干,对不明白的地方进行了翻译,并查看了piazza 【未完成】
2020.3.22 20:08-22:01完成P1的Problem 1【完成预期】
2020.3.22 22:03-22:20完成P1的Problem 1 对应的报告【完成预期】
2020.3.24 19:40-23:22完成P1中Problem 2 中的Implement ConcreteEdgesGraph【完成预期】
2020.3.24 23:24-0:12完成P1中Problem 2 中的Implement ConcreteEdgesGraph的报告【完成预期】
2020.3.28 14:58-17:41完成P1中Problem 2 中的Implement ConcreteVerticesGraph【完成预期】
2020.3.28 21:08-0:43给P1中Problem 2 中的Implement ConcreteVerticesGraph添加了详细注释,并且完成了Implement ConcreteVerticesGraph的报告【完成预期】
2020.3.29 0:50-1:51完成P1中Problem 3 中的Make the implementations generic以及对应的报告【完成预期】
2020.3.29 1:54-2:54完成P1中Problem 3 中的Implement Graph.empty()以及对应的报告【完成预期】
2020.3.29 18:42-0:04完成P1中的Problem 4 ,完成P1的代码部分的要求【完成预期】
2020.3.30 18:14-22:34完善P1部分spec内容以及添加了部分注释完成P1中的Problem 4对应的报告,完成了P1所有内容【完成预期】
2020.3.31 19:24-21:25完成P2的代码实现【完成预期】
2020.3.31 21:26-22:20完成P2的注释和spec【完成预期】
2020.3.31 22:24-23:16完成P2对应的报告【完成预期】
2020.4.4 18:16-1:38尝试完成P3【未完成】
2020.4.5 18:08-1:17尝试完成P3【未完成】
2020.4.6 18:04-1:20尝试完成P3【完成预期】
2020.4.7 18:46-22:30完善了P3【完成预期】
2020.4.8 20:52-0:49完成了P3实验报告的3.3.1【完成预期】
2020.4.9 13:16-16:52完成了P3实验报告的3.3.2【完成预期】
2020.4.9 17:49-20:11完成了P3实验报告的3.3.3【完成预期】
2020.4.9 20:13-21:03所有实验内容已经包括均已完成【完成预期】

5 实验过程中遇到的困难与解决途径遇到的难点解决途径
P1中,部分英文注释不明白【使用百度翻译进行翻译】
P1中,不理解子类与父类之间的关系【上网查阅了相关的知识】
P2中的问题ImplementConcreteVerticesGraph中,JUnit测试时,出现了StackOverflow【上网查阅了相关的知识,发现是两个函数递归调用导致P2中的问题】ImplementConcreteVerticesGraph中,JUnit测试时,对toString单独测试和一起测试时,出现不一样结果【上网查阅了相关的知识,发现是调用迭代器遍历HashMap时,两次测试输出的字符串顺序不同,改为LinkedHashMap后解决】
P3中不明确放置棋子这个方法在国际象棋中的体现【查看piazza后,发现是摆棋子的时候使用】
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训
6.2 针对以下方面的感受
(1) 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
相比于直接面向应用场景编程,我认为面向ADT的编程像是在做一块一块的小零件,零件应该设计成什么样子,各个零件之间如何拼接,以及如何保护零件不被恶意破坏,最后将这些零件组合起来,形成供用户使用的产品
(2) 使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型能够将自己的ADT适用于更加多的情况中,比如图的顶点的名字可以是字符串、可以是整型、也可以是浮点数,扩大了应用的范围,但是泛型化过程中会有一定的难度,比如有时候会出现令人困惑的黄色下划线警告(在piazza上看老师的回答后已经解决了)
(3) 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
优势是在每次写下代码或是修改代码后,都能快速的进行回归测试,节约了重复写测试的时间。最开始实验一的时候还是不适应,觉得“代码还没写出来,就想着测试,会不会太快了”,但是经过本次实验二,尤其是P3,让我感受到给出ADT的规约后就开始编写测试用例的巨大优势,现在我能适 应这种测试方式了
(4) P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
一是直接调用方法,节约了大量的编程时间;二是使得代码应用场景更广,提升了代码的价值;三是在未来工作时,节约了开发成本
(5) P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?
相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?已经适应了从具体应用场景到ADT的“抽象映射”。对于P3,我在一开始设计的时候就感觉很困扰——棋子类中要不要包括所属玩家,棋子类是可变的还是不可变的,添加历史记录要在哪个类里面添加。不过随着设计的越来越完善,还是感受到了满满的成就感
(6) 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
意义就是提升可维护性,可读性,安全性。因为不能确保ADT一写完以后就不会添加新的功能,也不能保证每一个客户都是善意的。因此这些specification, invariants, RI, AF,以及防止rep exposure是一个ADT中必不可少的内容。我愿意在以后编程中坚持这么做
(7) 关于本实验的工作量、难度、deadline。
相比于实验一,工作量、难度确实提高了很多,有几天为了做实验都熬到了一两点,不过好在有四周的时间
(8) 《软件构造》课程进展到目前,你对该课程有何体会和建议?
感觉这门课越来越深入,也越来越难,不过我还是有信心能学好的

相关文章:

  • 2022-12-23
  • 2021-05-23
  • 2022-12-23
  • 2022-12-23
  • 2021-07-30
  • 2022-01-01
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-10-12
  • 2021-11-30
  • 2021-11-16
  • 2021-05-23
  • 2021-06-01
  • 2021-07-31
  • 2021-10-10
相关资源
相似解决方案