代码6.4 ResultJSPTest.java代码片段
01 package book.lucene; 02 03 import static org.junit.Assert.*; 04 05 import org.junit.*; 06 import java.net.URL; 07 import com.gargoylesoftware.htmlunit.WebClient; 08 import com.gargoylesoftware.htmlunit.html.HtmlForm; 09 import com.gargoylesoftware.htmlunit.html.HtmlPage; 10 import com.gargoylesoftware.htmlunit.html.HtmlTable; 11 12 public class ResultJSPTest { 13 private static WebClient webClient; 14 15 private static HtmlPage page; 16 17 @BeforeClass 18 public static void setUp() throws Exception { 19 webClient = new WebClient(); 20 } 21 22 @Test 23 public void testContent() throws Exception { 24 String[] documentArray = { 25 "Apache Lucene - Building and Installing the Basic Demo", 26 "Apache Lucene - Query Parser Syntax", 27 "Apache Lucene - Scoring" }; 28 String[] summaryArray = { 29 "Apache > Lucene [Lucene] [Lucene] Main Wiki Lucene 2.3.1 Documentation Documentation Overview Javadocs All Core Demo Contrib Analyzers Ant Bdb Bdb-je Benchmark Highlighter Lucli Memory Miscellane", 30 "Apache > Lucene [Lucene] [Lucene] Main Wiki Lucene 2.3.1 Documentation Documentation Overview Javadocs All Core Demo Contrib Analyzers Ant Bdb Bdb-je Benchmark Highlighter Lucli Memory Miscellane", 31 "Apache > Lucene [Lucene] [Lucene] Main Wiki Lucene 2.3.1 Documentation Documentation Overview Javadocs All Core Demo Contrib Analyzers Ant Bdb Bdb-je Benchmark Highlighter Lucli Memory Miscellane" 32 }; 33 34 URL url = new URL( 35 "http://localhost:8080/luceneweb/results.jsp?query=hello+ world&maxresults= 100&startat=0"); 36 37 page = (HtmlPage) webClient.getPage(url); 38 HtmlTable table = (HtmlTable) page.getFirstByXPath("//table"); 39 assertNotNull(table); 40 for (int i = 0; i < 3; i++) { 41 assertEquals(documentArray[i], table.getCellAt(i+1, 0).as Text()); 42 assertEquals(summaryArray[i], table.getCellAt(i+1, 1).as Text()); 43 } 44 } 45 46 } |
第24~32行,定义documentArray和summaryArray数组存放期待的结果,documentArray中存放的是result.jsp页面中Document一栏的内容,summaryArray中存放的是result.jsp页面中Summary一栏中的内容。这里可以用数据文件或参数化列表代替。
第34~35行,生成URL对象发送请求,URL中的查询条件是“hello world”,分页设置为100条每页,从第0条记录开始。
第38行,因为result.jsp页面布局非常简单,只有一个table。这里调用getFirstByXPath直接根据table标签定位到搜索结果列表,并封装成HtmlTable对象。
第39行,先判断列表是否存在。
第40行,进一步判断列表中的每个单元格的值与期待结果是否一致。
这里的测试用例中URL是固定的,期待结果也是固定的,可以用Parameter参数化的方式加以改进。在单元测试用例的设计过程中,需要遵循的用例独立以及数据独立的原则。这里的用例与数据采用一一对应的关系,一个等价类总选取一个典型数据。如果需要增加其他类型的数据,例如需要测试query参数为空的情况,其实增加的应该是一条测试用例,该测试用例对应的仍然是固定的URL请求query=&maxresults= 100&startat=0,以及对应的测试结果。这样的设计能够简化测试用例。
有人会认为这样的设计带来了测试用例的维护成本,改用参数化的方式更加合理。然而经过实践,我们发现其实两者的不同在于维护的对象,固定测试数据的设计将维护的重点放在数据的维护上,而参数化设计的重点放在代码的维护上。更多时候,从整体上看,通过基于数据分离的良好代码结构,让仅维护数据相比较维护代码而言,更加能够适应频繁变更的产品代码。而参数化的设计在动态生成测试数据方面则更具优势。权衡比较,本项目采用了前者的设计方式。
测试动态代码
前面的测试方案实际上是将JSP看成是一个黑盒,但result.jsp中含有大量的Java代码。如何进行更细粒度的测试,如何保证JSP代码的覆盖率?这一类的文章很多,但是大多数探讨的是基于JavaBean的测试解决方案。针对本项目这种无Javabean的JSP,我们采用另一种形式的单元测试技巧——环境隔离测试。
在环境隔离测试中,JSP测试关注的重点在于代码的逻辑。这种思路下,除去嵌入的HTML代码,JSP可以看成是一个纯粹的Java类,不过额外完成了Servlet需要处理的工作。额外的工作与JSP代码逻辑本身关联很小,完全可以剥离。剥离出来的代码如下:
代码6.5 MockJSPSearchResult.java
001 package book.lucene; 002 003 import java.net.URLEncoder; 004 import javax.servlet.ServletException; 005 import org.apache.lucene.analysis.Analyzer; 006 import org.apache.lucene.analysis.standard.StandardAnalyzer; 007 import org.apache.lucene.document.Document; 008 import org.apache.lucene.queryParser.ParseException; 009 import org.apache.lucene.queryParser.QueryParser; 010 import org.apache.lucene.search.Hits; 011 import org.apache.lucene.search.IndexSearcher; 012 import org.apache.lucene.search.Query; 013 014 public class MockJSPSearchResult { 015 private RequestMockObject request; 016 017 private boolean error = false; 018 019 private int startindex = 0; 020 021 private int maxpage = 50; 022 023 private int thispage = 0; 024 025 private Hits hits = null; 026 027 private String indexName = "/opt/lucene/index"; 028 029 private IndexSearcher searcher = null; 030 031 private String startVal = null; 032 033 private Query query = null; 034 035 private String queryString = null; 036 037 private String maxresults = null; 038 039 public String escapeHTML(String s) { 040 s = s.replaceAll("&", "&"); 041 s = s.replaceAll("<", "<"); 042 s = s.replaceAll(">", ">"); 043 s = s.replaceAll("\"", """); 044 s = s.replaceAll("'", "'"); 045 return s; 046 } 047 048 public void searchResult(String _query, String _maxresults, String _startat) 049 throws Exception { 050 request = new RequestMockObject(_query, _maxresults, _startat); 051 052 try { 053 searcher = new IndexSearcher(indexName); 054 } catch (Exception e) { 055 System.out.println("%>"); 056 System.out.println("<p>ERROR opening the Index - contact sysadmin! 057 </p>"); 058 System.out.println("<p>Error message: <%=" 059 + escapeHTML(e.getMessage()) + "%></p>"); 060 System.out.println("<%"); 061 error = true; 062 } 063 System.out.println("%>"); 064 System.out.println("<%"); 065 if (error == false) { 066 queryString = request.getParameter("query"); 067 startVal = request.getParameter("startat"); 068 maxresults = request.getParameter("maxresults"); 069 try { 070 maxpage = Integer.parseInt(maxresults); 071 startindex = Integer.parseInt(startVal); 072 } catch (Exception e) { 073 } 074 075 if (queryString == null) 076 throw new ServletException("no query " + "specified"); 077 078 Analyzer analyzer = new StandardAnalyzer(); 079 try { 080 QueryParser qp = new QueryParser("contents", analyzer); 081 query = qp.parse(queryString); 082 } catch (ParseException e) { 083 System.out.println("%>"); 084 System.out.println("<p>Error while parsing query: 085 <%=escapeHTML(" 086 + e.getMessage() + ")%></p>"); 087 System.out.println("<%"); 088 error = true; 089 } 090 } 091 System.out.println("%>"); 092 System.out.println("<%"); 093 if (error == false && searcher != null) { 094 thispage = maxpage; 095 hits = searcher.search(query); 096 if (hits.length() == 0) { 097 System.out.println("%>"); 098 System.out.println("<p> I'm sorry I couldn't 099 find what you were looking for. </p>"); 100 System.out.println("<%"); 101 error = true; 102 } 103 } 104 105 if (error == false && searcher != null) { 106 System.out.println("%>"); 107 System.out.println("<table>"); 108 System.out.println("<tr>"); 109 System.out.println("<td>Document</td>"); 110 System.out.println("<td>Summary</td>"); 111 System.out.println("</tr>"); 112 System.out.println("<%"); 113 114 if ((startindex + maxpage) > hits.length()) { 115 thispage = hits.length() - startindex; 116 } 117 118 for (int i = startindex; i < (thispage + startindex); i++) { 119 System.out.println("%>"); 120 System.out.println("<tr>"); 121 System.out.println("<%"); 122 Document doc = hits.doc(i); 123 String doctitle = doc.get("title"); 124 String url = doc.get("path"); 125 System.out.println(url); 126 if (url != null && url.startsWith("../webapps/")) { 127 url = url.substring(10); 128 } 129 if ((doctitle == null) || doctitle.equals("")) 130 doctitle = url; 131 132 System.out.println("%>"); 133 System.out.println("<td><a href=\"<%=" + url + "%>\"> <%=" 134 + doctitle + "%></a></td>"); 135 System.out.println("<td><%=" + doc.get("summary") + "%> </td>"); 136 System.out.println("</tr>"); 137 System.out.println("<%"); 138 } 139 System.out.println("%>"); 140 System.out.println("<%"); 141 if ((startindex + maxpage) < hits.length()) { 142 143 String moreurl = "results.jsp?query=" 144 + URLEncoder.encode(queryString) + "& maxresults=" 145 + maxpage + "&startat=" + (startindex + maxpage); 146 System.out.println("%>"); 147 System.out.println("<tr>"); 148 System.out.println("<td></td><td><a href=\"<%=" + moreurl 149 + "%>\">More Results>></a></td>"); 150 System.out.println("</tr>"); 151 System.out.println("<%"); 152 } 153 System.out.println("%>"); 154 System.out.println("</table>"); 155 System.out.println("<%"); 156 if (searcher != null) 157 searcher.close(); 158 System.out.println("%>"); 159 } 160 } 161 } |