gradle 指南之构建java web应用程序

构建Java Web应用程序

Gradle包含一个war用于构建Java Web应用程序的插件,社区提供了一个优秀的插件,gretty用于在Jetty或Tomcat上测试和部署Web应用程序。本指南演示了如何构建简单的Web应用程序并使用gretty插件将其部署到Jetty上。您还将学习如何使用Mockito框架为servlet编写单元测试,以及如何使用grettySelenium 编写针对Web应用程序的功能测试。

你需要什么

  • 大约21分钟
  • 文本编辑器或IDE
  • Java发行版,版本7或更高版本
  • 一个Gradle,版本4.7或更高

创建一个Web应用程序

Gradle包含一个war插件,该插件记录在Web应用程序快速入门和用户手册的WAR插件一章中。该war插件扩展了Java插件以添加对Web应用程序的支持。默认情况下,它使用一个名为src/main/webapp与网络相关的资源的文件夹。

用户手册中的“Web应用程序快速入门”部分仍然指向jetty插件,该插件不赞成gretty使用此处使用的插件。war然而,特定于该插件的部分很好,并且该部分将很快更新。

因此,为名为webdemo的项目创建以下文件结构:

webdemo/
    src/
        main/
            java/
            webapp/
        test
            java/

任何servlet或其他Java类将进入src/main/java,测试将进入src/test/java,其他Web工件将进入src/main/webapp。

添加一个Gradle构建文件

添加一个调用build.gradle到项目根目录的文件,其中包含以下内容:
build.gradle

plugins {
    id 'war'  1️⃣
}

repositories {
    jcenter()
}

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'  2️⃣
    testCompile 'junit:junit:4.12'
}
  1. 使用war插件
  2. 当前发布的servlet API版本

该war插件添加的配置providedCompile和providedRuntime,类似于compile和runtime常规的Java应用程序,以代表在本地需要的,但不应该被添加到生成的依赖webdemo.war文件。
该plugins语法用于应用java和war插件。任何版本都不需要,因为它们包含在Gradle发行版中。
通过执行wrapper任务为项目生成Gradle包装是一个很好的做法:

$ gradle wrapper --gradle-version=4.7
:wrapper

这将产生gradlew与gradlew.bat脚本和gradle内如所描述的与包装罐夹包装器的用户手册的。

如果您使用的是Gradle 4.0或更高版本,则可能会看到您在本指南中可能看到的控制台输出较少的情况。在本指南中,使用–console=plain命令行上的标志显示输出。这是为了显示Gradle正在执行的任务。

像项目中添加一个servlet和元数据

定义Web应用程序元数据有两种选择。在servlet规范3.0版之前,元数据驻留在项目文件夹中调用的部署描述符web.xml中WEB-INF。从3.0开始,可以使用注释来定义元数据。
org/gradle/demo在src/main/java文件夹下创建一个包文件夹。添加一个servlet文件HelloServlet.java,其中包含以下内容:

src/main/java/org/gradle/demo/HelloServlet.java

package org.gradle.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "HelloServlet", urlPatterns = {"hello"}, loadOnStartup = 1) 1
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        response.getWriter().print("Hello, World!");  2
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        String name = request.getParameter("name");
        if (name == null) name = "World";
        request.setAttribute("user", name);
        request.getRequestDispatcher("response.jsp").forward(request, response);  3
    }
}
  1. 基于注解的servlet
  2. GET请求返回一个简单的字符串
  3. POST请求转发到JSP页面

该servlet使用@WebServlet注释进行配置。该doGet方法通过编写“Hello,World!”来响应HTTP GET请求 字符串到输出写入器。它通过查找调用的请求参数name并将其request作为一个称为属性的值添加到HTTP POST请求中user,然后转发到response.jsp页面。

该war插件支持使用较早的web.xml部署描述符,默认情况下该描述符应位于下面的WEB-INF文件夹中src/main/webapp。随意使用它作为基于注释的方法的替代方案。

你现在有一个简单的servlet来响应HTTP GET和POST请求。

将jsp页面添加到演示应用程序

通过在文件夹中创建文件index.html,将index页面添加到应用程序的根src/main/webapp目录中,其中包含以下内容:

src/main/webapp/index.html

<html>
<head>
  <title>Web Demo</title>
</head>
<body>
<p>Say <a href="hello">Hello</a></p>  1⃣️

<form method="post" action="hello">   2⃣️
  <h2>Name:</h2>
  <input type="text" id="say-hello-text-input" name="name" />
  <input type="submit" id="say-hello-button" value="Say Hello" />
</form>
</body>
</html>
  1. 链接提交GET请求
  2. 表单使用POST请求

该index.html页面使用链接向servlet提交HTTP GET请求,并使用表单提交HTTP POST请求。该表单包含一个名为的文本字段name,该字段可由servlet在其doPost方法中访问。
在它的doPost方法中,servlet将控制转发给另一个称为的JSP页面response.jsp。因此,src/main/webapp用以下内容定义该名称的文件:

src/main/webapp/response.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Hello Page</title>
    </head>
    <body>
        <h2>Hello, ${user}!</h2>
    </body>
</html>

该response页面访问user请求中的变量并将其呈现在h2标记中。

添加gretty插件并运行应用程序

该gretty插件是一个出色的社区支持的插件,可以在Gradle插件库中找到
https://plugins.gradle.org/plugin/org.akhikhl.gretty。该插件可以轻松地在Jetty或Tomcat上运行或测试webapps。
通过将下面的一行到将它添加到我们的项目plugins中块build.gradle。

Updating build.gradle to add gretty

plugins {
    id 'war'
    id 'org.akhikhl.gretty' version '1.4.2' 1️⃣
}
  1. 添加gretty插件

该gretty插件将大量任务添加到应用程序中,对于在Jetty或Tomcat环境中运行或测试非常有用。现在,您可以使用该appRun任务构建应用程序并将其部署到默认(Jetty)容器。

Executing the appRun task

$ ./gradlew appRun
:prepareInplaceWebAppFolder
:createInplaceWebAppFolder UP-TO-DATE
:compileJava
:processResources UP-TO-DATE
:classes
:prepareInplaceWebAppClasses
:prepareInplaceWebApp
:appRun
12:25:13 INFO  Jetty 9.2.15.v20160210 started and listening on port 8080
12:25:13 INFO  webdemo runs at:
12:25:13 INFO    http://localhost:8080/webdemo
Press any key to stop the server.
> Building 87% > :appRun

BUILD SUCCESSFUL

您现在可以通过http:// localhost:8080 / webdemo访问Web应用程序,并单击该链接执行GET请求或提交表单以执行POST请求。
虽然输出说Press any key to stop the server,标准输入不被Gradle拦截。要停止该过程,请按ctrl-C。

单元测试使用Mockito的servlet

开源的Mockito框架可以轻松地单元测试Java应用程序。将Mockito依赖项添加到配置build.gradle下的文件中testCompile。

Adding the Mockito library to build.gradle

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:2.7.19'  1️⃣
}
  1. 添加Mockito

要单元测试servlet,请org.gradle.demo在下面创建一个包文件夹src/test/java。添加HelloServletTest.java包含以下内容的测试类文件:

src/test/java/org/gradle/demo/HelloServletTest.java

package org.gradle.demo;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

public class HelloServletTest {
    @Mock private HttpServletRequest request;
    @Mock private HttpServletResponse response;
    @Mock private RequestDispatcher requestDispatcher;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void doGet() throws Exception {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);

        when(response.getWriter()).thenReturn(printWriter);

        new HelloServlet().doGet(request, response);

        assertEquals("Hello, World!", stringWriter.toString());
    }

    @Test
    public void doPostWithoutName() throws Exception {
        when(request.getRequestDispatcher("response.jsp"))
            .thenReturn(requestDispatcher);

        new HelloServlet().doPost(request, response);

        verify(request).setAttribute("user", "World");
        verify(requestDispatcher).forward(request,response);
    }

    @Test
    public void doPostWithName() throws Exception {
        when(request.getParameter("name")).thenReturn("Dolly");
        when(request.getRequestDispatcher("response.jsp"))
            .thenReturn(requestDispatcher);

        new HelloServlet().doPost(request, response);

        verify(request).setAttribute("user", "Dolly");
        verify(requestDispatcher).forward(request,response);
    }
}

测试创建模拟对象HttpServletRequest,HttpServletResponse以及RequestDispatcher类。对于doGet测试,创建一个PrintWriter使用a的StringWriter对象,并将模拟请求对象配置为在getWriter调用方法时将其返回。在调用doGet方法之后,测试检查返回的字符串是否正确。
对于发布请求,模拟请求被配置为返回给定名称(如果存在),否则getRequestDispatcher返回null,并且该方法返回关联的模拟对象。调用该doPost方法将执行请求。然后,Mockito验证是否setAttribute使用正确的参数在模拟响应上调用了该forward方法,并且在请求分派器上调用了该方法。

您现在可以使用Gradle对该test任务(或任何类似build依赖它的任务)进行测试。

$ ./gradlew build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:war
:assemble
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

测试输出可以以build/reports/tests/test/index.html通常的方式访问。你应该得到类似于以下的结果:

添加功能测试

该gretty插件与Gradle结合使用,可以轻松地为Web应用程序添加功能测试。为此,请将以下几行添加到您的build.gradle文件中:

Gretty additions to build.gradle for functional testing

gretty {
    integrationTestTask = 'test'  1⃣️
}

// ... rest from before ...

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:2.7.19'
    testCompile 'io.github.bonigarcia:webdrivermanager:1.6.1' 2⃣️
    testCompile 'org.seleniumhq.selenium:selenium-java:3.3.1' 3⃣️
}
  1. 告诉gretty在测试中启动和停止服务器
  2. 自动安装浏览器驱动程序
  3. 使用Selenium进行功能测试

该gretty插件需要知道哪个任务需要服务器的启动和停止。经常将它分配给您自己的任务,但为了保持简单,只需使用现有test任务即可。
Selenium是用于编写功能测试的流行开源API。2.0版基于WebDriver API。最近的版本要求测试人员为他们的浏览器下载和安装一个WebDriver版本,这可能很乏味而且很难自动化。该WebDriverManager项目可以很容易地让摇篮处理这个过程你。
将以下功能测试添加到您的项目的src/test/java目录中:

src/test/java/org/gradle/demo/HelloServletFunctionalTest.java

package org.gradle.demo;

import io.github.bonigarcia.wdm.ChromeDriverManager;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import static org.junit.Assert.assertEquals;

public class HelloServletFunctionalTest {
    private WebDriver driver;

    @BeforeClass
    public static void setupClass() {
        ChromeDriverManager.getInstance().setup(); 1⃣️
    }

    @Before
    public void setUp() {
        driver = new ChromeDriver();           2⃣️   
    }

    @After
    public void tearDown() {
        if (driver != null)
            driver.quit();                3⃣️         
    }

    @Test
    public void sayHello() throws Exception {      4⃣️
        driver.get("http://localhost:8080/webdemo");

        driver.findElement(By.id("say-hello-text-input")).sendKeys("Dolly");
        driver.findElement(By.id("say-hello-button")).click();

        assertEquals("Hello Page", driver.getTitle());
        assertEquals("Hello, Dolly!", driver.findElement(By.tagName("h2")).getText());
    }
}
  1. 如有必要,下载并安装浏览器驱动程序
  2. 启动浏览器自动化
  3. 完成后关闭浏览器
  4. 使用Selenium API运行功能测试

此测试的WebDriverManager部分检查最新版本的二进制文件,并在它不存在时下载并安装它。然后,sayHello测试方法将Chrome浏览器驱动到我们应用程序的根目录,填写输入文本字段,单击按钮并验证目标页面的标题,并且h2标记包含预期的字符串。
WebDriverManager系统支持Chrome,Opera,Internet Explorer,Microsoft Edge,PhantomJS和Firefox。检查项目文档以获取更多详细信息。

运行功能测试

使用test任务运行测试:

$ ./gradlew test
:prepareInplaceWebAppFolder UP-TO-DATE
:createInplaceWebAppFolder UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:prepareInplaceWebAppClasses UP-TO-DATE
:prepareInplaceWebApp UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:appBeforeIntegrationTest
12:57:56 INFO  Jetty 9.2.15.v20160210 started and listening on port 8080
12:57:56 INFO  webdemo runs at:
12:57:56 INFO    http://localhost:8080/webdemo
:test
:appAfterIntegrationTest
Server stopped.

BUILD SUCCESSFUL

该gretty插件在默认端口上启动嵌入式版本的Jetty 9,执行测试并关闭服务器。如果你看,你会看到Selenium系统打开一个新的浏览器,访问网站,填写表格,点击按钮,检查新页面,最后关闭浏览器。
集成测试通常通过创建单独的源集和专用任务来处理,但这超出了本指南的范围。有关详细信息,请参阅Gretty文档

总结概要

在本指南中,您学习了如何:

  • 使用warGradle构建中的插件来定义一个Web应用程序
  • 将一个servlet和JSP页面添加到一个Web应用程序
  • 使用gretty插件来部署应用程序
  • 单元使用Mockito框架测试一个servlet
  • 使用gretty和Selenium 功能测试Web应用程序