Uruchamianie testów jednostkowych na serwerze (JAX-RS)

Piszę aplikację JAX-RS (Jersey+Maven), która robi pewne trudne rzeczy (np. wywołuje natywne pliki wykonywalne osadzone w wojnie). Muszę uruchomić [część] moich testów jednostkowych (JUnit4) na serwerze (Amazon Elastic Beanstalk z Tomcat 7.0.22), aby sprawdzić, czy wszystko jest w porządku.

Czy istnieje standardowy, elastyczny sposób na zrobienie tego inaczej niż RYO (roll your own)? Rzeczy, które znalazłem, wydają się mieć więcej wspólnego z testowaniem integracji na maszynie deweloperskiej (tj. Jersey Test Framework). Parzyste RYO mnie myli... Jak mogę wywołać kod w pakietach testowych z pakietów źródłowych?

Zasadniczo chcę utworzyć zasób /test, który mogę wywołać, który zwróci moje wyniki testów jednostkowych z serwera w ładnym formacie. Nawet lepiej, gdybym mógł zrobić / test/{Kategoria}

Author: Fernando Correia, 2012-01-16

5 answers

Chciałem podzielić się tym, czego się nauczyłem po opublikowaniu tego pytania i umieścić swoją pierwszą odpowiedź na StackExchange (stronie, na którą dotarłem niezliczoną ilość razy za pośrednictwem google w poszukiwaniu rozwiązań moich niekończących się problemów)

Jednostka vs integracja vs Continuum testów funkcjonalnych

W tym temacie jest dużo poprawiania, argumentowania i trollowania, więc chciałbym to wyjaśnić. To wszystko jest naprawdę bardzo proste. Powiedz, że masz jakąś obsługę. Kiedy to nazwiesz, jest łańcuch wydarzenia, które w uproszczeniu zilustruję jako:

(request received) - (function 1 called) - (function 2 called) - (function 3 called) - (response sent)

Testowanie jednostkowe testuje każdą funkcję (lub klasę lub jednostkę) indywidualnie w izolacji, zasilając wejście i sprawdzając wyjście. Testowanie integracyjne zajmuje kilka jednostek (takich jak łańcuch funkcji 2-Funkcja 3), a także wykonuje ol ' in-and-out. Testy funkcjonalne przechodzą przez cały łańcuch, od zapytania do odpowiedzi. Ja pozostawić czytelnikowi odgadnięcie pewnych zalet i wad testowania na każdym poziomie skali. W każdym razie, wszystkie te testy można uruchomić na serwerze i są dobre powody, aby je uruchomić.

Rodzaje testów w kontenerze / na serwerze

  • Container-in-the-tests funkcja Spring i innych struktur iniekcji zależności pozwala skonfigurować kontener wypełniony tylko minimalnymi klasami (plus wszystkimi mockami) dla każdego z Twoich testów. Jest to bardzo wygodne, ponieważ eliminuje potrzebę ręcznego okablowania i lepiej przybliża środowisko produkcyjne. Pozwala to tylko na testowanie jednostkowe i integracyjne.
    • zalety: a) tradycyjne testy jednostkowe (z zaletami testów skupionych i izolowanych) są wygodniejsze b) bliżej środowiska produkcyjnego, ponieważ testujesz logikę automatycznego wiązania e) integruje się z IDE Test runner f) quick
    • wady: a) środowisko może być raczej inne niż produkcja b) nie zastępuje potrzeby testowania funkcjonalnego
  • Server-in-the-tests zwykły test runner uruchamia prawie zwyczajne testy jednostkowe, które uruchamiają wbudowany serwer lub kontener i wykonują do niego połączenia. Kilka frameworków (jak Jersey Testing Framework) pozwala na testowanie tylko funkcjonalne, ale większość (Arquillian, jeeunit) pozwala na wykonywanie wszystkich typów. W przypadku niektórych z tych frameworków, to tak, jakby testy były uruchomione na serwerze tylko przy Twoim kodzie i mogą sprawić, że każdy rodzaj telefonów.
    • zalety (poza tym, że masz dostęp do wszystkich usług kontenera i serwera): a) masz samodzielne testy i nie musisz nic instalować ani konfigurować b) testy są izolowane, ponieważ dla każdego testu lub zestawu testów tworzony jest nowy serwer/kontener. b) integruje się z IDE Test runner
    • wady: a) środowisko może być raczej inne niż produkcja (np. Jetty to nie Tomcat czy Glassfish) b) uruchomienie / zatrzymanie serwera zwalnia testy c) ramy są do bani. Jeeunit to malutki projekt, który nie był nawet testowany na Windowsie, Arquillian jest duży, ale bardzo nowy, słabo udokumentowany i też nie mogłem go uruchomić.
  • testy na serwerze tutaj testy są kompilowane i uruchamiane razem z Twoim kodem.
    • zalety: a) masz zwykłe, stare testy, które nie muszą być świadome ani używać jakiegokolwiek frameworka
    • wady: a) brak izolacji między testami (nie koniecznie problem, a nawet wadę, ale może trzeba podjąć środki ostrożności) nie jest to jednak możliwe, ponieważ nie jest to możliwe.]}
    • używanie Mavena podczas budowania Maven uruchamia serwer, ładuje się w specjalnej wojnie testowej, wykonuje testy i daje ładny raport Surefire.
      • dodatkowe zalety: a) zrobił to podczas budowania (i zintegruje się z narzędziami do ciągłej integracji i innymi) b) nie trzeba niczego instalować ani konfigurować (Maven będzie pobieranie, uruchamianie itp. serwera automatycznie)
      • dodatkowe wady: a) środowisko może być raczej inne (Maven używa Jetty i działa na twoim komputerze) b) nie można ponownie uruchomić w produkcji
    • in-WAR testing testy są trwale kompilowane z Twoim kodem. Zawsze i wszędzie, gdzie toczy się twoja wojna, możesz odpalić testy. Na twoim serwerze deweloperskim, podczas stagingu, nawet w produkcji. Oto moje pierwotne pytanie.
      • dodatkowe zalety: a) dokładnie odpowiednie środowisko. b) uruchamiaj testy kiedykolwiek
      • dodatkowe wady: a) potrzeba skonfigurowania serwera
Jest jeszcze jedna uwaga. Netbeans daje większość korzyści z testów Mavena testom in-WAR. Zawiera wbudowany serwer i uruchamia się i wdraża do niego automatycznie po kompilacji. To nawet otworzyć Firefoksa... po prostu skonfiguruj go tak, aby wskazywał na Twój zasób /test. To tak jak robić to w sposób Mavena, ale lepiej.

W każdym razie, pokażę Ci, jak zrobić testy Mavena i testy wojenne razem w tym samym projekcie Mavena.

Kontener-w-testach przy użyciu sprężyny:

Spring to rozbudowana rama kontenera. Jego mechanizmy wtrysku zależności przeplatają się z Jax-RS do wspaniałego efektu, kosztem znacznej krzywej uczenia się. Nie będę wyjaśniał, jak działa Spring czy Jax-RS. Przeskoczę od razu do instrukcji i mam nadzieję, że czytelnicy będą mogli dostosować pomysły do innych scenariusze.

Sposobem na uruchomienie kontenera w testach JUnit 4 jest użycie Spring Test runner, zadeklarowanie klas, które chcesz zarejestrować w kontenerze, zarejestrowanie klas pomocniczych specyficznych dla Jax-RS, zarejestrowanie moków i wreszcie użycie zasobów Jax-RS, jak gdyby była to zwykła Klasa: {]}

@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 wstrzykuje własny procesor ServletContextAwareProcessor. Jednak MockServletContextAwareProcessor jest konieczne, gdy ścieżka do rozpakowanego pliku WAR musi być ustawiona dynamicznie, ponieważ WebAppConfiguration pozwala tylko ustawić ścieżkę statycznie w czasie kompilacji. Używając tej klasy podczas uruchamiania-tests-in-the-server (patrz niżej), wstrzykuję prawdziwy ServletContext. Użyłem funkcji profili Springa, aby go stłumić za pomocą zmiennej środowiskowej (co nie jest zbyt eleganckie). setServletContext jest wywoływany po prostu przez Server Test runner.

@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);
    }
}    
}

Server-in-the-tests using Maven:

Krok 1) Utwórz regularne testy JUnit w folderze / src / test, ale nadaj im nazwę*.java lub * IT.java lub * ITCase.java (np. MyClassIT.java) można je nazwać inaczej, ale tego domyślnie oczekuje Failsafe. Oznacza test integracyjny, ale kod testowy może znajdować się w dowolnym miejscu kontinuum testowego. Na przykład możesz utworzyć instancję klasy i przetestować ją jednostkowo, lub możesz odpalić HttpClient( lub klienta Jersey), skierować go na siebie (zwróć uwagę na port poniżej) i funkcjonalnie przetestować swoje punkty wejścia.

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 jest tylko małą klasą pomocniczą, która wypisuje nazwę klasy testowej i test jak wykonuje (przydatne dla tests-in-server, patrz niżej):

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(); 
    }
}

Krok 2) Dodaj maven-failsafe-plugin i Maven-jetty-plugin do 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>
Krok 3) Zysk. Naprawdę, to jest to! Po prostu uruchom 'mvn install' lub naciśnij build w IDE, a kod zbuduje, twój zwykły * Test.uruchomią się testy Javy, uruchomi się serwer jetty, * IT.testy Javy ruszą, a Ty dostaniesz ładny raport.

Anywhere:

(używać razem LUB oddzielnie z powyższej instrukcji)

Krok 1) Zdobądź klasy testowe (katalog src/test/) osadzone w wojnie, instruując wtyczkę Maven-war, aby je dołączyła: (zaadaptowana z tutaj )

<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>

Uwaga: możesz utworzyć oddzielną wojnę z testami zintegrowanymi, tworząc dodatkowe wykonanie i w jego zestawie konfiguracyjnym oraz (szczegóły pozostawiam czytelnikowi)

Uwaga: idealnie, powyższe wykluczyłoby wszystkie regularne testy (i tylko skopiować * to.java) jednak nie mogłem dostać includes/excludes do pracy.

Będziesz również musiał dołączyć biblioteki testowe, dając maven-dependency-plugin dodatkowe wykonanie z celem copy-dependency, który zawiera zakres testowy

<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>

Jeśli maven-dependency-plugin ma już Inne wykonania (np. Netbeans wstawia jedną dla javaee-API), Nie usuwaj ich.

Krok 2) programowo uruchamiaj testy za pomocą 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();
}

Krok 3) wystawiaj swoje testy przez JAX-RS

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

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

        return runTests();
    }

    private String runTests() {
        ...
    }

}

Umieść tę klasę wraz z innymi klasami testowymi (w src / teście), aby mogła się do nich odwoływać.

Jeśli jednak podklasujesz klasę javax. ws. RS. Core. Application, w której rejestrujesz wszystkie swoje zasoby, będziesz miał problem z odwołaniem się do TestResource (ponieważ kod źródłowy nie może odwoływać się do kodu testowego). Aby obejść ten problem, stwórz całkowicie pustą klasę testresource pod src / main/...[ten sam pakiet]... Ta sztuczka działa, ponieważ atrapa TestResource zostanie nadpisana przez prawdziwe podczas pakowania.

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 {

}

Krok 4) Skonfiguruj IDE, aby uruchomić / wdrożyć aplikację i otworzyć przeglądarkę wskaż "/ test " automatycznie po kompilacji.

 27
Author: Aleksandr Dubinsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-12 08:09:53

Zwycięskim słowem kluczowym okazuje się"testowanie w kontenerze". Zupełnie nowy i wybitny framework to Arquillian.

O dziwo, nie ma nic innego. Ktoś inny na StackOverflow zapytał " nie widzę żadnego z tych projektów zbyt szeroko używanych, więc czy jest coś złego w testowaniu w kontenerze?"Ale nie otrzymał jasnej odpowiedzi.

Myślę, że to tylko mały obszar pomiędzy dwoma dużymi sferami testów jednostkowych i testów pełnej integracji należy to uwzględnić w testach w kontenerach. Dla mnie też potrzebuję tylko kilku testów, aby sprawdzić, czy zasoby serwera są dostępne i funkcjonalne. Prawdopodobnie powinienem był napisać je ręcznie, niż spędzić cały ten czas na badaniu (a następnie uczeniu się) testowania w kontenerze.

 3
Author: Aleksandr Dubinsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:29

Używając Mavena, Surefire może dostarczać sformatowane raporty wyników testów.

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

Istnieje wiele sposobów udostępniania treści tych raportów, niezależnie od tego, czy są one wysyłane do ciebie, czy publikowane na stronie internetowej. Masz wiele opcji.

 1
Author: Mike Yockey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-16 13:24:23

Jakarta Cactus wydaje się, że zrobił to, czego szukam. Jego Strona główna stwierdza: "Cactus jest prostym frameworkiem testowym do testowania jednostkowego kodu java po stronie serwera... Wykorzystuje JUnit... Cactus wdraża strategię w kontenerze..."URL taki jak http://localhost:8080/test/ServletTestRunner?suite = TestSampleServlet zapewni ładny wynik HTML.

Jednak Fundacja Apache umieściła go na strychu z powodu braku aktywnego rozwoju. Czy to znaczy, że nie powinienem myśleć o używasz go? Strona na poddaszu mówi "użytkownicy kaktusów są zachęcani do przejścia na inne techniki testowania" bez wyjaśniania, co to jest!

 1
Author: Aleksandr Dubinsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-17 07:35:10

Myślę, że nie ma standardowego sposobu, ale możesz zbadać używając Spring Remoting, aby wywołać metody na serwerze, który Cię interesuje, z maszyny programisty. Jeśli używasz interfejsów i wprowadzasz testowaną usługę, powinieneś być w stanie uruchomić ten sam test jednostkowy dwa razy, raz lokalnie i raz na serwerze, po prostu zmieniając konfigurację Spring.

 0
Author: artbristol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-16 15:32:12