Статьи

Ваши тесты предполагают, что JBoss работает и работает

Как разработчик Java EE, я склонен смешивать модульные тесты с интеграционными тестами, обычно используя  Arquillian . Но иногда, по непонятным причинам,  Arquillian  не может выполнить эту работу (скажем, упаковка приложения для тестирования слишком непонятна). Поэтому я хочу развернуть свое приложение, а затем выполнить несколько тестов. Но я действительно хочу, чтобы мои тесты выполнялись тогда и только тогда, когда веб-приложение развернуто, если нет, то тесты пропускаются. Другими словами, я хочу, чтобы мой  Jenkins  автоматически потерпел неудачу, если нет  JBoss . Для этого  я могу использовать  API управления JBoss HTTP  и  предположения JUnit .

Случай использования

Тестирование веб-приложения на JBossДопустим, у меня есть приложение Java EE с простой HTML-страницей (index.html), сервлетом (MyServlet.java) и конечной точкой REST (MyRESTEndpoint.java). После того как я упакован в WAR (sampleJavaEEJBossUtil.war) и развернут на JBoss, я хочу проверить все различные URL-адреса и HTTP-код ответа (т. Е. Сервлет развернут и работает, является ли конечная точка REST запущенной и запущенной…).

Так что это код этого очень простого сервлета

@WebServlet(urlPatterns = "/myservlet")
public class MyServlet extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            response.getWriter().println("Hello from the REST endpoint");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

И это код, который использует клиентский API JAX-RS 2.0 для тестирования сервлета, просто пингуя URL (я мог бы сделать что-то более умное, но суть этого поста не в этом.

public class URLTest {
 
    @Test
    public void checksTheServletIsDeployed() throws Exception {
 
        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("http://localhost:8080/sampleJavaEEJBossUtil").path("myservlet");
        assertEquals(Response.Status.OK.getStatusCode(), target.request(MediaType.TEXT_PLAIN).get().getStatus());
    }
}

Я выполняю тест, пока приложение развернуто, и JBoss запущен, и тест зеленый. Как вы можете себе представить, если я выключу JBoss или откажусь от приложения, тест провалится. В этом случае, что я действительно хочу, это игнорировать тест.

JBoss Admin CLI

Допустим, веб-приложение развернуто, JBoss запущен, все в порядке … давайте проверим все это с помощью CLI JBoss . Вы найдете этот интерфейс командной строки в $ JBOSS_HOME / bin / jboss-cli.sh. Когда вы выполняете его, вы подключаетесь к JBoss и вводите несколько команд, чтобы получить статус сервера и других компонентов (например, источник данных, фабрики JMS …). Следующие команды проверяют, работает ли JBoss, а также развернут ли наш пример JavaEEJBossUtil.war:

$ ./jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9990 /] :read-attribute(name=server-state)
{
    "outcome" => "success",
    "result" => "running"
}
[standalone@localhost:9990 /] /deployment=sampleJavaEEJBossUtil.war:read-attribute(name=status)
{
    "outcome" => "success",
    "result" => "OK"
}

JBoss Admin REST API

Помимо интерфейса командной строки, JBoss также имеет интерфейс REST для администрирования. Поэтому все команды, которые вы вводите в CLI, имеют HTTP-эквивалент. Чтобы узнать, запущен ли JBoss и запущен ли sampleJavaEEJBossUtil.war, мы можем просто зайти в браузер и ввести следующие URL:

http://localhost:9990/management?operation=attribute&name=server-state
 
 
http://localhost:9990/management/deployment/sampleJavaEEJBossUtil.war?operation=attribute&name=status

Обратите внимание, что API администрирования JBoss находятся по  адресу http: // localhost: 9990 / management . Тогда это просто вопрос прохождения определенных путей (например, развертывание) или параметров запроса (например, операция = атрибут)). Если вы введете эти URL-адреса в браузере, вы также заметите, что вам нужно войти в систему как администратор (сначала вам нужно создать пользователя / пароль с помощью утилиты $ JBOSS_HOME / bin / add-user.sh).

Для API администрирования JBoss требуется аутентификация DIGEST

Так что, если вы не хотите использовать браузер, а вместо этого использовать клиентский API JAX-RS 2.0 для подключения к нему? Давайте сначала используем curl,  чтобы увидеть, что он нам говорит.

~$ curl http://localhost:9990/management -v
* About to connect() to localhost port 9990 (#0)
* Connected to localhost (127.0.0.1) port 9990 (#0)
> GET /management HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:9990
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive
< WWW-Authenticate: Digest realm="ManagementRealm",domain="/management",algorithm=MD5
< Content-Length: 77

Как видно из вывода в строке № 11, API администрирования JBoss требует аутентификации DIGEST. Итак, предположив, что пользователь и пароль — admin / admin, вам необходимо ввести следующее:

~$ curl --digest 'http://admin:admin@localhost:9990/management' -v
* About to connect() to localhost port 9990 (#0)
* Connected to localhost (127.0.0.1) port 9990 (#0)
* Server auth using Digest with user 'admin'
> GET /management HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:9990
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive</pre>
< WWW-Authenticate: Digest realm="ManagementRealm",domain="/management",algorithm=MD5
< Content-Length: 77
<
* Ignoring the response-body
* Issue another request to this URL: 'http://admin:admin@localhost:9990/management'
* Connected to localhost (127.0.0.1) port 9990 (#0)
* Server auth using Digest with user 'admin'
> GET /management HTTP/1.1
> Authorization: Digest username="admin", realm="ManagementRealm", uri="/management", algorithm="MD5"
> User-Agent: curl/7.30.0
> Host: localhost:9990
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Authentication-Info: nextnonce="mJnypxK1UwINMTQwNTgwNDA2MTMzMHie0+SV9V15urC9I8n075w="
< Content-Type: application/json; charset=utf-8
< Content-Length: 2189
<
* Connection #0 to host localhost left intact

Как вы можете видеть, при использовании аутентификации DIGEST выходные данные являются гораздо более многословными, и в результате получается код возврата 200 (вместо 401). Итак, теперь у нас есть два важных компонента для доступа к API с помощью REST: мы знаем, как выглядят URL-адреса, и мы знаем, что нам нужно использовать аутентификацию DIGEST. Итак, давайте посмотрим, как это сделать.

Клиентский API JAX-RS для доступа к API управления JBoss

JAX-RS 2.0 имеет очень хороший клиентский API. Если мы хотим проверить, работает ли JBoss, нам нужно получить доступ к URL-адресу  http: // localhost: 9990 / management? Operation = attribute & name = server-state  и убедиться, что он возвращает «выполняется». С JAX-RS это так же просто, как написать:

public static boolean isJBossUpAndRunning() {
 
    Client client = ClientBuilder.newClient();
    WebTarget target = client.target("http://localhost:9990/management").queryParam("operation", "attribute").queryParam("name", "server-state");
    Response response = target.request(MediaType.APPLICATION_JSON).get();
    return response.getStatus() == Response.Status.OK.getStatusCode() && response.readEntity(String.class).contains("running");
}

Но, к сожалению, это не сработает, потому что вернет 401 (Несанкционированный). Нам нужно аутентифицироваться с использованием схемы DIGEST. Как мы это делаем в JAX-RS 2.0? Ну, мы не можем: o (Единственный трюк, который я обнаружил (благодаря  Биллу Бёрку  и Аруну Гупте ), это использование  HTTP-клиента Apache  и расширение  RESTEasy . Так что в приведенном ниже коде getClientmethod возвращает аутентифицированный HttpClient, который затем используется для доступа к API REST администрирования JBoss.

public class JBossUtil {
 
  private static ResteasyClient getClient() {
    // Setting digest credentials
    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "admin");
    credentialsProvider.setCredentials(AuthScope.ANY, credentials);
    HttpClient httpclient = HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider).build();
    ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpclient, true);
 
    // Creating HTTP client
    return new ResteasyClientBuilder().httpEngine(engine).build();
  }
 
  public static boolean isJBossUpAndRunning() {
 
    WebTarget target = getClient().target("http://localhost:9990/management").queryParam("operation", "attribute").queryParam("name", "server-state");
    Response response = target.request(MediaType.APPLICATION_JSON).get();
    return response.getStatus() == Response.Status.OK.getStatusCode() && response.readEntity(String.class).contains("running");
  }
}

Здесь я упростил код (без обработки исключений) и показываю только метод isJBossUpAndRunning. Но вы можете проверить полный  класс JBossUtil на GitHub , я создал другие методы.

Тестовый класс

Возвращаясь к нашему варианту использования, теперь у нас есть все части головоломки. Мы знаем, как проверить, что JBoss запущен и работает (мы могли бы даже убедиться, что  веб-приложение действительно развернуто ), и мы можем использовать  предположения JUnit . Следующий тест будет выполняться только тогда и только тогда, когда JBoss запущен и работает. Если нет, то тест будет автоматически игнорироваться благодаря   API Assume .

public class URLTest {
 
    @Test
    public void checksTheServletIsDeployed() throws Exception {
        Assume.assumeTrue(JBossUtil.isJBossUpAndRunning());
 
        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("http://localhost:8080/sampleJavaEEJBossUtil").path("myservlet");
        assertEquals(Response.Status.OK.getStatusCode(), target.request(MediaType.TEXT_PLAIN).get().getStatus());
     }
}

Вывод

Модульное тестирование важно, но когда вы выполняете свой код в контейнере, интеграционные тесты также очень полезны. Arquillian  решает большинство проблем и упрощает интеграционное тестирование, но можно использовать и другую технику. Основная цель нашей системы сборки — пройти все тесты … или игнорировать некоторые. С учетом предположений JUnit и некоторых API REST наши тесты могут быть осведомлены и выполняться только в том случае, если контейнер запущен и работает, если развернуто веб-приложение или источник данных действительно включен. Загрузите код , попробуйте и дайте мне знать, что вы думаете.

И спасибо  Биллу БёркуАруну ГуптеАлексису Хасслеру  и  Христосу Василакису  за помощь и советы.

Рекомендации