這個做業屬於哪一個課程 :軟件工程 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()的流程圖。
orderWord
部分單元測試過程截圖展現
利用JProfiler對程序進行性能監測。
overView
Memory
可看到內存的分配回收過程。
CPUview
代碼中最耗時的函數是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 |