Запуск юнит-тестов на сервере (JAX-RS) - PullRequest
22 голосов
/ 16 января 2012

Я пишу приложение JAX-RS (Jersey + Maven), которое делает некоторые хитрые вещи (например, вызывает собственные исполняемые файлы, встроенные в WAR).Мне нужно запустить [некоторые из] моих модульных тестов (JUnit4) на сервере (Amazon Elastic Beanstalk под управлением Tomcat 7.0.22), чтобы проверить, что все в порядке.

Существует ли стандартный, гибкий способ сделать этокроме RYO (сверните свое собственное)?То, что я обнаружил, похоже, больше связано с интеграционным тестированием на машине разработчика (т. Е. Jersey Test Framework).Даже RYO сбивает меня с толку ... как я могу вызвать код в тестовых пакетах из исходных пакетов?

По сути, я хочу создать ресурс / test, который я могу вызвать, который будет возвращать результаты моего модульного теста изСервер в симпатичном формате.Еще лучше, если бы я мог сделать / test / {category}

Ответы [ 5 ]

27 голосов
/ 28 января 2012

Я хотел бы поделиться тем, что я узнал после публикации этого вопроса, и опубликовал свой первый ответ на StackExchange (сайте, на который я приходил бесчисленное количество раз через Google в поисках решений моих бесконечных проблем)

Единица против интеграции против функционального континуума тестирования

Есть много исправлений и споров и троллинга на эту тему, поэтому я хотел бы прояснить это.Это все действительно очень просто.Скажем, у вас есть служба.Когда вы вызываете его, есть цепочка событий, которые я упрощенно проиллюстрирую как:

(запрос получен) - (вызвана функция 1) - (вызвана функция 2) - (вызвана функция 3) - (отправлен ответ)

В модульном тестировании каждая функция (или класс, или модуль) проверяется отдельно в отдельности, вводится вход и проверяется выход.Интеграционное тестирование занимает несколько единиц (например, функциональная цепочка 3-функция 3), а также выполняет вход и выход.Функциональное тестирование проходит по всей цепочке, от запроса до ответа.Я оставлю читателю угадать некоторые преимущества и недостатки тестирования на каждом уровне шкалы.В любом случае, ВСЕ ЭТИ ИСПЫТАНИЯ МОГУТ БЫТЬ ЗАПУСКАНЫ НА СЕРВЕРЕ, И ИМЕТЬ ХОРОШИЕ ПРИЧИНЫ, ЧТОБЫ ИСПОЛЬЗОВАТЬ ИХ.* Контейнер в тестах Функция Spring и других структур внедрения зависимостей позволяет вам настроить контейнер, который заполнен только минимальными классами (плюс все макеты) для каждого из ваших тестов.Это очень удобно, поскольку устраняет необходимость в ручном подключении и лучше приближается к производственной среде.Это позволяет только модульное и интеграционное тестирование.

  • Преимущества: а) традиционное модульное тестирование (с его преимуществами целенаправленных и изолированных тестов) стало более удобным б) ближе к производственной среде, так как вы тестируете логику автопроводки д) интегрируется с IDE Runner F)быстрая
  • Недостатки: а) среда может сильно отличаться от производственной; б) не заменяет необходимость функционального тестирования
Server-in-the-tests Обычный тестовый прогончик запускает почти обычные модульные тесты, которые запускают встроенный сервер или контейнер и вызывают его.Некоторые фреймворки (например, Jersey Testing Framework) допускают только функциональное тестирование, но большинство (Arquillian, jeeunit) позволяют выполнять все типы.При использовании некоторых из этих платформ тесты выполняются на сервере вместе с вашим кодом и могут выполнять любые вызовы.
  • Преимущества (помимо того, что у вас есть доступ ко всем службам контейнера и сервера): а) у вас есть автономные тесты, и вам не нужно ничего устанавливать или настраивать б) тесты изолированы, потому чтоСвежий сервер / контейнер создается для каждого теста или набора тестов.б) интегрируется с IDE Runner Test Runner
  • Недостатки: а) среда может довольно сильно отличаться от производственной (например, Jetty не Tomcat или Glassfish) б) запуск / остановка сервера замедляет тесты в)рамки сосут.Jeeunit - крошечный проект, который даже не тестировался на Windows, Arquillian - большой, но очень новый, плохо документированный, и я тоже не смог заставить его работать.
Tests-in-the-server Здесь тесты фактически скомпилированы и выполняются вместе с вашим кодом.
  • Преимущества: а) у вас есть простые, старые тесты, которые не нужно знать или использовать какие-либо фреймворки
  • Недостатки: а) отсутствие изоляции между тестами (не обязательно проблемаили даже недостаток, но, возможно, придется принять меры предосторожности) б) не интегрируется с IDE Runner (по крайней мере, в Netbeans)
  • Использование Maven во время сборки Maven запускает серверзагружает в ваш специальный тест WAR, выполняет тесты и дает хороший отчет Surefire.
    • Дополнительные преимущества: а) это было сделано во время сборки (и будет интегрировано с инструментами непрерывной интеграции и другими)b) не нужно ничего устанавливать или настраивать (Maven автоматически загружает, запускает и т. д. сервер)
    • Дополнительные недостатки: a) среда может быть довольно разной (Maven использует Jetty и работает на вашем компьютере)) b) не может быть повторно запущен в производстве
  • in-WAR-тестирование Тесты постоянно компилируются с вашим кодом.Когда бы и где бы ни была ваша WAR, вы можете запустить тесты.На вашем сервере разработки, во время постановки, даже в производстве.Это то, что был мой оригинальный вопрос.
    • Дополнительные преимущества: а) Точно правильная среда.б) запускать тесты всякий раз, когда
    • Дополнительные недостатки: а) необходимо настроить сервер

Есть еще одно замечание.Netbeans дает большинство преимуществ тестирования Maven для тестирования в WAR.Он включает в себя встроенный сервер, запускается и развертывается на нем автоматически после сборки.Он даже открывает Firefox ... просто установите его так, чтобы он указывал на ваш ресурс / test.Это все равно, что делать это способом Maven, но лучше.

В любом случае, я покажу вам, как проводить тестирование Maven и тестирование в WAR вместе в одном проекте Maven.

Container-в тестах с использованием Spring:

Spring - растягивающаяся структура контейнера.Его механизмы внедрения зависимостей переплетаются с Jax-RS, что дает великолепный эффект за счет значительной кривой обучения.Я не буду объяснять, как работает Spring или Jax-RS.Я перейду прямо к инструкциям, и надеюсь, что читатели смогут адаптировать идеи к другим сценариям.

Способ заставить контейнер работать в ваших тестах JUnit 4 - это использовать тестовый прогон Spring, объявив классы, которые вы 'я хотел бы зарегистрироваться в контейнере, зарегистрировать некоторые вспомогательные классы Jax-RS, зарегистрировать свои макеты и, наконец, использовать свой ресурс Jax-RS, как если бы он был обычным классом:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes={
    MyClass1.class,
    Myclass2.class,
    MyJaxRsResource.class,
    MockServletContextAwareProcessor.class,
    MyCTest.Config.class
})
public class MyCTest
{
    @Configuration
    static class Config 
    {
          // Set up and register mocks here, and watch them be autowired!
          @Bean public DBService dbJobService() throws DBException
            {
                return mock(DBService.class); 
            }
    }

    @Autowired MyJaxRsResource myResource;

    @Test public void test() {
         String response = myResource.get("hello");
    }
}

@WebAppConfigurationвнедряет свой собственный ServletContextAwareProcessor.Однако MockServletContextAwareProcessor необходимо, когда путь к распакованному файлу WAR необходимо задавать динамически, поскольку WebAppConfiguration позволяет устанавливать статический путь только во время компиляции.Используя этот класс при запуске тестов на сервере (см. Ниже), я внедряю настоящий ServletContext.Я использовал функцию профилей Spring, чтобы подавить ее через переменную окружения (что не очень элегантно).setServletContext вызывается просто исполнителем тестов сервера.

@Configuration
public class MockServletContextAwareProcessor {

public static void setServletContext(ServletContext sc) {
    servletContext = sc;
}    
private static ServletContext getServletContext() {
    return servletContext;
}
private static ServletContext servletContext;    

@Configuration
@Profile("server-test")
static class ServerTestContext {

    static public @Bean
    ServletContextAwareProcessor 
        scap() {
            ServletContext sc = getServletContext();
            return new ServletContextAwareProcessor(sc);
    }
}    
}

Сервер в тестах с использованием Maven:

Шаг 1) Создайте обычные тесты JUnit в папке / src / test,но назовите их IT * .java или * IT.java или * ITCase.java (например, MyClassIT.java). Вы можете назвать их по-разному, но это то, что Failsafe ожидает по умолчанию.ИТ означает интеграционное тестирование, но тестовый код может находиться в любом месте непрерывного тестирования.Например, вы можете создать экземпляр класса и выполнить его модульное тестирование, или вы можете запустить HttpClient (или клиент Jersey), указать его на себя (обратите внимание на порт ниже) и функционально протестировать ваши точки входа.

public class CrossdomainPolicyResourceSTest extends BaseTestClass {

static com.sun.jersey.api.client.Client client;

  @BeforeClass public static void 
startClient() {

        client = Client.create();
    }

  @Test public void 
getPolicy() {

        String response = 
            client
                .resource("http://localhost/crossdomain.xml")
                .get(String.class);

        assertTrue(response.startsWith("<?xml version=\"1.0\"?>"));
    }
}

BaseTestClass - это всего лишь небольшой вспомогательный класс, который печатает имя тестового класса и выполняет тест по мере его выполнения (полезно для тестов на сервере, см. Ниже):

public abstract class BaseTestClass {

@ClassRule public static TestClassName className = new TestClassName();
@Rule public TestName testName = new TestName();    

  @BeforeClass public static void 
printClassName() { 
        System.out.println("--" + className.getClassName() + "--"); 
    }    
  @Before public void 
printMethodName() {
        System.out.print(" " + testName.getMethodName()); 
    }    
  @After public void 
printNewLine() { 
        System.out.println(); 
    }
}

Шаг 2) Добавить maven-failsafe-plugin и maven-jetty-plugin для вашего pom.xml

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.11</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>maven-jetty-plugin</artifactId>
    <version>6.1.26</version>
    <configuration>
        <!-- By default the artifactId is taken, override it with something simple -->
        <contextPath>/</contextPath>
        <scanIntervalSeconds>2</scanIntervalSeconds>
        <stopKey>foo</stopKey>
        <stopPort>9999</stopPort>
        <connectors>
            <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                <port>9095</port>
                <maxIdleTime>60000</maxIdleTime>
            </connector>
        </connectors>
    </configuration>
    <executions>
        <execution>
            <id>start-jetty</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <scanIntervalSeconds>0</scanIntervalSeconds>
                <daemon>true</daemon>
            </configuration>
        </execution>
        <execution>
            <id>stop-jetty</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Шаг 3) Прибыль.На самом деле, это все!Просто запустите 'mvn install' или нажмите build в IDE, и код будет скомпилирован, ваши регулярные тесты * Test.java будут запущены, сервер Jetty запустится, тесты * IT.java будут запущены, и выполучите хороший отчет.

Упаковка ваших тестов в вашу WAR для выполнения в любом месте:

(используйте вместе или отдельно от приведенных выше инструкций)

Шаг 1) Получите ваши тестовые классы (каталог src / test /), встроенный в WAR, с помощью команды подключаемого модуля maven-war: (адаптировано с здесь )

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1.1</version>
    <configuration>
        <failOnMissingWebXml>false</failOnMissingWebXml>
        <webResources>
            <resource>
                <directory>${project.build.directory}/test-classes</directory>
                <targetPath>WEB-INF/classes</targetPath>
            </resource>
            <resource>
                <directory>${project.build.directory}/test-libs</directory>
                <targetPath>WEB-INF/lib</targetPath>
            </resource>
        </webResources>
    </configuration>
</plugin>

Примечание: вы можете создать отдельныйWAR со встроенными тестами путем создания дополнительного исполнения и в его наборе настроек и (подробности оставляю читателю)

Примечание. В идеале вышеперечисленное исключало бы все обычные тесты (и только копировал * IT.java). Однако я не мог заставить включаться / исключать работу.

Вам также придется включить тестбиблиотекам, предоставив плагину maven-dependency-plugin дополнительное выполнение с целью копирования-зависимости, которое включает в себя тестовую область

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.1</version>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <excludeScope>compile</excludeScope>
                <outputDirectory>${project.build.directory}/test-libs</outputDirectory>
                <overWriteReleases>true</overWriteReleases>
                <overWriteSnapshots>true</overWriteSnapshots>
                <overWriteIfNewer>true</overWriteIfNewer>
            </configuration>
        </execution>
    </executions>
</plugin>

Если плагин maven-dependency-plugin уже имеет другие исполнения (например, Netbeans вставляет его дляjavaee-endorsed-api), не удаляйте их.

Шаг 2) Программно запускайте тесты, используя JUnitCore (JUnit4).

String runTests() {
    PrintStream sysOut = System.out;
    PrintStream sysErr = System.err;
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(stream);
    try {
        System.setOut(out);
        System.setErr(out);
        TextListener listener = new TextListener(out);
        JUnitCore junit = new JUnitCore();
        junit.addListener(listener);

        junit.run(MyClassIT.class,
                  AnotherClassIT.class,
                  ...etc...);

    } finally {
        System.setOut(sysOut);
        System.setErr(sysErr);
        out.close();
    }

    return stream.toString();
}

Шаг 3) Предоставляйте свои тесты через JAX-RS

@Path("/test")
public class TestResource {

    @GET
    @Produces("text/plain")
    public String getTestResults() {

        return runTests();
    }

    private String runTests() {
        ...
    }

}

Поместите этот класс вместе с другими вашими тестовыми классами (в src / test), чтобы он мог ссылаться на них.

Однако, если вы создаете подкласс javax.ws.rs.Класс core.Application, где вы регистрируете все свои ресурсы, у вас будет проблема со ссылкой на TestResource (поскольку исходный код не может ссылаться на тестовый код).Чтобы обойти это, создайте полностью пустой фиктивный класс TestResource в src / main / ... [тот же пакет] ... Этот прием работает, потому что фиктивный TestResource будет перезаписан реальным во время упаковки.

public class ShoppingApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        return new HashSet<Class<?>>() {{
            add(TestResource.class);
        }};
    }

    @Override
    public Set<Object> getSingletons() {
        return new HashSet<Object>();
    }
}

package ...same package as the real TestResource...
public class TestResource {

}

Шаг 4) Настройте IDE для запуска / развертывания приложения и откройте в браузере пункт «/ test» автоматически после сборки.

3 голосов
/ 17 января 2012

Победившим ключевым словом оказывается «проверка в контейнере».Совершенно новый и выдающийся фреймворк: Arquillian .

Странно, но, похоже, больше ничего нет.Кто-то еще в StackOverflow спросил"Я не вижу ни одного из этих проектов, слишком широко используемых, так что же плохого в тестировании в контейнере?"Но не получил четкого ответа.

Я полагаю, что это всего лишь небольшая область между двумя большими сферами модульного тестирования и полного интеграционного тестирования, которое должно быть покрыто внутриконтейнерным тестированием.Мне тоже нужно всего несколько тестов, чтобы проверить, доступны ли ресурсы сервера и работают ли они.Вероятно, им следовало написать их от руки, чем потратить все это время на исследование (а затем изучение) внутриконтейнерного тестирования.

1 голос
/ 17 января 2012

Джакартский кактус , кажется, сделал то, что я ищу.Его домашняя страница гласит: «Cactus представляет собой простую инфраструктуру тестирования для модульного тестирования Java-кода на стороне сервера ... Он использует JUnit ... Cactus реализует стратегию в контейнере ...» URL, такой как http://localhost:8080/test/ServletTestRunner?suite=TestSampleServlet, будетобеспечить хороший вывод HTML.

Однако Apache Foundation поместил его на чердак из-за отсутствия активной разработки.Значит ли это, что я не должен думать об этом?На странице «Мансарда» написано, что «пользователям Cactus рекомендуется переключаться на другие методы тестирования», не объясняя, что это такое!

1 голос
/ 16 января 2012

Используя Maven, Surefire может предоставлять вам отформатированные отчеты о результатах тестирования.

http://maven.apache.org/plugins/maven-surefire-report-plugin/report-mojo.html

Существует множество способов сделать содержимое этих отчетов доступным, независимо от того, отправлены они вам или опубликованы на веб-странице. У вас есть много вариантов.

0 голосов
/ 16 января 2012

Не думаю, что есть стандартный способ, но вы могли бы исследовать использование Spring Remoting для вызова методов на интересующем вас сервере с вашего компьютера разработчика.Если вы используете интерфейсы и внедряете тестируемый сервис, вы сможете запустить один и тот же модульный тест дважды, один раз локально и один раз на сервере, просто изменив конфигурацию Spring.

...