这个做业属于哪一个课程 :软件工程 1916|Wgit
代码签入记录:github
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 30 |
• Estimate | • 估计这个任务须要多少时间 | 60 | 40 |
Development | 开发 | 600 | 720 |
• Analysis | • 需求分析 (包括学习新技术) | 100 | 60 |
• Design Spec | • 生成设计文档 | 60 | 120 |
• Design Review | • 设计复审 | 60 | 45 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 30 | 30 |
• Coding | • 具体编码 | 500 | 600 |
• Code Review | • 代码复审 | 60 | 100 |
• Test | • 测试(自我测试,修改代码,提交修改) | 90 | 120 |
Reporting | 报告 | 90 | 150 |
• Test Report | • 测试报告 | 50 | 40 |
• Size Measurement | • 计算工做量 | 10 | 30 |
• Postmortem & Process Improvement Plan | • 过后总结, 并提出过程改进计划 | 10 | 20 |
合计 | 1800 | 2125 |
拿到题目以后咱们先是对基础功能进行了分析,决定初步实现以后再考虑进阶需求。做业的基础功能:编程
咱们打算将统计字符、单词数、行数以及词频做为四个相关联的函数。选择编程语言方面,一是对Java比较熟悉,二是考虑到进阶需求须要编写爬虫,而个人队友此前已有过用Java编写爬虫的经验,所以咱们选定Java做为开发语言。app
需求肯定以后进入开发阶段,制定代码规范以及绘制类图、程序运行流程图,分配测试任务。查找资料方面,通常是上网搜索,有时也会翻看相关书籍。编程语言
基本需求函数
代码方面因为将主要功能分解成了几个函数,所以只有一个main类。性能
此类包含四个主要功能函数,topTenWords()统计出现频率最高的前十个单词,countLine()统计非空白有效行数,countWord()统计单词数,countChar()则统计字符数。因为单个函数的逻辑并不复杂,所以不对单个函数进行流程图分析,只给出整个类在读取文件并输出指定内容过程的主要流程图。因为四个函数间互有联系,后续函数需用到前边函数的结果,所以在运行过程当中需遵循必定的函数顺序。
单元测试
进阶需求学习
爬虫思路:主要使用jsoup(jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套很是省力的API,可经过DOM,CSS以及相似于jQuery的操做方法来取出和操做数据 )。先浏览CVPR2018官网,获取页面的所有代码,找到须要的内容(论文题目、摘要),利用获取class、id、tag的方法获取所需数据,而后将获取到的数据转换为字符串,设置格式后输出。测试
功能函数:共分红如下几个功能函数:countChar()统计字符、countWord()统计单词、countLine()统计行数、orderWord()统计单词权重、WordCount2()构造函数。类图以下:
因为进阶需求是基于基础需求的开发,所以主要功能逻辑与基础需求一致,主要区别在于加入了权重的计算,所以只重点给出计算权重的函数orderWord()的流程图。
利用JProfiler对程序进行性能监测。
overView
Memory
可看到内存的分配回收过程。
代码中最耗时的函数是countWord函数,在测试时也有所体现。
基本需求
countChar函数
public int countChar() { int count=0; try { FileInputStream fileInputStream = new FileInputStream(file); int charInt = fileInputStream.read(); for (count = 0; charInt != -1; count++) { fileCharIntegers.add(charInt); charInt=fileInputStream.read(); } fileInputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //处理回车换行\r\n 13 10 为一个字符 for (int i = 0; i < fileCharIntegers.size(); i++) { if (fileCharIntegers.elementAt(i)==13) { if (i<fileCharIntegers.size() && fileCharIntegers.elementAt(i+1)==10) { count--; } } } return count; }
代码说明:读取文件input.txt的内容,由于\r\n算做一个字符,所以统计完字符数以后还需从新判断相邻两字符是不是\r\n,若是是将字符数-1。
countWord函数
for (int i = 0; i < fileCharIntegers.size(); i++) { //四个英文字母开头 if (Character.isLetter((char)(int)fileCharIntegers.elementAt(i))){ countFourLetter++; sb.append((char)(int)fileCharIntegers.elementAt(i)); if (countFourLetter>=4 && i == fileCharIntegers.size()-1) { strings.add(sb.toString().toLowerCase()); } }else if (Character.isDigit((char (int)fileCharIntegers.elementAt(i))&&countFourLetter>=4) { sb.append((char)(int)fileCharIntegers.elementAt(i)); if (countFourLetter>=4 && i == fileCharIntegers.size()-1) { strings.add(sb.toString().toLowerCase()); } //System.out.println("the digit:"+sb.toString()); }else { if (countFourLetter>=4) { strings.add(sb.toString().toLowerCase()); } countFourLetter=0; sb.delete(0, sb.length()); } }
代码说明:判断字符串是否由四个字母开头,以后对后续字符进行判断(是不是字母、数字或空白字符),判断完毕后转换为小写,存入map中。
countLine
while((string = d.readLine()) != null){ //System.out.println(count); char chars[] = new char[string.length()]; chars=string.toCharArray(); for (int i = 0; i < chars.length; i++) { //System.out.println(chars[i]); if ((int)chars[i] > 32 && (int)chars[i] != 127) { count++; break; } } }
代码说明:由readLine函数读取行数,再进行是否包含空白字符的判断,若不是有效行则不计数。
topTenWord
for (int i = 0; i < strings.size(); i++) { map.put(strings.elementAt(i), 0); } //统计词频 for (int i = 0; i < strings.size(); i++) { int temp=map.get(strings.elementAt(i)); temp++; map.put(strings.elementAt(i), temp); } //输出出现频率最高的10个单词, map<key,value>以value排序 //经过ArrayList构造函数把map.entrySet()转换成list list = new ArrayList<Map.Entry<String, Integer>>(map.entrySet()); //经过比较器实现比较排序 Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() { public int compare(Map.Entry<String, Integer> mapping1, Map.Entry<String, Integer> mapping2) { return mapping2.getValue().compareTo(mapping1.getValue()); } });
代码说明:统计每一个单词出现频率,创建单词与频率的映射,并按照频率高低及字母表(频率一致时参考字母表)排序,将前十个单词输出。
进阶需求
爬虫
public class Main { public static final String BASIC_URL = "http://openaccess.thecvf.com/"; public static final String CVRP_URL = "http://openaccess.thecvf.com/CVPR2018.py"; public static List<String> urList = new ArrayList<String>(); public static void main(String[] args) { try { /*重定向标准输出流*/ System.setOut(new PrintStream("result.txt")); getPaperUrl(); getPaperDetail(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void getPaperDetail() { int size; size = urList.size(); // size = 10; for (int i = 0; i < size; i++) { try { Document doc = Jsoup.connect(urList.get(i)).get(); Element content = doc.getElementById("content"); Element paperTitle = content.getElementById("papertitle"); Element paperAbstract = content.getElementById("abstract"); System.out.println(i); System.out.println("Title: "+paperTitle.text()); System.out.println("Abstract: "+paperAbstract.text()+"\n\n"); } catch (IOException e) { e.printStackTrace(); } } } public static void getPaperUrl() { try { Document doc = Jsoup.connect(CVRP_URL).get(); // System.out.println(doc); Element content = doc.getElementById("content"); Elements ptitles = content.getElementsByClass("ptitle"); for (int i = 0; i < ptitles.size(); i++) { String link = ptitles.get(i).getElementsByTag("a").attr("href"); urList.add(BASIC_URL+link); // System.out.println(BASIC_URL+link); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
代码说明:获取整个网页HTML代码后再根据<content><ptitle><a href>
等标签获取文章标题、摘要、连接等内容,重定向输出后设置要求格式输出。
进阶需求主要功能函数
countWord
public int countWord() { numOfWord=0; for (int i = 0; i < titleList.size(); i++) { int countFourLetter = 0; StringBuffer sb = new StringBuffer(); for (int j = 0; j < titleList.get(i).length(); j++) { //四个英文字母开头 if (Character.isLetter(titleList.get(i).charAt(j))) { countFourLetter++; sb.append(titleList.get(i).charAt(j)); if (countFourLetter>=4 && j == titleList.get(i).length()-1) { if (titleWordMap.get(sb.toString())!=null) { int temp = titleWordMap.get(sb.toString()); temp++; titleWordMap.put(sb.toString().toLowerCase(), temp); }else { titleWordMap.put(sb.toString().toLowerCase(), 1); } numOfWord++; } }else if (Character.isDigit(titleList.get(i).charAt(j))&&countFourLetter>=4) { sb.append(titleList.get(i).charAt(j)); if (countFourLetter>=4 && j == titleList.get(i).length()-1) { if (titleWordMap.get(sb.toString())!=null) { int temp = titleWordMap.get(sb.toString()); temp++; titleWordMap.put(sb.toString().toLowerCase(), temp); }else { titleWordMap.put(sb.toString().toLowerCase(), 1); } numOfWord++; } //System.out.println("the digit:"+sb.toString()); }else { if (countFourLetter>=4) { if (titleWordMap.get(sb.toString())!=null) { int temp = titleWordMap.get(sb.toString()); temp++; titleWordMap.put(sb.toString().toLowerCase(), temp); }else { titleWordMap.put(sb.toString().toLowerCase(), 1); } numOfWord++; } countFourLetter=0; sb.delete(0, sb.length()); } } } for (int i = 0; i < abstractList.size(); i++) { int countFourLetter = 0; StringBuffer sb = new StringBuffer(); for (int j = 0; j < abstractList.get(i).length(); j++) { //四个英文字母开头 if (Character.isLetter(abstractList.get(i).charAt(j))) { countFourLetter++; sb.append(abstractList.get(i).charAt(j)); if (countFourLetter>=4 && j == abstractList.get(i).length()-1) { if (abstractWordMap.get(sb.toString())!=null) { int temp = abstractWordMap.get(sb.toString()); temp++; abstractWordMap.put(sb.toString().toLowerCase(), temp); }else { abstractWordMap.put(sb.toString().toLowerCase(), 1); } numOfWord++; } }else if (Character.isDigit(abstractList.get(i).charAt(j))&&countFourLetter>=4) { sb.append(abstractList.get(i).charAt(j)); if (countFourLetter>=4 && j == abstractList.get(i).length()-1) { if (abstractWordMap.get(sb.toString())!=null) { int temp = abstractWordMap.get(sb.toString()); temp++; abstractWordMap.put(sb.toString().toLowerCase(), temp); }else { abstractWordMap.put(sb.toString().toLowerCase(), 1); } numOfWord++; } //System.out.println("the digit:"+sb.toString()); }else { if (countFourLetter>=4) { if (abstractWordMap.get(sb.toString())!=null) { int temp = abstractWordMap.get(sb.toString()); temp++; abstractWordMap.put(sb.toString().toLowerCase(), temp); }else { abstractWordMap.put(sb.toString().toLowerCase(), 1); } numOfWord++; } countFourLetter=0; sb.delete(0, sb.length()); } } } // System.out.println(titleWordMap); // System.out.println(abstractWordMap); return numOfWord; }
代码说明:读入字符以后进行单词判断,而后根据标题和摘要标签进行分类存入List。
orderWord
public void orderWord(int weightOfTitle, int weightOfAbstract) { //title和abstract中的单词的并集 Set<String> titleSet = titleWordMap.keySet(); Set<String> abstractSet = abstractWordMap.keySet(); Set<String> totalSet = new HashSet<String>(); totalSet.addAll(titleSet); totalSet.addAll(abstractSet); //带上权重从新计算词频 Iterator<String> setIterator = totalSet.iterator(); while(setIterator.hasNext()) { String key = setIterator.next(); int titleValue = titleWordMap.get(key)!=null ? titleWordMap.get(key):0; int abstractValue = abstractWordMap.get(key)!=null ? abstractWordMap.get(key):0; totalWordMap.put(key,titleValue*weightOfTitle+abstractValue*weightOfAbstract); } //按照词频排个序 //经过ArrayList构造函数把map.entrySet()转换成list orderedWordList = new ArrayList<Map.Entry<String, Integer>>(totalWordMap.entrySet()); //经过比较器实现比较排序 Collections.sort(orderedWordList, new Comparator<Map.Entry<String, Integer>>() { public int compare(Map.Entry<String, Integer> mapping1, Map.Entry<String, Integer> mapping2) { return mapping2.getValue().compareTo(mapping1.getValue()); } }); }
WordCount2
public WordCount2(String filePath) { try { DataInputStream dataInputStream = new DataInputStream(new FileInputStream(filePath)); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(dataInputStream)); String string; while((string=bufferedReader.readLine()) != null) { if (string.matches("^\\d+")) {//论文编号 char[] cbuf = new char[10]; bufferedReader.read(cbuf,0,7); //title里的单词 String titleString = bufferedReader.readLine(); titleList.add(titleString); //System.out.println(titleString); //abstract里的单词 bufferedReader.read(cbuf,0,10); String abstractString = bufferedReader.readLine(); abstractList.add(abstractString); //System.out.println(abstractString); //空格两行不计 bufferedReader.readLine(); bufferedReader.readLine(); } } dataInputStream.close(); bufferedReader.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
测试环节咱们使用白盒测试用例设计方法来设计测试用例。白盒测试有六种覆盖标准:语句覆盖、断定覆盖、条件覆盖、断定/条件覆盖、条件组合覆盖和路径覆盖,发现错误的能力呈由弱至强的变化。 经过研究需求,咱们认为应重点测试的状况有如下几种:
部分测试代码
public class MainTest { Main wordcount = new Main("D:\\学习\\软工实践\\wordcount\\input.txt"); @Test public void testCountChar() { int numOfChar=wordcount.countChar(); int testChar=33; assertEquals(testChar,numOfChar); } @Test public void testCountWord() { int numOfWord=wordcount.countWord(); int testWords=2; assertEquals(testWords,numOfWord); } @Test public void testCountLine() { int numOfLine=wordcount.countLine(); int testLine=4; assertEquals(testLine,numOfLine); } @Test public void testTopTenWord() { List<Map.Entry<String, Integer>> list = wordcount.topTenWord(); String[] testWord= {"snakesre5","name"}; String[] testNum= {"1","1"}; for(int i=0;i<10&&i<list.size();i++) { assertEquals(testWord[i],list.get(i).getKey()); assertEquals(testNum[i],list.get(i).getValue()); } } }
一些测试样例
编号 | 测试用例 | 预期结果 | 运行结果 |
---|---|---|---|
1 | 空白文档 | characters:0 words:0 lines:0 |
characters:0 words:0 lines:0 |
2 | 含三个换行符 | characters:3 words:0 lines:0 |
characters:3 words:0 lines:0 |
3 | 三个换行符+ Name |
characters:7 words:1 lines:1 <name>:1 |
characters:7 words:1 lines:1 <name>:1 |
4 | \r\n hi asind asiwdb edfgEfDG AsiWdB ASIWDB aSiWdB |
characters: 61 words: 7 lines: 3 <asiwdb>: 4 <asind>: 1 <edfg>: 1 <efdg>: 1 |
characters: 61 words: 7 lines: 3 <asiwdb>: 4 <asind>: 1 <edfg>: 1 <efdg>: 1 |
5 | hn ji o.xs ijml9648 ijml9748 78964152sdcf @)754 ^&*() |
characters: 55 words: 3 lines: 5 <ijml9648>: 1 <ijml9748>: 1 <sdcf>: 1 |
characters: 55 words: 3 lines: 5 <ijml9648>: 1 <ijml9748>: 1 <sdcf>: 1 |