Ответ 1
Я хотел поделиться тем, что узнал после публикации этого вопроса, и выложить свой первый ответ на StackExchange (сайт, на котором я пришел бесчисленное количество раз через Google в поисках решений для моих бесконечных проблем)
Единица против интеграции с континуумом функционального тестирования
Там много поправок и споров и троллинга на эту тему, поэтому я хотел бы прояснить это. Все это очень просто. Скажите, что у вас есть услуга. Когда вы вызываете это, есть цепочка событий, которые я упрощенно проиллюстрирую как:
(полученный запрос) - (функция 1 называется) - (вызов функции 2) - (вызываемая функция 3) - (отклик отправлен)
Единичное тестирование тестирует каждую функцию (или класс или блок) отдельно по отдельности, подавая вход и проверяя вывод. Тестирование интеграции занимает несколько единиц (например, цепочки функций 2-функции 3), а также делает ol-in-and-out. Функциональное тестирование проходит по всей цепочке, от запроса до ответа. Я оставлю его читателю, чтобы угадать некоторые преимущества и недостатки тестирования на каждом уровне масштаба. Во всяком случае, ВСЕ ЭТИ ИСПЫТАНИЯ МОГУТ БЫТЬ РАБОТАНЫ В СЕРВЕРЕ, И ЕСТЬ ХОРОШИЕ ПРИЧИНЫ, ЧТОБЫ ИХ ИМУТЬ.
Типы тестирования внутри контейнера/сервера
- Container-in-the-tests. Функция Spring и другие среды инъекций зависимостей позволяют настроить контейнер, заполненный только минимальными классами (плюс все макеты) для каждого ваших тестов. Это очень удобно, поскольку устраняет необходимость ручной проводки и лучше приближает производственную среду. Это позволяет тестировать только единицы измерения и интеграции.
- Преимущества: а) традиционное модульное тестирование (с его преимуществами целенаправленных и изолированных тестов) стало более удобным b) ближе к производственной среде, так как вы проверяете логику автоустройства e) интегрируется с тест-листом IDE f) быстро
- Недостатки: а) окружающая среда может отличаться от производства b) не заменяет необходимость функционального тестирования.
- Сервер-в-тесте Обычный тестовый бегун запускает почти обычные модульные тесты, которые запускают встроенный сервер или контейнер и вызывают на него вызовы. Несколько фреймворков (например, Framework Testing Framework) допускают только функциональное тестирование, но большинство (Arquillian, jeeunit) позволяют делать все типы. С некоторыми из этих фреймворков это похоже на то, что тесты выполняются на сервере вместе с вашим кодом и могут выполнять любые вызовы.
- Преимущества (помимо того, что у вас есть доступ ко всем службам контейнера и сервера): а) у вас есть автономные тесты и не нужно устанавливать или настраивать что-либо b) тесты изолированы, потому что для каждого тестового или тестового набора создается новый сервер/контейнер. б) интегрируется с тестируемым IDE
- Недостатки: а) окружающая среда может отличаться от производства (например, Jetty не является Tomcat или Glassfish) b) запуск/остановка сервера замедляет тесты; c) основы сосать. Jeeunit - это крошечный проект, который даже не был протестирован в Windows. Arquillian большой, но очень новый, плохо документированный, и я не мог заставить его работать.
- Тесты в сервере. Здесь тесты фактически скомпилированы и запускаются вместе с вашим кодом.
- Преимущества: а) у вас простые, старые тесты, которые не нужно знать или использовать какие-либо рамки.
- Недостатки: a) отсутствие изоляции между испытаниями (не обязательно проблема или даже недостаток, но, возможно, необходимо принять меры предосторожности) b) не интегрируется с IDE-тестировщиком (по крайней мере, в Netbeans)
- Использование Maven во время сборки Maven запускает сервер, загружает ваш специальный тест WAR, выполняет тесты и дает хороший отчет Surefire.
- Дополнительные преимущества: а) это было сделано во время сборки (и будет интегрироваться с инструментами непрерывной интеграции и другими) b) нет необходимости устанавливать или настраивать что-либо (Maven будет загружать, запускать и т.д. сервер автоматически)
- Дополнительные недостатки: a) окружающая среда может быть совсем другой (Maven использует Jetty, и он работает на вашей машине) b) не может повторно запускаться в процессе производства
- тестирование in-WAR. Тесты постоянно компилируются вместе с вашим кодом. Когда бы и где бы ни находилась ваша ВОЙНА, вы можете запустить тесты. На вашем сервере разработки, во время постановки, даже в производстве. Это мой первоначальный вопрос.
- Дополнительные преимущества: a) ТОЧНО правильная среда. б) запускать тесты всякий раз, когда
- Дополнительные недостатки: a) необходимо настроить сервер
Там еще один момент. Netbeans дает большую часть преимуществ тестирования Maven для тестирования в WAR. Он включает встроенный сервер и запускается и развертывается автоматически после сборки. Он даже открывает Firefox... просто установите его, чтобы указать на ваш/тестовый ресурс. Это так же, как делать это с Maven, но лучше.
В любом случае, я покажу вам, как проводить тестирование Maven и тестирование в WAR во время одного и того же проекта Maven.
Контейнер в тестах с использованием Spring:
Spring представляет собой растягивающуюся структуру контейнера. Его механизмы впрыска зависимостей переплетаются с Jax-RS славным эффектом за счет значительной кривой обучения. Я не буду объяснять, как работает Spring или Jax-RS. Я перейду в инструкции и, надеюсь, читатели смогут адаптировать идеи к другим сценариям.
Способ получения контейнера, идущего в ваших тестах JUnit 4, - использовать тестовый бегун Spring, объявить классы, которые вы хотите зарегистрировать в контейнере, зарегистрировать некоторые вспомогательные классы Jax-RS, зарегистрировать свои mocks и, наконец, использовать ваш ресурс 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 позволяет только установить путь статически во время компиляции. Используя этот класс при запуске the-tests-in-the-server (см. Ниже), я ввожу настоящий 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 ожидает по умолчанию. IT означает интеграционный тест, но тестовый код может находиться где угодно в тестовом континууме. Например, вы можете создать экземпляр класса и unit test, или вы можете запустить HttpClient (или Джерси-клиент), указать его на себя (обратите внимание на порт ниже) и функционально проверить свои точки входа.
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-failafe-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" или удалите сборку в среде IDE, и код будет создан, ваши обычные тесты Test.java будут запущены, сервер причалов будет запущен, будут выполняться тесты * IT.java, и вы будете получите хороший отчет.
Упаковка ваших тестов в WAR для работы в любом месте:
(используйте вместе или отдельно от вышеуказанных инструкций)
Шаг 1) Получите ваши тестовые классы (каталог src/test/), встроенные в WAR, указав плагин maven-war-plugin: (адаптирован из здесь)
<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" после сборки.