Javaで同時リクエストをテストする方法

2014-11-02T00:00:00+00:00 Java

例えば、テスト時に同時リクエストが発生させて処理が異常がなのをチェックしたりする方法

検証アプリケーションの作成

単純にSpring Frameworkでの依存性注入は基本的にはSingletonで行われる。そこでSingletonで注入されると問題になるようなケースを利用する。ちなみにhttp://javatechnology.net/spring/spring-singleton-scope/を利用

SampleService.java

package sample;

import org.springframework.stereotype.Service;

// @Scope("prototype")は指定しない
@Service
public class SampleService {

    private String name;

    public String getName() {
        System.out.println(Thread.currentThread().toString());

        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SampleController.java

package sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Scope("prototype")
@Controller
@RequestMapping("/sample")
public class SampleController {

    @Autowired
    SampleService service;

    @RequestMapping("/index")
    @ResponseBody
    public String index(@RequestParam("name") String name) throws InterruptedException {
        service.setName(name);
        Thread.sleep(3000);

        return service.getName();
    }
}

ここには@Scope(“prototype”)を指定しているけど、コントローラー自体はprototypeインスタンス方式が採用されてもそれで注入されるSampleService側自体はprototypeインスタンスではなくSingletonインスタンスになる。なので同時リクエストをした場合等においては返ってくるレスポンス自体が後でリクエストされたので汚染されてしまって不整合な値になるのではと思う

まぁ単純にこういうケースな処理する事まずありえないと思うんですが(あくまで注入したオブジェクトの状態を破壊するような実装する事自体が)

まぁあくまでアプリケーション側の例題なので(ry

SampleTest.java

でテスト時の要求として複数のURLに同時にリクエストして処理する的な所だと思うので

package sample;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.context.WebApplicationContext;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ContextConfiguration("classpath:spring-web-servlet.xml")
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class SampleTest {

    @Autowired
    WebApplicationContext context;

    private static MockMvc mvc;

    @Before
    public void setUp() {
        mvc = webAppContextSetup(context).build();
    }

    @Test
    public void test_run() throws Exception {
        ExecutorService es = Executors.newFixedThreadPool(2);

        Future<MvcResult> future1 = es.submit(new Callable<MvcResult>() {
            @Override
            public MvcResult call() {
                MvcResult result =  null;

                try {
                    result = mvc.perform(get("/sample/index?name=hoge"))
                        .andExpect(status().isOk())
                        .andReturn();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return result;
            }
        });

        Future<MvcResult> future2 = es.submit(new Callable<MvcResult>() {
            @Override
            public MvcResult call() {
                MvcResult result = null;

                try {
                    result = mvc.perform(get("/sample/index?name=fuga"))
                        .andExpect(status().isOk())
                        .andReturn();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return result;
            }
        });

        MvcResult result1 = future1.get();
        MvcResult result2 = future2.get();

        assertThat(result1.getResponse().getContentAsString(), is("hoge"));
        assertThat(result2.getResponse().getContentAsString(), is("fuga"));
    }
}

んな感じでExecutorServiceを使ってやれば良いっぽい。テスト実行してレポート見ると

っていうような感じで同時リクエストして注入オブジェクトが汚染されるような状態になりエラーになるような感じ。でSampleService側に@Scope(“prototype”)をつけるとテストが正常になる

一応メモっておく

余談

今回のプロジェクトに限らずにJUnitのテストを並列で動かすようなケースであれば http://java.dzone.com/articles/concurrent-junit-tests を参考にConcurrentJUnitRunnerを使えば出来るっぽい

Spring Frameworkのdefault scope chrome.gcm