ホームページ >Java >&#&チュートリアル >コンテナの外部で JSP ページをテストする方法
Web アプリケーションの開発で最も面倒なことは、テストしたい場合はデプロイしなければならないことです。もちろん、すべての部分がこのようになるわけではありません。注意深く設計すれば、Java プログラムでビジネス ロジックをテストできます。アプリケーション サーバーを実行せずに、データ アクセス、インターフェイス、ストアド プロシージャをテストできます。ただし、GUI (Jsp によって生成された HTML) をテストしている場合は、テストする前にそれをデプロイする必要があります。
多くのチームは、Sellenium、Mercury、またはその他のツールを使用して、Web サーバー経由で GUI をテストします。ただし、ページの内容が同じでスタイルが変更された場合でも、テストは脆弱になります。他のチームは、Cactus を使用してこの脆弱性に対処したり、HtmlUnit や HttpUnit などの原始的なツールを使用して Web アプリケーションによって生成された HTML を監視しています。これらの問題については、別の一連のブログで説明する予定です。
この記事では、JUnit または HtmlUnit を使用して Jsp ページをテストし、コンテナから完全に分離するシンプルで簡単な技術を紹介します。これもこの技術の利点です。
必ずしもコンテナーを実行し続ける必要はなく、さらには存在し続ける必要もありません。特定の Web サーバーを選択する前に、Jsp をテストできます。
変更のたびに再デプロイする必要がないため、編集/コンパイル/テストのプロセスが速くなります。
テストファースト開発を使用して、Jsp を継続的にビルドできます。
Jsp テクノロジーがコンテナーの外で普及していない理由は、Jsp がコンテナー内で実行されるように設計されているためです。設計者は、コンテナの外で実行する可能性についてはあまり考えていませんでした。したがって、Jsp コンパイラーによって生成されるコードは、多くの場合、コンテナーによって提供される多くのコンポーネントに依存します。 JSP コードを生成するツールでも、正常にデプロイされた Web アプリケーションがすでに実行されていることを前提としています。したがって、コンテナの外で実行するには、これらのツールとコンポーネントを開発する必要があります。
なぜ、これほど多くのフレームワークやツールの設計者は、あなたがそれらが提供する小さな世界に住むことを期待するのでしょうか? JSP をコンパイルする前に完全な Web アプリケーションを構築する必要があるのはなぜですか?なぜこれらのものをコンテナ内で実行する必要があるのでしょうか?情報の隠蔽は、10 年前から優れたソフトウェア設計の基本原則でした。私たちの業界はいつこの問題を真剣に受け止めるでしょうか?
Jsp をテストする最初のステップは、Jsp をサーブレットにコンパイルすることです。このステップを実行するには、最初に Jsp を Java 形式に変換する必要もあります。 Apache には Jasper というツールが用意されており、Jasper を呼び出して MyPage.jsp の Java 形式のソース ファイル MyPage_jsp.java を作成します。その後、お気に入りの IDE を使用して、このファイルをサーブレットにコンパイルできます。
残念ながら、Jasper はコマンド ラインで使用するように設計されていません。あるいは、まさにそのように設計されていません。ただし、Jasper にはコマンド ライン パラメーターを処理するための main 関数があり、java org.apache.jasper.JspC を呼び出すことで簡単に呼び出すことができます。ただし、Jasper は、それが実行される環境がコンテナー環境と一致していることを期待します。クラスパス上に多数の Apache Jar ファイルが存在し、Web アプリケーションの web.xml を見つけることができることを確認する必要があります。また、Web アプリケーション Jar や TLD ファイルなどが含まれる WEB-INF ディレクトリを見つけられる必要もあります。つまり、Jasper は完全な Web アプリケーションを見つけることができる必要があります。
さらに悪いことに、TOMCAT の呼び出し方法と完全に一致していない限り、特定の Jasper バージョン (私は Tomcat 5.5.20 を使用しています) にいくつかのバグがあり、生成されるコードにはいくつかのエラーが含まれます。
最初に行うことは面倒ですが、比較的簡単です。正しいディレクトリとファイル構造を作成してから、Ant で Jasper を呼び出す必要があります (クラスパスの方が制御が簡単です)。 2 番目のポイントは、それを機能させるには調査とテストが必要であるということです。以下は、正常に実行できる Ant ファイルです。 JspC への呼び出しは最後のタスクに表示されます。
<プロジェクト名="ライブラリ" デフォルト="コンパイル" basedir=".">
<プロパティ環境="env"/>
fileset>
<パス要素の場所= ${catalina.home}/shared/classes"/>
<ターゲット名="clean">
< ;classpath refid="compile.classpath"/>
&&lt;ターゲット名= "jar"依存= "コンパイル"&gt;
&lt; mkdir dir = "$ {build.jar.home}"/&gt;
&lt ;jar jarfile="${build.jar.home}/application.jar" basedir="${build.classes.home}" include="**/application/**/*.class" />
<コピー先dir="${ビルド。 war.home}">
< ;jar jarfile="${dist.home}/${app.name}.war" basedir="${build.war.home}"/>
<クラスパス>
<名前=を含める"*.jar"/>
< include name="*.jar"/>
「 >
includes= "**/jsp/**/*.class"
もちろん、${build.war の下にすべての標準ファイルとディレクトリが必要です。 home} を実行して、機能することを確認します。 Jsp でカスタム タグを使用する場合は、対応するすべての TLD ファイルが TLD ディレクトリにあることも確認してください。
Jspc のコマンド ラインは、Tomcat が提供する JspC Ant タスクを使用するのではなく、ant ファイル内で呼び出されることに注意してください。カスタムタグがあると正しく動作しないことがわかったためです。おそらく私が混乱しているか、JspC に実際にバグがあるのかもしれません。しかし、Jasper に正しいコードを生成させる唯一の方法は、Jasper をコマンド ラインから呼び出し、Jsp ファイル パスをコマンド ライン引数として明示的に渡すことです。 Ant タスクに依存するか、コマンド ラインを使用してすべての Web アプリケーションで Jsps を検索してコンパイルすると、不正なコードが生成されます。 (このブログを参照してください)
Java ファイルを取得したので、それを分析しましょう。まずは、以下のJspファイルをご覧ください。
<%@ page import="com.objectmentor.library.utils.DateUtil" %>
<%@ page import="com.objectmentor.library.web. controller.patrons.LoanRecord" %>
<%@ page import="java.util.List" %><%
List loanRecords = (リスト) request .getAttribute("loanRecords");
if (loanRecords.size() > 0) {
%>
期限 |
| 罰金 | |
---|---|---|---|
<%=loanRecord。 id%>
|
<%=loanRecord.title%>
|
<%=DateUtil.dateToString(loanRecord.dueDate)%>
|
|
<%
}
%>
次は Jasper が生成した代価です
package com.objectmentor.library.jsp.WEB_002dINF.pages.patrons.books;
import javax.servlet.*;
javax.servletをインポートする.http.*;
import javax.servlet.jsp.*;
import com.objectmentor.library.utils.DateUtil;
import com.objectmentor.library.web.controller.patrons。 LoanRecord;
import java.util.List;
public Final class loanRecords_jsp extends org.apache.jasper.runtime.HttpJspBase
実装 またはg.apache.jasper.runtime。 JspSourceDependent {
private static java.util.List _jspx_dependants;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException、ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
サーブレットコンテキスト アプリケーション = null;
ServletConfig config = null;
JspWriter out = null;
オブジェクトページ = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
試してください {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html");
pageContext = _jspxFactory.getPageContext(this, リクエスト, レスポンス,
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');
アウト。 write('/n');
out.write('/n');
List loanRecords = (List) request.getAttribute("loanRecords");
if (loanRecords.size() > 0) {
out.write("/n");
out.write("< table class=/"list/" id=/"loanRecords/">/n");
out.write(" out.write(" out.write(" out.write("> ;/th>/n "); out.write(" /n");
/n");ID/n");
タイトル /n");
out.write(" ");
for (int i = 0;私は<ローンレコード.サイズ(); i++) {
LoanRecord loanRecord = (LoanRecord) launchRecords.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"); アウト。 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("
/n"); ");
/n");
");
/n");
");
/n");
"); /n");
out.write(" ");
}
out.write("/n") ;
アウト。 write("/n");
}
} catch (Throwable t) {
if (! (t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer() ;
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
} } ついに { if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context ); } } }
なぜこのクラスが最終と宣言される必要があるのですか?テスト スタブの派生クラスを作成したい場合はどうすればよいですか?生成されたクラスが非常に不快で、オーバーライドすることさえできないのはなぜでしょうか?
このコードを注意深く読むと、このサーブレット インスタンスを使用するには HttpServletRequest と HttpServletResponse のインスタンスが必要であることがわかります。
さらに注意深く調べると、サーブレットはすべての HTML を JspWriter のインスタンスに書き込み、JspWriter は PageContext から取得されることがわかります。このすべての HTML を保持する JspWriter のモックアップ バージョンを作成し、次にモック JspWriter を送出する PageContext のモックアップ バージョンを作成できれば、テストでこの HTML にアクセスできるようになります。
幸いなことに、Tomcat の設計者は、JspWriter の作成を JspFactory のファクトリー クラスに組み込みました。そして、このファクトリー クラスはオーバーライドできます。これは、サーブレットを変更せずに、サーブレット内で独自の JspWriter クラスを取得できることを意味します。必要なのは次のコードだけです。
class MockJspFactory extends JspFactory {
public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, letResponse servletResponse、文字列 string、ブール値 b、int i、ブール値 b1) {
return new MockPageContext(new MockJspWriter());
}
public void releasePageContext( PageContext pageContext) {
}
public JspEngineInfo getEngineInfo() {
return null;
}
}
さて、私たちは必要なのはモック Jspwriter です。プレゼンテーションを簡単にするために、次のものを使用しました:
MockJspWriter
package com.objectmentor.library.web.framework.mocks;
javax.servletをインポートします。 jsp.JspWriter;
import java.io.IOException;
public class MockJspWriter extends JspWriter {
プライベート StringBuffer が送信されたコンテンツ;
public MockJspWriter(intbufferSize, boolean autoFlush) {
super(bufferSize, autoFlush);
submittedContent = new StringBuffer();
}
public String getContent() {
return submitContent.toString();
}
public void print(String arg0) throws IOException {
submittedContent.append(arg0);
}
public void write(char[] 、int arg1、int arg2) IOException をスローします {
for (int i=0; i
submittedContent.append(String.valueOf(arg0[arg1++]));
}
public void write(String content) throws IOException {
submittedContent.append(content);
}
// 面白くないことがたくさんあるメソッドは省略されました。 私はそれらを与えただけです
// 実装を縮退します。 (例: {})
}
不要关心那珂我省略掉的未实现方法としては、私たちのテストを実行するために必要なだけの方法で十分であると考えています。以下の場合は、その退化を使用するだけです。
同様のメソッドを使用して、MockPageContext、MockHttpServletRequest、および MockHttpServletResponse クラスを構築できます。
MockPageContext
package com.objectmentor.library.web.framework.mocks;import javax.servlet.*;
import javax.サーブレット.http.*;
import javax.servlet.jsp.*;
import java.io.IOException;
import java.util.Enumeration;
public class MockPageContext extends PageContext {
プライベート最終JspWriter出力;
プライベートHttpServletRequestリクエスト;
パブリックMockPageContext(JspWriter out) {
this.out = out;
request = new MockHttpServletRequest();
}
getOut( ) {
出てください;
}
public ServletRequest getRequest() {
return request; } // 多くの縮退関数が省略されました。 } MockHttpServletRequest パッケージ com.objectmentor.library.web.framework.mocks; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import主要; import java.util.*; public class MockHttpServletRequestimplements HttpServletRequest { private Stリングメソッド; private String contextPath; private String requestURI; private HttpSession session = new MockHttpSession(); private Map パラメータ = new HashMap(); プライベートマップ属性 = 新しいHashMap(); public MockHttpServletRequest(Stringメソッド, String contextPath, 文字列 requestURI) { super(); this .method = メソッド; this.contextPath = contextPath; this.requestURI = requestURI; } public MockHttpServletRequest() { this("GET"); } public MockHttpServletRequest(Stringメソッド) { this(メソッド, "/ライブラリ", "/Library/foo/bar.jsp"); } public String getContextPath() { contextPath を返す; } public String getMethod() { returnメソッド; } public String getRequestURI( ) { return requestURI; } public String getServletPath() { return requestURI.substring(getContextPath().length ()); } public HttpSession getSession() { リターンセッション; } public HttpSession getSession (boolean arg0) { return session; } public Object getAttribute(String 0) { returnattributs.get( arg0); } public String getParameter(String arg0) { return (文字列)parameters.get(arg0); } public Map getParameterMap() { returnパラメータ; } public Enumeration getParameterNames( ) { return null; } public void setSession(HttpSession session) { this.session = セッション; } public void setParameter(String s, String s1) { parameters.put(s, s1); } public void setAttribute(String name, Object value) { attributes.put(name, value); } // 多くの縮退メソッドが省略されました。 } MockHttpServletResponse package com.objectmentor.library.web.framework.mocks; javaxをインポートする.servlet.ServletOutputStream; import javax.servlet.http.*; import java.io.*; import java.util.Locale; 公開クラスMockHttpServletResponse は HttpServletResponse を実装します { // すべての関数は縮退するように実装されています。 } loanRecords_jsp のサーブレットの例を作成できるそして、调用を開始します! 私たちの唯一の赋试用例は次のとおりです: 就像预期的一样,测试失败了。这是因为还有些内容还没补充上,不过所剩无多。如果你仔细的看过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年的编辑。<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>();<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>
结论
以上がコンテナの外部で JSP ページをテストする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。