Web entwickeln Eines der ärgerlichsten Dinge an Anwendungen ist, dass man sie bereitstellen muss, um sie zu testen. Natürlich sind nicht alle Teile so. Wenn Sie sorgfältig entwerfen, können Sie die Geschäftslogik in einem Java-Programm testen. Sie können Datenzugriff, Schnittstellen und gespeicherte Prozeduren testen, ohne dass der Anwendungsserver ausgeführt wird. Wenn Sie jedoch die GUI (von Jsp generiertes HTML) testen, müssen Sie sie bereitstellen, bevor Sie sie testen können.
Viele Teams greifen auf Sellenium, Mercury oder andere Tools zurück, um GUI über Webserver zu testen. Selbst wenn der Inhalt der Seite gleich bleibt, sich aber der Stil ändert, wird der Test fragil. Andere Teams verwenden Cactus, um diese Schwachstelle zu beheben, oder primitive Tools wie HtmlUnit und HttpUnit, um den von Webanwendungen generierten HTML-Code zu überwachen. Ich werde diese Themen in einer weiteren Blogreihe diskutieren.
In diesem Artikel werde ich eine einfache Technologie vorstellen, die JUnit oder HtmlUnit zum Testen von JSP-Seiten verwendet und vollständig vom Container getrennt ist. Dies ist auch der Vorteil dieser Technologie.
Sie müssen den Container nicht unbedingt am Laufen halten oder überhaupt existieren. Sie können Ihr JSP testen, bevor Sie einen bestimmten Webserver auswählen.
Sie müssen nicht nach jeder Änderung eine erneute Bereitstellung durchführen, sodass der Bearbeitungs-/Kompilierungs-/Testprozess schneller erfolgt.
Sie können die Test-First-Entwicklung verwenden, um Jsp kontinuierlich zu erstellen.
Der Grund, warum Jsp-Testtechnologie außerhalb des Containers nicht beliebt ist, liegt darin, dass Jsp für die Ausführung im Container konzipiert ist. Die Designer haben nie viel über die Möglichkeit nachgedacht, außerhalb des Containers zu laufen. Daher basiert der vom JSP-Compiler generierte Code häufig auf vielen vom Container bereitgestellten Komponenten. Selbst Tools, die JSP-Code generieren, gehen davon aus, dass bereits eine erfolgreich bereitgestellte Webanwendung ausgeführt wird. Um also außerhalb von Containern laufen zu können, müssen Sie diese Tools und Komponenten entsprechend entwickeln.
Warum erwarten die Designer so vieler Frameworks und Tools immer Sie? ? In der kleinen Welt leben, die sie bieten? Warum muss ich eine vollständige Webanwendung erstellen, bevor ich JSP kompiliere? Warum müssen diese Dinge in Containern laufen? Das Verbergen von Informationen war bereits vor 10 Jahren ein Grundprinzip guten Softwaredesigns. Wann wird unsere Branche das ernst nehmen?
Der erste Schritt beim Testen eines Jsp besteht darin, es in ein Servlet zu kompilieren. Um diesen Schritt zu erreichen, müssen wir zunächst auch Jsp in das Java-Format konvertieren. Apache stellt ein Tool namens Jasper bereit. Wir rufen Jasper auf, um eine Quelldatei im Java-Format MyPage_jsp.java für MyPage.jsp zu erstellen. Sie können diese Datei dann mit Ihrer bevorzugten IDE in ein Servlet kompilieren.
Leider ist Jasper nicht für die Verwendung in der Befehlszeile konzipiert oder nicht ganz darauf ausgelegt. Jasper verfügt jedoch über eine Hauptfunktion zum Verarbeiten von Befehlszeilenparametern, die einfach durch Aufrufen von Java org.apache.jasper.JspC aufgerufen werden kann. Allerdings erwartet Jasper, dass die Umgebung, in der es ausgeführt wird, mit der Containerumgebung konsistent ist. Sie müssen sicherstellen, dass sich im Klassenpfad viele Apache-Jar-Dateien befinden und dass die web.xml-Datei der Webanwendung gefunden werden kann. Es muss auch in der Lage sein, das WEB-INF-Verzeichnis zu finden, das die Webanwendung Jar sowie TLD-Dateien usw. enthält. Kurz gesagt, Jasper muss in der Lage sein, eine vollständige Webanwendung zu finden.
Wenn es noch schlimmer ist, gibt es in einigen spezifischen Jasper-Versionen (ich verwende Tomcat 5.5.20) einige Fehler im Code, es sei denn, es stimmt vollständig mit der Art und Weise überein, wie TOMCAT aufgerufen wird Es generiert einige Fehler.
Der erste Schritt ist mühsam, aber relativ einfach. Sie müssen die richtige Verzeichnis- und Dateistruktur erstellen und dann Jasper in Ant aufrufen (Classpath ist einfacher zu steuern). Der zweite Punkt ist, dass einige Recherchen und Tests erforderlich sind, damit es funktioniert. Das Folgende ist die Ant-Datei, die erfolgreich ausgeführt werden kann. Der Aufruf an JspC erscheint in der letzten Aufgabe.
< /target>
< ;fileset dir="${catalina.home}/common/lib">
classpath>
includes="**/jsp/** /*.class"
/>
Natürlich willst du das alles Kriterien Die Dateien und Verzeichnisse befinden sich unter ${build.war.home}, um die Funktionsfähigkeit zu gewährleisten. Wenn Sie in Ihrem JSP benutzerdefinierte Tags verwenden, stellen Sie außerdem sicher, dass sich alle entsprechenden TLD-Dateien in Ihrem TLD-Verzeichnis befinden.
Es ist zu beachten, dass die Befehlszeile von Jspc in der Ant-Datei aufgerufen wird, anstatt den von Tomcat bereitgestellten JspC Ant Task zu verwenden. Weil ich festgestellt habe, dass es nicht richtig funktioniert, wenn Sie benutzerdefinierte Tags haben. Vielleicht bin ich verwirrt, oder es liegt tatsächlich ein Fehler in JspC vor. Aber die einzige Möglichkeit, Jasper dazu zu bringen, korrekten Code zu generieren, besteht meiner Meinung nach darin, ihn über die Befehlszeile aufzurufen und den JSP-Dateipfad explizit als Befehlszeilenargument zu übergeben! Wenn Sie sich auf den Ant Task verlassen oder die Befehlszeile verwenden, um in allen Webanwendungen zur Kompilierung nach Jsps zu suchen, wird falscher Code generiert. (Siehe diesen Blog)
Da wir nun die Java-Datei haben, analysieren wir sie. Schauen Sie sich bitte zunächst die folgende JSP-Datei an.
<%@ page import="com.objectmentor.library.utils.DateUtil" %>
<%@ page import="com.objectmentor.library.web.controller.patrons.LoanRecord" %>
<% @ page import="java.util.List" %>
<%
List LoanRecords = (Liste) request.getAttribute("loanRecords");
if (loanRecords.size() > 0) {
%>
<% for (int i = 0; i < lendRecords.size(); i++) { LoanRecordLoanRecord = (LoanRecord) LoanRecords.get(i); %> td> <% } %>
ID
Titel
Fälligkeitsdatum
Gut
">
<%=loanRecord.id%>
<%=loanRecord.title%>
<%=DateUtil.dateToString(loanRecord.dueDate)%>
<%=loanRecord.fine.toString()%>
<%
}
%>
下面则是Jasper所生成的代码.
package com.objectmentor.library.jsp.WEB_002dINF.pages.patrons.books ;
javax.servlet.* importieren;
javax.servlet.http importieren .*;
import javax.servlet.jsp.*;
import com.objectmentor.library.utils.DateUtil;
import com.objectmentor.library.web.controller.patrons.LoanRecord;
import java.util.List;
private static java.util.List _jspx_dependants;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request, HttpServletResponse Response)
wirft java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Objektseite = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
Versuchen Sie es mit {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html");
pageContext = _jspxFactory.getPageContext (dies, Anfrage, Antwort,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write('/n');
out.write('/n');
out.write('/n');
List LoanRecords = (List) request.getAttribute("loanRecords");
if (loanRecords.size() > 0) {
out.write("/n");
out.write(" out.write(" out.write( " out.write(" out.write(" out.write(" out.write(" out.write(" ");
for (int i = 0; i < lendRecords.size(); i++) { LoanRecord yieldRecord = (LoanRecord) yieldRecords.get(i ); out.write("/n"); out.write(" out.print(i%2==0?"even="odd"); out.write("/">/n "); out.write(" out.print(loanRecord.id); out.write("/n"); out.write(" out.write(" out.print(loanRecord.title); out.write("/n"); out.write(" out.write(" out.print(DateUtil.dateToString(loanRecord.dueDate)); out .write("/n"); out.write(" out.write(" out.print(loanRecord.fine.toString()); out.write("/n"); out.write(" out.write(" out.write(" ");
}
out.write("/n"); out.write(" table>/n");
} } Catch (Throwable t) { if (!(t Instanz von SkipPageException )){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } endlich { if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context); } } }
Warum sollte dieser Kurs für endgültig erklärt werden? Was passiert, wenn ich eine von einem Test-Stub abgeleitete Klasse erstellen möchte? Warum sollte jemand eine generierte Klasse so anstößig finden, dass ich sie nicht einmal überschreiben kann?
Lesen Sie diesen Code sorgfältig und Sie werden feststellen, dass wir zur Verwendung dieser Servlet-Instanz die Instanzen HttpServletRequest und HttpServletResponse benötigen. Wenn wir es genauer untersuchen, werden wir feststellen, dass das Servlet den gesamten HTML-Code in eine Instanz von JspWriter schreibt und JspWriter von PageContext abgerufen wird. Wenn wir eine Modellversion des JspWriter erstellen könnten, um den gesamten HTML-Code zu speichern, und dann eine Modellversion des PageContext erstellen könnten, um den simulierten JspWriter zu versenden, könnten wir in unseren Tests auf diesen HTML-Code zugreifen. Glücklicherweise haben die Designer von Tomcat die Erstellung von JspWriter in die Factory-Klasse von JspFactory eingefügt. Und diese Fabrikklasse kann überschrieben werden! Dies bedeutet, dass wir unsere eigene JspWriter-Klasse im Servlet erhalten können, ohne das Servlet zu ändern. Sie benötigen lediglich den folgenden Code.
Klasse MockJspFactory erweitert JspFactory { public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse, String string, boolean b, int i, boolean b1) { return new MockPageContext(new MockJspWriter()); } public void releasePageContext(PageContext pageContext) { }
public JspEngineInfo getEngineInfo() { return null; } }
Was wir jetzt brauchen, ist ein simulierter Jspwriter. Zur Vereinfachung der Präsentation habe ich Folgendes verwendet:
MockJspWriter Paket com.objectmentor.library.web.framework.mocks;
import javax.servlet.jsp.JspWriter; import java.io.IOException;
öffentliche Klasse MockJspWriter erweitert JspWriter {
privater StringBuffer failedContent;
public MockJspWriter(int bufferSize, boolean autoFlush) { super(bufferSize, autoFlush); submittedContent = new StringBuffer(); } public String getContent() { return subscribedContent.toString(); }
public void print(String arg0) wirft IOException { submittedContent.append(arg0); }
public void write(char[] arg0, int arg1, int arg2) löst eine IOException { for (int i=0; i submittedContent.append(String.valueOf (arg0[arg1++])); } public void write(String content) löst eine IOException { submittedContent.append(content); } // viele uninteressante Methoden eliminiert. Ich habe ihnen gerade // degenerierte Implementierungen gegeben. (z. B. {}) } 无需关心那些我省略掉的未实现方法,我认为只需要关心那些足够使得我的测试得以运行的方法即可.对于剩下的,我只会使用其退化实现. IDE对于创建这些mock类非常有帮助.它 Sie können auch MockPageContext, MockHttpServletRequest und MockHttpServletResponse verwenden类。 MockPageContext Paket com.objectmentor.library.web .framework.mocks; javax.servlet importieren.*; javax importieren .servlet.http.*; import javax.servlet.jsp.*; import java.io.IOException; import java.util.Enumeration; öffentliche Klasse MockPageContext erweitert PageContext { privat final JspWriter out; privat HttpServletRequest request; public MockPageContext(JspWriter out) { this.out = out; request = new MockHttpServletRequest(); } public JspWriter getOut() { zurückkehren; } public ServletRequest getRequest() { return request; } // viele entartete Funktionen eliminiert. } MockHttpServletRequest Paket com.objectmentor.library.web.framework. spottet; javax.servlet importieren.*; javax.servlet importieren. http.*; import java.io.*; import java.security.Principal; import java.util.*; öffentliche Klasse MockHttpServletRequest implementiert HttpServletRequest { private String-Methode; private String contextPath; private String requestURI; private HttpSession session = new MockHttpSession(); private Map attributes = new HashMap(); private Map attributes = new HashMap(); public MockHttpServletRequest(String method, String contextPath, String requestURI) { super(); this.method = method; this.contextPath = contextPath; this .requestURI = requestURI; } public MockHttpServletRequest() { this("GET"); } public MockHttpServletRequest(String method) { this(method, "/Library", "/Library/foo/bar.jsp"); } public String getContextPath() { return contextPath; } public String getMethod() { return method; } public String getRequestURI() { Return RequestURI; } public String getServletPath() { return requestURI.substring(getContextPath().length()); } public HttpSession getSession() { Rückkehrsitzung; } public HttpSession getSession(boolean arg0) { return session; } öffentliches Objekt getAttribute (String arg0) { return attributes.get(arg0); } public String getParameter(String arg0) { return (String)parameters.get(arg0); } public Map getParameterMap() { Rückgabeparameter; } öffentliche Aufzählung getParameterNames() { return null; } public void setSession(HttpSession session) { this.session = session; } public void setParameter(String s, String s1) { parameters.put(s, s1); } public void setAttribute(String name, Object value) { attributes.put(name, value); } // Viele degenerierte Methoden wurden eliminiert. } MockHttpServletResponse Paket com.objectmentor.library.web.framework.mocks; import javax.servlet.ServletOutputStream; import javax.servlet.http. *; import java.io.*; import java.util.Locale; öffentliche Klasse MockHttpServletResponse implementiert HttpServletResponse { // alle Funktionen sind degeneriert implementiert. } 有了这些mock对象,现在我就可以创建一个LoanRecords_jsp的servlet实例并且开始调用它!我的头一个测试用例就像下面这样: 就像预期的一样,测试失败了。这是因为还有些内容还没补充上,不过所剩无多。如果你仔细的看过Jsp文件,你就会发现它调用了request.getAttribute(“loanRecords”)并且期望返回一个List。但因为目前的测试并未为这样的属性赋值,从而导致了代码抛出了异常。 要想成功让servlet输出HTML,我们还需要加载这个属性。然后,我们就可以使用HtmlUnit来解析此HTML并且编写相应的单元测试。 HtmlUnit非常的容易使用,尤其是在测试所产生的像是本例这样的web pages上。我这里还有篇文章详细的介绍了它。 下面就是最终测试加载属性的测试,它通过htmlunit来检测HTML,并且做出正确的判断: 这篇发表在此的技术能够用来测试几乎所有目前我们所见过的web页面,并且脱离容器,也无需web server的运行。相对来说,它也比较容易去设置,并且非常易于扩展。有了它,你就可以快速的进行编辑、编译、测试的周期性迭代,并且你也能遵循测试驱动开发的原则了。<br> (原文链接网址: http://blog.objectmentor.com/articles/category/testing-guis; Robert C. Martin的英文blog网址: http://blog.objectmentor.com/) 作者简介:Robert C. Martin是Object Mentor公司总裁,面向对象设计、模式、UML、敏捷方法学和极限编程领域内的资深顾问。他不仅是Jolt获奖图书《敏捷软件开发:原则、模式与实践》(中文版)(《敏捷软件开发》(英文影印版))的作者,还是畅销书Designing Object-Oriented C++ Applications Using the Booch Method的作者。Martin是Pattern Languages of Program Design 3和More C++ Gems的主编,并与James Newkirk合著了XP in Practice。他是国际程序员大会上著名的发言人,并在C++ Report杂志担任过4年的编辑。 Das obige ist der detaillierte Inhalt vonSo testen Sie JSP-Seiten außerhalb des Containers. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!/n");
/n");
/n");ID /n");
Title /n");
Fälligkeitsdatum< /th>/n");
Fine /n") ;
/n");");
/n");
");
/n");
");
/n");
");
/n");
Endgültige Beschwerde
<br/>
public void testSimpleTest() throws Exception {
MockJspWriter jspWriter = new MockJspWriter();
MockPageContext pageContext = new MockPageContext(jspWriter);
JspFactory.setDefaultFactory(new MockJspFactory(pageContext));
HttpJspBase jspPage = new loanRecords_jsp();
HttpServletRequest request = new MockHttpServletRequest();
HttpServletResponse response = new MockHttpServletResponse();
<br/>
jspPage._jspInit();
jspPage._jspService(request, response);
<br/>
assertEquals("", jspWriter.getContent());
}
<br/>
<br/>
package com.objectmentor.library.jspTest.books.patrons.books;
<br/>
import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.html.*;
import com.objectmentor.library.jsp.WEB_002dINF.pages.patrons.books.loanRecords_jsp;
import com.objectmentor.library.utils.*;
import com.objectmentor.library.web.controller.patrons.LoanRecord;
import com.objectmentor.library.web.framework.mocks.*;
import junit.framework.TestCase;
import org.apache.jasper.runtime.HttpJspBase;
<br/>
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import java.util.*;
<br/>
public class LoanRecordsJspTest extends TestCase {
private MockPageContext pageContext;
private MockJspWriter jspWriter;
private JspFactory mockFactory;
private MockHttpServletResponse response;
private MockHttpServletRequest request;
private WebClient webClient;
private TopLevelWindow dummyWindow;
<br/>
protected void setUp() throws Exception {
jspWriter = new MockJspWriter();
pageContext = new MockPageContext(jspWriter);
mockFactory = new MockJspFactory(pageContext);
<br/>
JspFactory.setDefaultFactory(mockFactory);
response = new MockHttpServletResponse();
request = new MockHttpServletRequest();
webClient = new WebClient();
webClient.setJavaScriptEnabled(false);
dummyWindow = new TopLevelWindow("", webClient);
}
<br/>
public void testLoanRecordsPageGeneratesAppropriateTableRows() throws Exception {
HttpJspBase jspPage = new loanRecords_jsp();
jspPage._jspInit();
<br/>
List<LoanRecord> loanRecords = new ArrayList<LoanRecord>();<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false"> addLoanRecord(loanRecords,
"99",
"Empire",
DateUtil.dateFromString("2/11/2007"),
new Money(4200));
addLoanRecord(loanRecords,
"98",
"Orbitsville",
DateUtil.dateFromString("2/12/2007"),
new Money(5200));
<br>
request.setAttribute("loanRecords", loanRecords);
<br>
jspPage._jspService(request, response);
<br>
StringWebResponse stringWebResponse = new StringWebResponse(jspWriter.getContent());
HtmlPage page = HTMLParser.parse(stringWebResponse, dummyWindow);
HtmlElement html = page.getDocumentElement();
<br>
HtmlTable table = (HtmlTable) html.getHtmlElementById("loanRecords");
List<HtmlTableRow> rows = table.getHtmlElementsByTagName("tr");
assertEquals(3, rows.size());
<br>
assertEquals("even", classOfElement(rows.get(1)));
assertEquals("odd", classOfElement(rows.get(2)));
<br>
List<HtmlTableDataCell> firstRowCells = rows.get(1).getCells();
assertEquals(4, firstRowCells.size());
<br>
List<HtmlTableDataCell> secondRowCells = rows.get(2).getCells();
assertEquals(4, secondRowCells.size());
<br>
assertLoanRecordRowEquals("99", "Empire", "02/11/2007", "$42.00", firstRowCells);
assertLoanRecordRowEquals("98", "Orbitsville", "02/12/2007", "$52.00", secondRowCells);
}
<br>
private String classOfElement(HtmlTableRow firstDataRow) {return firstDataRow.getAttributeValue("class");}
<br>
private void assertLoanRecordRowEquals(String id, String title, String dueDate, String fine, List<HtmlTableDataCell> rowCells) {
assertEquals(id, rowCells.get(0).asText());
assertEquals(title, rowCells.get(1).asText());
assertEquals(dueDate, rowCells.get(2).asText());
assertEquals(fine, rowCells.get(3).asText());
}
<br>
private void addLoanRecord(List<LoanRecord> loanRecords, String id, String title, Date dueDate, Money fine) {
LoanRecord loanRecord = new LoanRecord();
loanRecord.id = id;
loanRecord.title = title;
loanRecord.dueDate = dueDate;
loanRecord.fine = fine;
<br>
loanRecords.add(loanRecord);
}
<br>
private class MockJspFactory extends JspFactory {
private PageContext pageContext;
public MockJspFactory(PageContext pageContext) {
this.pageContext = pageContext;
}
<br>
public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse, String string, boolean b, int i, boolean b1) {
return pageContext;
}
<br>
public void releasePageContext(PageContext pageContext) {
}
<br>
public JspEngineInfo getEngineInfo() {
return null;
}
}
}
<br>
<span style="font-size: 9pt;">上述的测试确保了所生成的HTML中表格中的每一行都具有正确的内容。这项测试确实能够测出是否存在这样的表格,并且判断出是否表格的每一行是按照正确的顺序来展现的。同时,它也确保了每一行的相应style。测试忽略了此外的表单以及语法部分。</span>
结论