웹 애플리케이션 개발에서 가장 짜증나는 점은 테스트하려면 배포해야 한다는 것입니다. 물론 모든 부분이 이렇지는 않습니다. 신중하게 디자인하면 Java 프로그램에서 비즈니스 로직을 테스트할 수 있습니다. 애플리케이션 서버를 실행하지 않고도 데이터 액세스, 인터페이스 및 저장 프로시저를 테스트할 수 있습니다. 그러나 GUI(Jsp에서 생성된 HTML)를 테스트하는 경우 테스트하기 전에 먼저 배포해야 합니다.
많은 팀이 웹 서버를 통해 GUI를 테스트하기 위해 Sellenium, Mercury 또는 기타 도구를 사용합니다. 그러나 페이지의 내용은 그대로 유지하고 스타일이 변경되더라도 테스트는 취약해집니다. 다른 팀에서는 Cactus를 사용하여 이 취약점을 해결하거나 HtmlUnit 및 HttpUnit과 같은 기본 도구를 사용하여 웹 애플리케이션에서 생성된 HTML을 모니터링합니다. 이러한 문제는 다른 블로그 시리즈에서 논의하겠습니다.
이 기사에서는 JUnit이나 HtmlUnit을 사용하여 Jsp 페이지를 테스트하고 컨테이너와 완전히 분리되는 간단하고 쉬운 기술을 소개하겠습니다. 이는 이 기술의 장점이기도 합니다.
컨테이너를 계속 실행하거나 기존 상태로 유지할 필요는 없습니다. 특정 웹서버를 선택하기 전에 Jsp를 테스트할 수 있습니다.
수정할 때마다 다시 배포할 필요가 없으므로 편집/컴파일/테스트 프로세스가 더 빨라집니다.
테스트 우선 개발을 사용하여 지속적으로 Jsp를 빌드할 수 있습니다.
JSP 기술이 컨테이너 외부에서 인기가 없는 이유는 JSP가 컨테이너 내부에서 실행되도록 설계되었기 때문입니다. 디자이너들은 컨테이너 외부에서 실행될 가능성에 대해 많이 생각해 본 적이 없습니다. 따라서 Jsp 컴파일러에서 생성된 코드는 컨테이너에서 제공하는 많은 구성 요소에 의존하는 경우가 많습니다. JSP 코드를 생성하는 도구라도 이미 성공적으로 배포된 웹 애플리케이션이 실행되고 있다고 가정합니다. 따라서 컨테이너 외부에서 실행하려면 이러한 도구와 구성 요소를 개발해야 합니다.
왜 그렇게 많은 프레임워크와 도구의 설계자들은 여러분이 자신들이 제공하는 작은 세상에서 살기를 기대합니까? JSP를 컴파일하기 전에 완전한 웹 애플리케이션을 구축해야 하는 이유는 무엇입니까? 왜 이런 것들이 컨테이너에서 실행되어야 합니까? 정보 은닉은 이미 10년 전부터 좋은 소프트웨어 설계의 기본 원칙이었습니다. 우리 업계는 언제 이것을 심각하게 받아들일까요?
Jsp 테스트의 첫 번째 단계는 이를 서블릿으로 컴파일하는 것입니다. 이 단계를 달성하려면 먼저 Jsp를 Java 형식으로 변환해야 합니다. Apache는 Jasper라는 도구를 제공하여 MyPage.jsp에 대한 Java 형식 소스 파일 MyPage_jsp.java를 생성합니다. 그런 다음 선호하는 IDE를 사용하여 이 파일을 서블릿으로 컴파일할 수 있습니다.
안타깝게도 Jasper는 명령줄에서 사용하도록 설계되지 않았거나 정확히 그런 식으로 설계되지 않았습니다. 그러나 Jasper에는 명령줄 매개변수를 처리하는 기본 기능이 있으며 java org.apache.jasper.JspC를 호출하여 쉽게 호출할 수 있습니다. 그러나 Jasper는 실행되는 환경이 컨테이너 환경과 일치할 것으로 기대합니다. 클래스 경로에 Apache Jar 파일이 많이 있는지, 그리고 웹 애플리케이션의 web.xml을 찾을 수 있는지 확인해야 합니다. 또한 웹 애플리케이션 Jar와 TLD 파일 등이 포함된 WEB-INF 디렉터리를 찾을 수 있어야 합니다. 즉, Jasper는 완전한 웹 애플리케이션을 찾을 수 있어야 합니다.
상황이 더 나쁘다면 TOMCAT이 호출되는 방식과 완전히 일치하지 않는 한 일부 특정 Jasper 버전에 버그가 있고(저는 tomcat 5.5.20을 사용하고 있습니다) 이것이 생성하는 코드에 약간의 오류가 있을 것입니다.
먼저 해야 할 일은 지루하지만 비교적 간단합니다. 올바른 디렉터리와 파일 구조를 만든 다음 Ant에서 Jasper를 호출해야 합니다(Classpath가 제어하기 더 쉽습니다). 두 번째 요점은 그것이 작동하려면 약간의 연구와 테스트가 필요하다는 것입니다. 다음은 성공적으로 실행될 수 있는 ant 파일이다. JspC에 대한 호출은 마지막 작업에 나타납니다.
<프로젝트 이름="라이브러리" default="compile" basedir=".">
<속성 환경="env"/>
fileset>
< ;경로요소 위치=" ${catalina.home}/shared/classes"/>
& lt;대상 이름="clean">
< ;classpath refid="compile.classpath"/>
< ;jar jarfile="${build.jar.home}/application.jar" basedir="${build.classes.home}" include="**/application/**/*.class" />
< ;jar jarfile="${dist.home}/${app.name}.war" basedir="${build.war.home}"/> ㅋㅋㅋ <이름 포함= "*.jar"/> < include name="*.jar"/> ㅋㅋㅋ includes= "**/jsp/**/*.class" 물론 ${build.war.home 아래에 모든 표준 파일과 디렉터리가 있어야 합니다. } 작동하는지 확인하세요. Jsp에서 사용자 정의 태그를 사용하는 경우 해당 TLD 파일이 모두 TLD 디렉토리에 있는지 확인하십시오. 이제 Java 파일이 있으므로 분석해 보겠습니다. 먼저 아래 Jsp 파일을 살펴보시기 바랍니다. <%@ 페이지 가져오기="com.objectmentor.library.utils.DateUtil" %><%@ 페이지 가져오기="com.objectmentor.library.web. Controller.patrons.LoanRecord" %> <%@ page import="java.util.List" %>
<%
List LoanRecords = (목록) 요청 .getAttribute("loanRecords");
if (loanRecords.size() > 0) {
%>
<%=loanRecord. id%>
|
<%=loanRecord.title%>
& lt;/td>
| <%=DateUtil.dateToString(loanRecord.dueDate)%>
|
& lt;%=loanRecord.fine. toString()%>
|
<%
}
%>
하면则是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 최종 클래스 LoanRecords_jsp는 org.apache.jasper.runtime.HttpJspBase
을 확장합니다. implements org. 아파치.재스퍼.런타임. JspSourceDependent {
private static java.util.List _jspx_dependents;
public Object getDependants() {
return _jspx_dependents;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
java.io. IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession 세션 = null;
ServletContext 애플리케이션 = null;
ServletConfig 구성 = 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, 참, 8192, 참);
_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 = (목록) request.getAttribute("loanRecords");
if (loanRecords.size() > 0) {
out.write("/n");
out.write("< 테이블 클래스=/"list/" id=/"loanRecords/">/n");
out.write(" out.write(" 번째>ID/n"); out.write(" 번째>제목/n"); out.write(" <일>마감일일>/n"); out.write(" <일>괜찮습니다 일>/n "); out.write(" /n");
/n");
out.write(" ");
for (int i = 0; 나는 < LoanRecords.size(); i++) {
LoanRecord LoanRecord = (LoanRecord) LoanRecords.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(날짜 Util.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(투척 가능 t) {
if(! (t 인스턴스of 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는 JspFactory를 확장합니다. servletResponse, 문자열 문자열, 부울 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 클래스 MockJspWriter는 JspWriter {
을 확장합니다 private StringBuffer submitContent;
public MockJspWriter(int bufferSize, boolean autoFlush) {
super(bufferSize, autoFlush);
submittedContent = new StringBuffer();
}
public String getContent() { return submitContent.toString(); }
public void print(String arg0)에서 IOException이 발생합니다. submittedContent.append(arg0); }
public void write(char[] arg0 , int arg1, int arg2) IOException이 발생합니다. { for (int i=0; i submittedContent.append(String.valueOf(arg0[arg1++])); } public void write(String content)가 IOException을 발생시킵니다. { submittedContent.append(content); } // 흥미롭지 않은 내용이 많음 메서드는 생략되었습니다. 방금 // 구현을 저하시켰습니다. (예: {}) }
无需关心那些我省略掉的未实现이 방법은 내 마음에 드는 测试得以运行적 방법입니다. 나의 IDE는 对于创建这些mock类不常有帮助。它能够自动化的构建方法原型,并为那些接口或是抽象类所需要实现的方法给化的实现。
동일한 사용 방법创建出MockPageContext,MockHttpServletRequest以及MockHttpServletResponse类。
MockPageContext
package com.objectmentor.library.web.framework.mocks;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import java.io.IOException;
import java.util.Enumeration;
public 클래스 MockPageContext는 PageContext를 확장합니다.
비공개 최종 JspWriter 출력;
비공개 HttpServletRequest 요청;
공개 MockPageContext(JspWriter out) {
this.out = out;
request = new MockHttpServletRequest();
}
public JspWriter getOut()
반품;
}
public ServletRequest getRequest() {
반품 요청; } // 많은 퇴화 기능이 제거되었습니다. } MockHttpServletRequest 패키지 com.objectmentor.library.web.framework.mocks; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.security.Principal; import java.util.*; public 클래스 MockHttpServletRequest는 HttpServletRequest를 구현합니다. { private String 메서드 개인 문자열 contextPath; private String requestURI; private HttpSession session = new MockHttpSession(); private Map 매개변수 = new HashMap(); 비공개 지도 속성 = 신규 HashMap(); public MockHttpServletRequest(String method, String contextPath, 문자열 요청URI ) { super(); 이것 .method = 메소드; this.contextPath = contextPath; this.requestURI = requestURI; } public MockHttpServletRequest() { this("GET"); } public MockHttpServletRequest(문자열 메서드) { this(메서드, "/ Library", "/Library/foo/bar.jsp"); } public String getContextPath() { return contextPath; } public String getMethod() { 반환 방법; } 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 arg0) { return attribute.get( arg0); } public String getParameter(String arg0) { return (문자열) 매개변수.get(arg0); } public Map getParameterMap() { 반환 매개변수; } 공개 열거 getParameterNames( ) { return null; } public void setSession(HttpSession 세션) this.session = 세션; } public void setParameter(String s, String s1) { parameters.put(s, s1); } public void setAttribute(문자열 이름, 개체 값) { attributes.put(이름, 값); } // 많은 퇴화 메서드가 제거되었습니다. } 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의 서블릿을 사용했습니다.并且开始调사용它!저의 头一个测试用例就image下면这样: 就像预期的一样,测试失败了。这是因为还有些内容还没补充上,不过所剩无多。如果你仔细的看过Jsp文件,你就会发现它调用了request.getAttribute(“loanRecords”)并且期望返回一个List。但因为目前的测试并未为这样的属性赋值,从而导致了代码抛出了异常。 要想成功让servlet输出HTML,我们还需要加载这个属性。然后,我们就可以使用HtmlUnit来解析此HTML并且编写相应的单元测试。 HtmlUnit非常的容易使用,尤其是在测试所产生的像是本例这样的web pages上。我这里还有篇文章详细的介绍了它。 下面就是最终测试加载属性的测试,它通过htmlunit来检测HTML,并且做出正确的判断:<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>
这篇发表在此的技术能够用来测试几乎所有目前我们所见过的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年的编辑。
위 내용은 컨테이너 외부에서 JSP 페이지를 테스트하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!