web-dev-qa-db-ja.com

サーバーでの単体テストの実行(JAX-RS)

私はいくつかのトリッキーなことをするJAX-RS(Jersey + Maven)アプリケーションを書いています(例えば、WARに埋め込まれたネイティブ実行可能ファイルを呼び出す)。サーバー(Tomcat7.0.22を実行しているAmazonElastic Beanstalk)でユニットテスト(JUnit4)を実行して、すべてが正常であることを確認する必要があります。

RYO(自分でロール)以外に、これを行うための標準的で柔軟な方法はありますか?私が見つけたものは、開発者のマシン(つまり、Jersey Test Framework)での統合テストともっと関係があるようです。 RYOでさえ私を混乱させています...ソースパッケージからテストパッケージのコードを呼び出すにはどうすればよいですか?

基本的に、サーバーからユニットテストの結果をきれいな形式で返す呼び出し可能な/ testリソースを作成したいと思います。/test/{category}ができれば、さらに良いです。

22

この質問を投稿した後に学んだことを共有し、StackExchange(無限の問題の解決策を求めてグーグルを通じて何度もアクセスしたサイト)に最初の回答を掲載したいと思いました。

ユニットvs統合vs機能テストの連続体

このテーマについては、多くの修正、議論、トローリングが行われているので、それを明確にしたいと思います。それはすべて本当にとても簡単です。あなたがいくつかのサービスを持っているとしましょう。あなたがそれを呼ぶとき、私が簡単に説明する一連のイベントがあります:

(要求を受信) 関数1を呼び出し)-(関数2を呼び出し)-(関数3を呼び出し)-(応答を送信)

ユニットテストでは、各関数(またはクラスまたはユニット)を個別にテストし、入力を入力して出力をチェックします。統合テストは、いくつかのユニット(機能2、機能3チェーンなど)を取り、また、オールインとオールアウトを行います。機能テストは、要求から応答までのチェーン全体で実行されます。スケールの各レベルでテストすることのいくつかの長所と短所を推測するのは読者に任せます。とにかく、これらのテストはすべてサーバーで実行でき、そこで実行するのには十分な理由があります。

コンテナ内/サーバー内テストの種類

もう1つポイントがあります。 Netbeansは、Mavenテストのほとんどの利点をWAR内テストに提供します。組み込みサーバーが含まれており、ビルド後に自動的に起動してデプロイします。 Firefoxも開きます.../testリソースを指すように設定するだけです。それはMavenのやり方と同じですが、もっと良いです。

とにかく、同じMavenプロジェクトでMavenテストとin-WARテストを一緒に行う方法を紹介します。

Springを使用したContainer-in-the-tests:

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を挿入します。ただし、アンパックされたWARファイルへのパスを動的に設定する必要がある場合はMockServletContextAwareProcessorが必要です。これは、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)/ src/testフォルダーに通常のJUnitテストを作成しますが、IT * .Javaまたは* IT.Javaまたは* ITCase.Java(MyClassIT.Javaなど)という名前を付けます。別の名前を付けることもできますが、これがフェイルセーフです。デフォルトで期待します。 ITは統合テストの略ですが、テストコードはテストの連続体のどこにあってもかまいません。たとえば、クラスをインスタンス化して単体テストしたり、HttpClient(またはJersey Client)を起動して自分自身に向けたり(以下のポートに注意)、エントリポイントを機能的にテストしたりできます。

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'を実行するか、IDEでビルドを押すだけで、コードがビルドされ、通常の* Test.Javaテストが実行され、jettyサーバーが起動し、* IT.Javaテストが実行されます。素敵なレポートを入手してください。

テストをWARにパッケージ化して、どこでも実行できるようにします。

(上記の手順と一緒に、または個別に使用してください)

ステップ1)maven-war-pluginにそれらを含めるように指示することにより、WARに埋め込まれたテストクラス(src/test /ディレクトリ)を取得します:(---(ここ から適応)

<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のみをコピーします)。ただし、includes/excludesを機能させることができませんでした。

また、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に1つ挿入する場合)、それらを削除しないでください。

ステップ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の参照で問題が発生します(ソースコードはテストコードを参照できないため)。これを回避するには、src/main/... [同じパッケージ]の下に完全に空のダミーTestResourceクラスを作成します...このトリックは、ダミーのTestResourceがパッケージ化中に実際の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」へのブラウザーポイントを開きます。

28

受賞キーワードは「コンテナ内テスト」であることが判明しました。真新しくて卓越したフレームワークは Arquillian です。

不思議なことに、他に何もないようです。 StackOverflowの他の誰か 質問 「これらのプロジェクトはあまり広く使われているとは思わないので、コンテナ内のテストに何か問題がありますか?」しかし、明確な返事はありませんでした。

コンテナ内テストでカバーする必要があるのは、単体テストと完全統合テストの2つの大きな領域の間の小さな領域にすぎないと思います。私にとっても、サーバーリソースにアクセスでき、機能しているかどうかを確認するために必要なテストはほんの一握りです。おそらく、コンテナ内のテストの調査(そして学習)にすべての時間を費やすよりも、手作業でそれらを書くべきでした。

3

Jakarta Cactus 私が探していることをしたようです。そのホームページには、「Cactusはサーバーサイドのユニットテスト用のシンプルなテストフレームワークですJavaコード... JUnitを使用しています... Cactusはコンテナ内戦略を実装しています...」URL http:// localhost:8080/test/ServletTestRunner?suite = TestSampleServlet のように、きれいなHTML出力が提供されます。

しかし、Apache Foundationは、活発な開発が行われていないため、屋根裏部屋に置きました。それは私がそれを使うことを考えるべきではないという意味ですか?屋根裏部屋のページには、「サボテンのユーザーは、他のテスト手法に切り替えることをお勧めします」と書かれていますが、それらが何であるかは説明されていません。

1

Surefireは、Mavenを使用して、テスト結果のフォーマットされたレポートを提供できます。

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

これらのレポートのコンテンツを利用可能にする方法は、送信されるかWebページに公開されるかにかかわらず、いくつもあります。多くのオプションがあります。

1
Mike Yockey

標準的な方法はないと思いますが、Spring Remotingを使用して、開発者のマシンから目的のサーバー上のメソッドを呼び出すことを検討できます。インターフェースを使用してテストしているサービスを注入する場合、Spring構成を変更するだけで、同じ単体テストを2回(ローカルで1回、サーバーで1回)実行できるはずです。

0
artbristol