如果图片/视频显示有问题,还请查看readme.html文件
1.将压缩包解压缩,里面有OOAD_LAB.jar文件,在命令行中运行即可运行
java -jar OOAD_LAB.jar
2.关于测试,由于使用了Junit,在网上搜了好久都找不到可以命令行直接运行测试的,要么就是过于麻烦,要么就是利用maven,因此我这里录制了一个运行测试的视频,如果助教想要实际运行测试的话,可以按照方法二进行操作
3.使用程序执行
如果要按照手动测试用例执行,进行书签时,还请将测试用例中的
open "test1.bmk"
修改为
open "bookmark/test1.bmk"
以便于后面使用ls-tree命令时可以更好的展示文件结构
1.将压缩包解压缩,里面包括了源代码
2.使用idea打开,进入src/Entry.java中,运行main函数,即可运行代码
3.在test目录右键,点击run all test,即可运行所有测试用例
├──src # 源文件代码目录
│ └──OOAD_LAB
│ ├──bookmark
│ │ ├──"Bookmark.java"
│ │ ├──"MarkInfo.java"
│ │ └──"OperationParentNode.java"
│ ├──command
│ │ ├──"Command.java"
│ │ ├──commands
│ │ │ ├──"AddCommand.java"
│ │ │ ├──"DeleteCommand.java"
│ │ │ ├──"LsTreeCommand.java"
│ │ │ ├──"OpenCommand.java"
│ │ │ ├──"ReadBookmarkCommand.java"
│ │ │ ├──"SaveCommand.java"
│ │ │ └──"ShowTreeCommand.java"
│ │ └──"CommandStack.java"
│ ├──"Console.java"
│ ├──"Entry.java"
│ ├──"FileIOUtil.java"
│ ├──"Model.java"
│ └──treeView
│ ├──bookmarkTree
│ │ ├──"BookmarkCP.java"
│ │ ├──"BookmarkNP.java"
│ │ └──decorator
│ │ ├──"BookmarkLabelProvider.java"
│ │ ├──"BookmarkReadCountDecorator.java"
│ │ ├──"BookmarkStarDecorator.java"
│ │ └──"ILabelProvider.java"
│ ├──fileTree
│ │ ├──"FileSystemCP.java"
│ │ └──"FileSystemNP.java"
│ ├──"INameProvider.java"
│ ├──"ITreeContentProvider.java"
│ └──"TreeViewer.java"
├──test # 测试文件代码目录
│ └──OOAD_LAB
│ ├──bookmark
│ │ └──"BookmarkTest.java"
│ ├──command
│ │ ├──commands
│ │ │ ├──"AddCommandTest.java"
│ │ │ ├──"DeleteCommandTest.java"
│ │ │ ├──"LsTreeCommandTest.java"
│ │ │ ├──"OpenCommandTest.java"
│ │ │ ├──"ReadBookmarkCommandTest.java"
│ │ │ └──"ShowTreeCommandTest.java"
│ │ └──"CommandStackTest.java"
│ ├──test_bookmark
│ │ └──"test.bmk" # 测试用书签文件
│ └──"ConsoleTest.java"
本次lab在实现中一共分为了三层,分别是用户层,业务层和数据层.
-
用户层:
- 在用户层中,实现了Console类,用于接收用户的输入,并将对应的命令转为不同的命令类,传递给业务层.
- 代码示例:(Console.java)
public void getUserCommand() { System.out.println("Welcome to the bookmark system!"); Scanner scanner = new Scanner(System.in); while (true) { String commandString = scanner.nextLine(); //split the OOAD_LAB.command by space String[] commandItems = commandString.split(" "); //each case is the same as the UI button //add OOAD_LAB.command if (commandItems[0].contains("add")) { String[] commands = cutTheItem(commandItems.length==2?makeArrayToFour(commandItems):commandItems); command = new AddCommand(commands); CommandStack.execute(command); } //... //redo if (commandItems[0].equals("redo")) { CommandStack.redo(); } } scanner.close(); }
- 在用户层中,实现了CommandStack类,用于存储用户的命令,并提供撤销和重做的功能,同时每个命令都会调用对应Model层的方法以实现业务逻辑
- 代码示例:(CommandStack.java)
public static void execute(Command command) { command.execute(); //if the OOAD_LAB.command is add or delete ,we need to suport OOAD_LAB.command redo and undo if (command instanceof AddCommand || command instanceof DeleteCommand) { reStack.push(command); //if can still redo,we need to clear the redo stack if (unStack.size() > 0) { unStack.clear(); } } } public static void undo() { if(!reStack.isEmpty()) { Command command = reStack.pop(); command.undo(); unStack.push(command); } else{ System.out.println("Can not undo"); } } public static void redo() { if(!unStack.isEmpty()) { Command command = unStack.pop(); command.execute(); reStack.push(command); } else{ System.out.println("Can not redo"); } }
-
业务层
- 由于业务层的实际上只是对bookmark做一些操作,实例上的代码量并不大,因此就写在了一个单独的Model类中
- 这个model类通过为Command提供封装好的借口,实现了对bookmark的增删改查
- 代码示例:(Model.java)
public class Model { public Map<OperationParentNode, ?> addCommand(String command[]) { //... } public Map<OperationParentNode, ?> deleteCommand(String deleteCommand[]) { //... } public void saveBookmarkToFile(String fileName) { //... } public void loadBookmarkFromFile(String fileName) { //... } //... }
- 除此之外,还写到了一些工具类,如FileIO等用来帮助实现业务逻辑
-
数据层
在实现add和delete的command时,将add和delete的操作封装成command,并将command压入栈(commandStack)中,实现undo和redo的功能. 而在实现其他command时,同样也是将操作封装成command,command通过与model业务层交互实现对应的操作
在实现书签的星标和阅读次数的显示时,使用了装饰器模式,将书签的星标和阅读次数的显示封装成装饰器,并将装饰器添加到书签的labelProvider中,实现书签的星标和阅读次数的显示. [注]:因手动测试用例未使用阅读次数,故show-tree时不打印阅读次数,但是对应的类以及代码都已经实现
在实现树形结构的显示时,使用了适配器模式,将文件系统的树形结构和书签的树形结构封装成适配器,并将适配器添加到树形结构的显示中,实现树形结构的显示.
在对标签的状态进行保存时,把最顶层的书签作为单例模式存储,便于model获取书签信息以及打印等相关操作
因为本次lab中的命令较少,且命令的类型较为简单,基本没有出现需要大量的更换整套内容的情况 同时,即使是需要从外部读取一些内容,也只是从文件中获取,比较简单,从而并未使用工厂模式
张老师在课上提到,每个命令至少给一个测试用例,因此我这里对每个命令给出了至少一个测试用例,除此之外,还为CommandStack以及Console类给出了对应的测试用例
-
add命令以及undo和redo功能(共两个)
- addTitleCommandAndUndoTest()
- addBookmarkAndUndoCommandTest()
-
delete命令以及undo和redo功能(共两个)
- deleteBookMarkCommandAndUndoTest()
- deleteTitleCommandAndUndoTest()
-
ls-tree命令(共一个)
- lsTreeCommandTest()
-
show-tree命令(共一个)
- showTreeCommandTest()
-
open命令(共一个)
- openCommandTest()
-
read-bookmark命令(共三个)
- readBookmarkCommandTest1()
- readBookmarkCommandTest2()
- readBookmarkCommandTest3()
-
console类(共一个)
- consoleTest()
-
commandStack类(共两个)
- executeTest()
- undoAndRedoTest()
这两个测试用例分别对应于手动测试的用例1,2,3,5的组合,涵盖了所有的命令
- ComplexTest类中
- complexTest1()
- 用于组合测试add,delete,read,show-tree,undo,redo等基本命令的实现与操作
- complexTest2()
- 用于组合测试open,save,ls-tree等于业务逻辑不太相关的实现与操作
- complexTest1()
- 1.以addTitleCommandAndUndoTest()测试函数为例,在测试之前,我们会有一个准备好的测试用的书签,这个书签在这里由BookmarkTest.java中的initAddCommandBookmarkTest()函数给出
public static Bookmark initAddCommandBookmarkTest(){
Bookmark bookmarkTest = new Bookmark("",0,new ArrayList<>(),new ArrayList<Bookmark>());
ArrayList<Bookmark> subBookmark1_1 = new ArrayList<>();
List<Pair<String,MarkInfo>> b3_marks = new ArrayList<>();
b3_marks.add(new Pair<>("JFP",new MarkInfo("JFP","https://www.cambridge.org/core/journals/journal-of-functionalprogramming",0,false,4)));
Bookmark b3_1 = new Bookmark("函数式",3,b3_marks,new ArrayList<Bookmark>());
Bookmark b3_2 = new Bookmark("面向对象",3,new ArrayList<>(),new ArrayList<Bookmark>());
List<Pair<String,MarkInfo>> b2_1_marks = new ArrayList<>();
b2_1_marks.add(new Pair<>("elearning",new MarkInfo("elearning","https://elearning.fudan.edu.cn/courses",0,false,3)));
List<Pair<String,MarkInfo>> b2_2_marks = new ArrayList<>();
b2_2_marks.add(new Pair<>("Markdown-Guide",new MarkInfo("Markdown-Guide","https://www.markdownguide.org",0,false,3)));
ArrayList<Bookmark> b2_2_subBookmarks = new ArrayList<>();
List<Pair<String,MarkInfo>> b2_3_marks = new ArrayList<>();
b2_3_marks.add(new Pair<>("Category-Theory",new MarkInfo("Category-Theory","http://www.appliedcategorytheory.org/what-is-appliedcategory-theory/",0,false,3)));
b2_2_subBookmarks.add(b3_1);
b2_2_subBookmarks.add(b3_2);
Bookmark b2_1 = new Bookmark("课程",2,b2_1_marks,new ArrayList<Bookmark>());
Bookmark b2_2 = new Bookmark("参考资料",2,b2_2_marks,b2_2_subBookmarks);
Bookmark b2_3 = new Bookmark("待阅读",2,b2_3_marks,new ArrayList<Bookmark>());
subBookmark1_1.add(b2_1);
subBookmark1_1.add(b2_2);
subBookmark1_1.add(b2_3);
bookmarkTest.addSubBookmark(new Bookmark("个人收藏",1,new ArrayList<>(),subBookmark1_1));
return bookmarkTest;
}
- 在获取到初始的bookmark后,我们对应的执行测试操作,操作都在addTitleCommandAndUndoTest()函数中给出,使用junit框架来进行结果的assert对比,查看结果是否一样
- 在这里,我们assert比较的并不是某个bookmark转为字符串的值,因为如果bookmark转字符串的规则发生变化后,测试用例将会失效
- 因此我们使用模型比较的方式,也即比较两个书签的内部结构是否相同(包括层次,子书签等),为此,我重写了Bookmark和MarkInfo的equals()方法
- 我们接下来看一下测试了哪些内容,以下给出了addTitleCommandAndUndoTest()函数的内容
@Test
public void addTitleCommandAndUndoTest() {
Bookmark bookmark1 = initAddCommandBookmarkTest();
Command addCommand1 = new AddCommand(new String[]{"add-title","测试添加标题","at","面向对象"},new Model(bookmark1));
CommandStack.execute(addCommand1);
//the after add title bookmarks structure is:
//|---个人收藏
//
//| |---课程
//| |---elearning(https://elearning.fudan.edu.cn/courses)
//
//| |---参考资料
//| |---Markdown-Guide(https://www.markdownguide.org)
//
//| | |---函数式
//| | |---JFP(https://www.cambridge.org/core/journals/journal-of-functionalprogramming)
//
//| | |---面向对象
//
//| | | |---测试添加标题
//
//| |---待阅读
//| |---Category-Theory(http://www.appliedcategorytheory.org/what-is-appliedcategory-theory/)
assertEquals(bookmark1, getAfterAddTitleCommandBookMarkTest1());
//to see the undo result
CommandStack.undo();
assertEquals(bookmark1, initAddCommandBookmarkTest());
//-----------------------------------------------------------------
Bookmark bookmark2 = initAddCommandBookmarkTest();
Command addCommand2 = new AddCommand(new String[]{"add-title","测试添加标题","at","课程"},new Model(bookmark2));
CommandStack.execute(addCommand2);
//the after add title bookmarks structure is:
//|---个人收藏
//
//| |---课程
//| |---elearning(https://elearning.fudan.edu.cn/courses)
//
//| | |---测试添加标题
//
//| |---参考资料
//| |---Markdown-Guide(https://www.markdownguide.org)
//
//| | |---函数式
//| | |---JFP(https://www.cambridge.org/core/journals/journal-of-functionalprogramming)
//
//| | |---面向对象
//
//| |---待阅读
//| |---Category-Theory(http://www.appliedcategorytheory.org/what-is-appliedcategory-theory/)
assertEquals(bookmark2, getAfterAddTitleCommandBookMarkTest2());
//to see the undo result
CommandStack.undo();
assertEquals(bookmark2, initAddCommandBookmarkTest());
}
-
可以看到,我们在这里面不仅比较了添加title的结果,还比较了undo等操作是否正确.
-
其他函数的测试内容与这里介绍的基本类似,最终实现了所有命令以及一些重要类的测试任务,同时还测试了复杂的组合业务逻辑