Maison  >  Article  >  Java  >  Comment tester les pages JSP en dehors du conteneur

Comment tester les pages JSP en dehors du conteneur

巴扎黑
巴扎黑original
2017-08-10 15:12:491558parcourir

JspTechnologie de test

Développer le Web L’un des aspects les plus ennuyeux des applications est qu’il faut les déployer pour les tester. Bien entendu, toutes les parties ne sont pas ainsi. Si vous concevez soigneusement, vous pouvez tester la logique métier dans un programme Java. Vous pouvez tester l'accès aux données, les interfaces et les procédures stockées sans que le serveur d'applications ne soit en cours d'exécution. Cependant, si vous testez l'interface graphique (HTML généré par Jsp), vous devez la déployer avant de pouvoir la tester.

De nombreuses équipes se tournent vers Sellenium, Mercury ou d'autres outils pour tester les interfaces graphiques via des serveurs Web. Cependant, même si le contenu de la page reste le même mais que le style change, le test deviendra fragile. D'autres équipes utilisent Cactus pour remédier à cette vulnérabilité, ou des outils primitifs comme HtmlUnit et HttpUnit pour surveiller le HTML généré par les applications Web. Je discuterai de ces questions dans une autre série de blogs.

Dans cet article, je présenterai une technologie simple et facile qui utilise JUnit ou HtmlUnit pour tester les pages Jsp et est complètement séparée du conteneur. C'est aussi l'avantage de cette technologie.

Vous n'êtes pas nécessairement obligé de maintenir le conteneur en fonctionnement ou même d'exister. Vous pouvez tester votre Jsp avant de choisir un serveur Web spécifique.

Vous n'avez pas besoin de redéployer après chaque modification, le processus d'édition/compilation/test sera donc plus rapide.

Vous pouvez utiliser le développement axé sur les tests pour créer en continu Jsp.

La raison pour laquelle la technologie de test Jsp en dehors du conteneur n'est pas populaire est que Jsp est conçu pour s'exécuter dans le conteneur. Les concepteurs n’ont jamais beaucoup réfléchi à la possibilité de fonctionner en dehors du conteneur. Par conséquent, le code généré par le compilateur Jsp repose souvent sur de nombreux composants fournis par le conteneur. Même les outils qui génèrent du code JSP supposent qu’une application Web déployée avec succès est déjà en cours d’exécution. Ainsi, pour fonctionner en dehors des conteneurs, vous devez développer ces outils et composants.

Plaintes concernant la gestion des dépendances

Pourquoi les concepteurs de tant de frameworks et d'outils vous attendent-ils toujours ? Vivre dans le petit monde qu'ils offrent ? Pourquoi dois-je créer une application Web complète avant de compiler JSP ? Pourquoi ces choses doivent-elles fonctionner dans des conteneurs ? La dissimulation d’informations était un principe de base d’une bonne conception logicielle il y a déjà 10 ans. Quand notre industrie prendra-t-elle cela au sérieux ?

Compilation de Jsp

La première étape pour tester une Jsp consiste à la compiler dans une servlet. Pour réaliser cette étape, nous devons également d'abord convertir Jsp au format Java. Apache fournit un outil appelé Jasper Nous appelons Jasper pour créer un fichier source au format Java MyPage_jsp.java pour MyPage.jsp. Vous pouvez ensuite compiler ce fichier dans un servlet à l'aide de votre IDE préféré.

Malheureusement, Jasper n'est pas conçu pour être utilisé en ligne de commande, ou il n'est pas entièrement conçu de cette façon. Mais Jasper a une fonction principale pour traiter les paramètres de ligne de commande, et elle peut être facilement appelée en appelant java org.apache.jasper.JspC. Cependant, Jasper s'attend à ce que l'environnement dans lequel il s'exécute soit cohérent avec l'environnement du conteneur. Vous devez vous assurer qu'il y a beaucoup de fichiers Apache Jar sur le chemin de classe et qu'il peut trouver le web.xml de l'application Web. Il doit également être capable de trouver le répertoire WEB-INF qui contient l'application Web Jar ainsi que les fichiers TLD, etc. Bref, Jasper doit pouvoir trouver une application web complète.

Si les choses sont pires, à moins que cela ne soit tout à fait cohérent avec la façon dont TOMCAT est appelé, il y a quelques bugs dans certaines versions spécifiques de Jasper (j'utilise Tomcat 5.5.20), Le code il génère des erreurs.

La première chose à faire est fastidieuse mais relativement simple. Vous devez créer la bonne structure de répertoires et de fichiers, puis appeler Jasper dans Ant (Classpath est plus facile à contrôler). Le deuxième point est que cela nécessite des recherches et des tests pour que cela fonctionne. Voici le fichier fourmi qui peut être exécuté avec succès. L'appel à JspC apparaît dans la dernière tâche.

 

 

 

 

 

 

 

   

     

   

   

   

     

   

   

     

   

   

   

     

   

 

 

   

   

 

 

   

   

     

   

 

 

   

   

  < /target>

 

   

     

   

   

     

   

    

   

 

 

   

   

     

     

     

        < ;fileset dir="${catalina.home}/common/lib">

         

       

       

         

       

       

         

    

                                          > 

 

  includes="**/jsp/** /*.class"

 />

Bien sûr, vous voulez que tous les critères Les fichiers et répertoires soient sous ${build.war.home} pour garantir le fonctionnement. Si vous utilisez des balises personnalisées dans votre Jsp, assurez-vous également que tous les fichiers TLD correspondants se trouvent dans votre répertoire TLD.

Il est à noter que la ligne de commande de Jspc est appelée dans le fichier ant au lieu d'utiliser la tâche Ant JspC fournie par Tomcat. Parce que j'ai trouvé que cela ne fonctionnait pas correctement lorsque vous aviez des balises personnalisées. Peut-être que je suis confus, ou qu'il y a effectivement un bug dans JspC. Mais le seul moyen que j'ai trouvé pour que Jasper génère du code correct est de l'appeler depuis la ligne de commande et de transmettre explicitement le chemin du fichier Jsp comme argument de ligne de commande ! Si vous comptez sur sa tâche Ant ou utilisez la ligne de commande pour rechercher des Jsps dans toutes les applications Web à compiler, cela générera un code incorrect. (Voir ce blog)

Maintenant que nous avons le fichier Java, analysons-le. Tout d’abord, veuillez jeter un œil au fichier Jsp ci-dessous.

<%@ page import="com.objectmentor.library.utils.DateUtil" %>

<%@ page import="com.objectmentor.library.web.controller.patrons.LoanRecord" %>

<% @ page import="java.util.List" %>

<%

Liste LoanRecords = (Liste) request.getAttribute("loanRecords");

if (loanRecords.size() > 0) {

%>

                                                  >

 < ;th>Date d'échéance

 

 

<%

pour ( int i = 0; i < prêtRecords.size(); i++) {

 LoanRecord prêtRecord = (LoanRecord) prêtRecords.get(i);

%>

 

">

   

   

   

 

  <%

    }

  %>

Titre Bien
<%=loanRecord.id%>

   

<%=loanRecord.title%>

   

   

<%=DateUtil.dateToString(loanRecord.dueDate)%>

   

<%=loanRecord.fine.toString()%>

   

<%

  }

%>

下面则是Jasper所生成的代码。

package com.objectmentor.library.jsp.WEB_002dINF.pages.patrons.books ;

importer javax.servlet.*;

importer javax.servlet.http .*;

import javax.servlet.jsp.*;

import com.objectmentor.library.utils.DateUtil;

importer com.objectmentor.library.web.controller.patrons.LoanRecord;

importer java.util.List;

public final class LoanRecords_jsp étend org.apache.jasper.runtime.HttpJspBase

    implémente org.apache.jasper.runtime.JspSourceDependent {

  java.util.List statique privée _jspx_dependants;

  objet public getDependants() {

return _jspx_dependants;

  }

  public void _jspService (demande HttpServletRequest, réponse HttpServletResponse)

        lance java.io.IOException, ServletException {

    JspFactory _jspxFactory = null;

    PageContext pageContext = null;

    Session HttpSession = null;

    Application ServletContext = null;

    ServletConfig config = null;

    JspWriter out = null;

    Page d'objet = ceci ;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    essayez {

      _jspxFactory = JspFactory.getDefaultFactory();

      response.setContentType("text/html");

      pageContext = _jspxFactory.getPageContext (ceci, demande, réponse,

                  nul, vrai, 8192, vrai);

      _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("

/n");

      out.write(" 

/n");

      out.write( "   

/n");

      out.write("    

/n");

      out.write("   

/n") ;

      out.write(" 

/n");

      out.write("  ");

    for (int i = 0; i < 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("   

/n");

      out.write("   

/n");

      out.write("   

/n");

      out.write("   

/n");

      out.write(" 

/n");

      out.write("  ");

    }

      out.write("/n");

      out.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);

 }

 } enfin {

 if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);

 }

}

}

Plainte finale

Pourquoi ce cours devrait-il être déclaré final ? Que faire si je souhaite créer une classe dérivée d'un stub de test ? Pourquoi quelqu'un trouverait-il une classe générée si offensante que je ne peux même pas la remplacer ?

Lisez attentivement ce code et vous constaterez que pour utiliser cette instance de servlet, nous avons besoin des instances HttpServletRequest et HttpServletResponse.

Si nous l'étudions plus attentivement, nous constaterons que la servlet écrit tout le HTML sur une instance de JspWriter, et JspWriter est obtenu à partir du PageContext. Si nous pouvions créer une version maquette de JspWriter pour contenir tout ce HTML, puis créer une version maquette de PageContext pour distribuer le JspWriter fictif, nous serions alors en mesure d'accéder à ce code HTML dans nos tests.

Heureusement, les concepteurs de Tomcat ont placé la création de JspWriter dans la classe usine de JspFactory. Et cette classe d’usine peut être remplacée ! Cela signifie que nous pouvons obtenir notre propre classe JspWriter dans le servlet sans changer le servlet. Tout ce dont vous avez besoin est le code suivant.

la classe MockJspFactory étend JspFactory {

public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse, String string, boolean b, int i, boolean b1) {

 retourne le nouveau MockPageContext(new MockJspWriter());

 }

publication nullePageContext(PageContext pageContext) {

}

public JspEngineInfo getEngineInfo() {

return null;

 }

}

Maintenant, ce dont nous avons besoin, c'est d'un Jspwriter simulé. Pour faciliter la présentation, j'ai utilisé ce qui suit :

MockJspWriter

package com.objectmentor.library.web.framework.mocks;

import javax.servlet.jsp.JspWriter;

import java.io.IOException;

classe publique MockJspWriter étend JspWriter {

private StringBuffer submitContent;

public MockJspWriter(int bufferSize, boolean autoFlush) {

super(bufferSize, autoFlush);

subownedContent = new StringBuffer();

}

  public String getContent() {

    return submitContent.toString();

  }

  public void print(String arg0) lance IOException {

    subownedContent.append(arg0);

  }

  public void write(char[] arg0, int arg1, int arg2) lance IOException {

    pour (int i=0; i

      subownedContent.append(String.valueOf (arg0[arg1++]));

  }

  public void write(String content) lance IOException {

    subownedContent.append(content);

  }

  // beaucoup de méthodes inintéressantes ont été éludées.  Je viens de leur donner

  // implémentations dégénérées.  (par exemple {})

}

无需关心那些我省略掉的未实现方法,我认为只需要关心那些对于剩下的,我只会使用其退化实现。

IDE 对于创建这些mock类非常有帮助。它能够自动化的构建方法原型,并为那些接口或是抽象类所需要实现的方法给出退化的实现。

同样的用类似方法创建出MockPageContext,MockHttpServletRequest以及MockHttpServletResponse类。

MockPageContext

package com.objectmentor.library.web .framework.mocks;

importer javax.servlet.*;

importer javax .servlet.http.*;

importer javax.servlet.jsp.*;

importer java.io.IOException;

import java.util.Enumeration;

classe publique MockPageContext étend PageContext {

  finale privée JspWriter out ;

  privé Demande HttpServletRequest ;

  public MockPageContext (JspWriter out) {

    this.out = out;

    request = new MockHttpServletRequest();

  }

  public JspWriter getOut() {

    revenir;

  }

  public ServletRequest getRequest() {

    demande de retour ;

  }

  // beaucoup de fonctions dégénérées élidées.

>

MockHttpServletRequest

package com.objectmentor.library.web.framework. se moque ;

importer javax.servlet.*;

importer javax.servlet. http.*;

importer java.io.*;

importer java.security.Principal;

importer java.util.*;

classe publique MockHttpServletRequest implémente HttpServletRequest {

  méthode String privée ;

  contextPath de chaîne privée ;

  chaîne privée requestURI;

  session HttpSession privée = new MockHttpSession();

  paramètres de carte privés = new HashMap();

  attributs de carte privés = new HashMap();

  public MockHttpServletRequest (méthode String, String contextPath,

string requestUri) {

super ();

this.method = méthode;

    this.contextPath = contextPath;

    this .requestURI = requestURI;

  }

  public MockHttpServletRequest() {

    this("GET");

  }

  public MockHttpServletRequest (méthode String) {

    this(method, "/Library", "/Library/foo/bar.jsp");

  }

  public String getContextPath() {

    return contextPath;

  }

  public String getMethod() {

    méthode de retour ;

  }

  public String getRequestURI() {

    demande de retourURI ;

  }

  public String getServletPath() {

    return requestURI.substring(getContextPath().length());

  }

  public HttpSession getSession() {

    séance de retour;

  }

  public HttpSession getSession(boolean arg0) {

    session de retour ;

  }

  objet public getAttribute (String arg0) {

    return attributs.get(arg0);

  }

  public String getParameter(String arg0) {

    return (String) settings.get(arg0);

  }

  public Map getParameterMap() {

    paramètres de retour ;

  }

  énumération publique getParameterNames() {

    return null;

  }

  public void setSession (session HttpSession) {

    this.session = session;

  }

  public void setParameter(String s, String s1) {

    parameters.put(s, s1);

  }

  public void setAttribute(String name, Object valeur) {

    attributes.put(name, value);

  }

  // Beaucoup de méthodes dégénérées élidées.

}

MockHttpServletResponse

package com.objectmentor.library.web.framework.mocks;

importer javax.servlet.ServletOutputStream;

importer javax.servlet.http. *;

importer java.io.*;

importer java.util.Locale;

classe publique MockHttpServletResponse implémente HttpServletResponse {

  // toutes les fonctions sont implémentées pour être dégénérées.

}

有了这些mock对象,现在我就可以创建一个LoanRecords_jsp的servlet实例并且开始调用它!我的头一个测试用例就像下面这样:

<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/>

就像预期的一样,测试失败了。这是因为还有些内容还没补充上,不过所剩无多。如果你仔细的看过Jsp文件,你就会发现它调用了request.getAttribute(“loanRecords”)并且期望返回一个List。但因为目前的测试并未为这样的属性赋值,从而导致了代码抛出了异常。

要想成功让servlet输出HTML,我们还需要加载这个属性。然后,我们就可以使用HtmlUnit来解析此HTML并且编写相应的单元测试。

HtmlUnit非常的容易使用,尤其是在测试所产生的像是本例这样的web pages上。我这里还有篇文章详细的介绍了它。

下面就是最终测试加载属性的测试,它通过htmlunit来检测HTML,并且做出正确的判断:

<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>

结论

这篇发表在此的技术能够用来测试几乎所有目前我们所见过的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年的编辑。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
ID Titre Date d'échéance< /th>/n");

      out.write("   

Fine
");

      out.print(loanRecord.id);

      out.write("/n");

      out.write("   

");

      out.print(loanRecord.title);

      out.write("/n");

      out.write("

");

      out.print(DateUtil.dateToString(loanRecord.dueDate));

      out .write("/n");

      out.write("   

");

      out.print(loanRecord.fine.toString());

      out.write("/n");

      out.write("