文章目录
前言
实验训练对于抽象数据类型的设计、规约、测试,同时使用OOP实现ADT。
在Lab1的基础上增加了代码量和难度,但是由于框架都给出来了,只要往里面塞代码,在项目组织上还是难度不高的。
这个博客是对我在实验中遇到的困难的总结,特别是对MIT的作业的意思的理解,以及一些关于测试的感悟。
1. 目标概述
需要能够在从问题描述中识别出ADT的基础上,设计规约,评估规约的质量,并根据其设计测试用例,之后进行ADT的泛型话,设计ADT的多种不同的实现,最后测试其实现并评估覆盖度。
2.实验过程
2.1 Poetic Walks
最难的部分:读懂MIT的要求,非常善于运用修辞手法的一个作业描述,用翻译插件不如自己读,在对应诗意漫步和语料库部分做一个详细说明。
该任务需要在获取代码后,按照测试优先的原则,先设计、记录和实施测试Graph<String>以及GraphInstanceTest;其次完成带有String的label的加权有向图ConcreteEdgesGraph以及ConcreteVerticesGraph的测试以及ADT;在此基础上,完成对泛型 Graph<L> 的编写和测试, 并实现 Graph.empty(),在GraphStaticTest中对其他不可变类label进行测试。
完成了Graph<L>后,要求对其进行应用,即在此基础上完成Poetic Walk中语料库的构建和诗歌的生成。
在整个任务过程中,需要注意spec、AF、RI、rep exposure防止方法的编写和测试策略、方法说明的编写。
2.1.1测试优先导向的测试用例编写
把所有测试都先放在这部分了。
Test Graph <String>:
根据接口Graph中每个方法的JavaDoc注释生成Testing Strategy,对每个方法的不同情况进行划分:
/**
* Testing strategy
* <p>
* vertices()、sources()、targets()不需划分
* <p>
* 划分Add():
* <pre>
* 1. 添加一个已经存在的顶点
* 2. 添加一个不存在的顶点
* </pre>
* <p>
* 划分Set():
* <pre>
* 1. 参数中weight==0,且该边不存在于图中
* 2. 参数中weight==0,且该边存在于图中
* 3. 参数中weight!=0,且该边不存在于图中
* 4. 参数中weight!=0,且该边存在于图中
* </pre>
* <p>
* 划分Remove():
* <pre>
* 1. 从图中删除一个存在的顶点
* 2. 从图中删除一个不存在的顶点
* </pre>
* <p>
* 增加测试其他不可变labels
* <pre>
* 1. Integer测试set方法
* 2. Double测试set方法
* </pre>
*/
然后编写测试用例,之后子类的Testing Strategy就和它差不多了。
TestConcreteEdgesGraph:
要对内部类Edge进行测试,其他和父类差不多:
// Testing strategy for Edge
// TODO
// 1. 对Edge的构造方法进行测试
// 2. 对Edge的toString进行测试
TestConcreteVerticesGraph()
还是对内部类设计:
/* *
* <p>
* Testing strategy for Vertex
* 1. 测试getVertexName()方法
* 2. 对每个方法的每种情况进行测试
* <pre>
* addATargetNode():1.添加一个不存在的终点 2.添加一个已经存在的终点
* addASourceNode():1.添加一个不存在的起点 2.添加一个已经存在的起点
* deleteATargetNode():1.删除一个存在的终点 2.删除一个不存在的终点
* deleteASourceNode():1.删除一个存在的起点 2.删除一个不存在的起点
* 其余方法只有一种情况
* </pre>
*/
TestGraph<L>
是对泛型的测试,但是我写Graph<String>的时候就没String有而Object没有的方法,所以稍微改改声明和内部,一样的测试立马就通过了。
Graph.empty()
在GraphStaticTest测试了L为Integer的和Double的,不出意外是快快地通过了。
GraphPoet
针对GraphPoet中的构造方法、poem()、toString()进行测试。
/**testing strategy
* TODO
* <p>
* 对GraphPoet构造方法测试
* <pre>
* 对文件状态划分:
* 1. 空文件
* 2. 文件中只有一行
* 3. 文件中有多行
* 对生成图的边划分:
* 1. 边的权值全都相等
* 2. 边的权值存在不等
* 对生成图是否有树状结构划分
* 1. 图没有树状结构结构
* 2. 图含有树状结构
*</pre>
*
*
*<p>
* 对poem()测试
* <pre>
* 1. 传入为空字符串
* 2. 传入一行字符串,字符串全部为小写字母或大写字母
* 3. 传入一行字符串,字符串含有大写字母和小写字母
* </pre>
* <p>
* 对toString测试
* <pre>
* * 1. 文件为一行字符串
* * 2. 文件为多行字符串
*</pre>
*/
2.1.2 实现
感觉需要细说的是MIT的要求,Poetic Walk那一段的意思。
看似很复杂很激励,其实就是弄懂它的构造函数部分以及生成诗歌部分。
构造函数读取文件即语料库初始化图,用于给输入的字符串加边点为小写状态的文件中的单词,边为相邻单词代表的顶点的从前一个顶点指向后一个顶点的有向边边的权值为同一对相邻单词出现的次数(顺序敏感), 如果两个顶点代表的单词没有相邻出现过, 则不存在从量单词中前一个单词到后一个单词的边。
生成诗歌部分,输入的字符串会被拆分为一个一个单词,按照语料库生成的那个图,检查所有的相邻单词对,如果其在语料库中,之间含有一条长度为2的边,那就把边上的那个顶点对应的单词加入到这个句子里这个单词对中间的位置,实现句子的扩充。
但是注意只对原来的句子里的单词对进行检查,不对加入语料库中桥生成的新单词对进行检查,这就简单非常多了,只需要找那些a->b->c的路,甚至用不到完全地广搜,单纯地搜索两层就行了。
// Abstraction function:
// TODO
// GraphPoet是一个由语料库文件生成的有向图,其中,顶点为小写状态的文件中的单词,
// 边为相邻单词代表的顶点的从前一个顶点指向后一个顶点的有向边
// 边的权值为同一对相邻单词出现的次数(顺序敏感),
// 如果两个顶点代表的单词没有相邻出现过,
// 则不存在从量单词中前一个单词到后一个单词的边
弄懂之后实现代码就比较简单了。
对于重复出现的构成语料库的单词对,可以这样处理:
Map<String,Integer> nodePair=new HashMap<>();
for(int i=0;i<list.size()-1;i++){
String start=list.get(i);
String end=list.get(i+1);
int weight=0;
//出现过不止一次
if(nodePair.containsKey(start+"and"+end)) {
weight = nodePair.get(start + "and" + end);
}
nodePair.put(start+"and"+end,weight+1);
graph.add(start);graph.add(end);
graph.set(start,end,weight+1);
}
2.2 Re-implement the Social Network in Lab1
实验要求请基于在 3.1 节 Poetic Walks 中定义的 **Graph<L.>**及其两种实现,重新实现 Lab1 中 3.3 节的 FriendshipGraph 类。且不变动 Lab1 的 3.3 节给出的客户端代码(例如 main()中的代码),即同样的客户端代码仍可运行。重新执行在 Lab1 里所写的 JUnit 测试用例,测试在本实验里新实现的 FriendshipGraph 类仍然表现正常。
将任务委派给**Graph<Person>**实现,快速地结束了,感觉是读完MIT的英文作业之后的小点心。
总结
通过设计ADT,我更好地理解抽象概念,如封装、抽象和信息隐藏。考虑如何有效地组织数据和操作,以满足特定的需求,提高了我的设计技能。通过对RI、AF、spec等的书写,使代码更加规范有条理。通过设计测试用例以及代码覆盖度检查,我更好地理解的测试的作用以及设计规范。
通过编写测试用例,可以在实现代码之前发现规约中可能存在的问题或不清晰的地方,帮助我们理解如何使用ADT,从而影响设计决策;测试驱动的开发可以帮助我们编写出更健壮、更可靠的代码,并且在开发过程中就可以持续进行测试,这将使得问题更容易被发现和修复。软件构造实验真好玩。