Как разработчик Java EE, я склонен смешивать модульные тесты с интеграционными тестами, обычно используя Arquillian . Но иногда, по непонятным причинам, Arquillian не может выполнить эту работу (скажем, упаковка приложения для тестирования слишком непонятна). Поэтому я хочу развернуть свое приложение, а затем выполнить несколько тестов. Но я действительно хочу, чтобы мои тесты выполнялись тогда и только тогда, когда веб-приложение развернуто, если нет, то тесты пропускаются. Другими словами, я хочу, чтобы мой Jenkins автоматически потерпел неудачу, если нет JBoss . Для этого я могу использовать API управления JBoss HTTP и предположения JUnit .
Случай использования
Допустим, у меня есть приложение 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 наши тесты могут быть осведомлены и выполняться только в том случае, если контейнер запущен и работает, если развернуто веб-приложение или источник данных действительно включен. Загрузите код , попробуйте и дайте мне знать, что вы думаете.
И спасибо Биллу Бёрку , Аруну Гупте , Алексису Хасслеру и Христосу Василакису за помощь и советы.