おしりんブログ

新人PGおしりんの色々まとめるよブログ

テスト~Spring~その2

みなさんこんにちは、おしりんです。

私事ですが、先日23歳になりました。
社会人になって初めての誕生日でした。

当日は朝から「誕生日なのに仕事…」とかブツブツ言ってました。

でもね、ほんと、誕生日って自分にとってはなんとなく特別な日だけど、
他人からしたら、別になんでもない日なんですよね。

あ、なんかひねくれたようなこと言ってますけど
嬉しい事に仕事おわりに美味しいご飯連れてってもらえたので、
結果的には( ´∀`)bグッ!な一日になりました。

そんなどうでもいい話から始まりましたが、
本日まとめるのはこちら。ででん。

JUnitを使用したSpringのテストについて第二弾!

前回の記事から続いているのでもしよかったらこちらもどうぞ。
テスト~Spring~

そんなわけで

第二弾ではあれです。
テストの中でDBにつないじゃいます。

コントローラークラスを実装する時、どうやってもDBにつながないことなんてないじゃないですか。
コントローラーの中でサービスクラスをAutoWiredしてDBにつなぐじゃないですか。

あれをMockを使わないでテストしちゃいます。

使用するのは、前回から引き続き

  • SpringJUnit4ClassRunner

と、こちら

この2つを組み合わせてテストを実装していきます。

@Annotation

まずは@Annotationについて。
結構ごちゃごちゃとクラスにくっつけます。

  • @RunWith(SpringJUnit4ClassRunner.class)
  • @ContextConfiguration(locations = { "classpath:test-application-config.xml" })

ここまでは前回と同じ。
追加するのは次の3つ。

  • @TransactionConfiguration
  • @Transactional
  • @WebAppConfiguration

トランザクションの管理をしてくれる。
テスト用に更新したデータをロールバックしてくれたりとか。

一番下のは…ちょっとなんで付けたのか思い出せないんですけど、
これなかったら正常に動かなかったです。f:id:shiori_west:20150309191825p:plain

どこで見つけてきたんだっけなあ。(にじみ出るテキトー精神)

設定ファイルへの追記

前回の記事でも|д゚)チラッと書いたけど
DBUnitを使用する場合は、データソースを上書く必要があります。

 <!-- もともとの設定 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/DB名" />
        <property name="username" value="root" />
        <property name="password" value="mysql" />
    </bean>

    <!-- 追加部分 -->
    <bean id="dataSourceTest"
        class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
        <constructor-arg ref="dataSource"/>
    </bean>

とりあえずこんな感じ。

SpringとDBUnitを一緒に使うためのデータソースは
TransactionAwareDataSourceProxyじゃなきゃだめなのです。

理由はこちらのサイトで詳しく説明してくれてます。
ありがたやありがたや…

DBUnitへの対応

SpringのテストをDBUnitに対応させるには、以下のテストケースを継承させる。

  • DataSourceBasedDBTestCase

TestCase that uses a DataSourceDatabaseTester.
ということで、このクラスのメソッドをごちゃごちゃとオーバーライドして実装していきます。

さっそく実装していきまひょう。
の、前に。

このクラス、デフォルトのタイムゾーンが9時間ずれています。
GMT(世界標準時間)との時差を計算して、あえてずらしてあるらしいっす。(モヤさま風)

この時差を修正しないと、DBUnitを経由して日時のインサートやら更新やらをした際、
意図した時間と異なる時間が登録されてしまいます。

ということで、DataSourceBasedDBTestCaseクラスのラッパークラスを作成して、
その中でタイムゾーンを修正したいと思います。

public abstract class DataSourceBasedDBTestCaseWrapper extends DataSourceBasedDBTestCase {
    @Override
    protected void setUp() throws Exception {
        TimeZone t = TimeZone.getDefault();
        // dbunit modifies time incorrectly when timezone is not GMT.
        TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
        databaseTester.onSetup();
        TimeZone.setDefault(t);

        super.setUp();
    }
}

参考▶▶山下寛人オフィシャルブログ
ありがとうございましたm(__)m

なので、テストクラスに継承させるのはDataSourceBasedDBTestCase.classではなく、
自作したDataSourceBasedDBTestCaseWrapper.classということになりまする。

テストデータの準備

引き続きDBUnitを使用するための準備です。
DBUnitについては詳しく説明しませんー。

今回はエクセルにテストデータを定義したいと思いまうす。
エクセルの他にもCSVXML等にも定義できまうす。

こんな感じどん。

f:id:shiori_west:20150309191825p:plain

ファイル名は原則としては「テストケース名.xls」らしい?

S2JUnit4とかは「テストクラス名_メソッド名.xls」か「テストクラス名.xls」
この形じゃないと動かなかった気がする。

シートにテーブル名を定義して、一行目にカラムを定義する。
あとはテスト実行時に読み込みたいデータを追加していくだけ。

ファイルを作成する時の注意点としては、

  • 不必要なシートは削除する

くらいですかね。
不必要なシートが存在したままファイルを読み込むとエラーが発生するんですよ。

エクセル立ち上げた時にデフォルトで作成される「シート2」とか「シート3」とか
ついつい消し忘れてしまってよく怒られます。

ファイルはテストクラスから見えるところに置いて下さい。
私は今回src/test/resource上にxlsフォルダを作成し、その中に配備しました。

テストの実装

準備が整ったのでようのやくで実装していきますー。
1つ上の見出しでも書いたように、色々とオーバーライドしていきます。

とりあえずテスト対象のコントローラークラスどん。

かなりテキトーなおいにーが漂っていますが、
ログイン認証をしてくれるクラスだと思っていただければ…。

DBにユーザ情報が存在したらSUCCESSページに、失敗したらFAILページに遷移します。

@Controller
public class LoginController {

    @Autowired
    UserService userService;

    @RequestMapping(value="/login", method = RequestMethod.GET)
    public String login(Model model, Principal principal) {
        String id = principal.getName();
        UserInfo user = UserService.findById(id);

        String view;
        if (user != null) {
            view = "success";
        } else {
            view = "fail";
        }
        return view;
    }
}

つづいてテストどん。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:test-application-config.xml" })
@TransactionConfiguration
@Transactional
@WebAppConfiguration
public class LoginControllerTest extends DataSourceBasedDBTestCaseWrapper {

    private final String RESOURCE_PATH
        = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    // テストケース名の取得
    private String testClassName = "xls/" + getClass().getSimpleName();

    @Autowired
    LoginController sut;

    // ①
    @Autowired
    private TransactionAwareDataSourceProxy dataSourceTest;

    // 使用するDBの設定
    @Override
    protected void setUpDatabaseConfig(DatabaseConfig config) {
        config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());
    }

    // ①で定義したデータソースの取得
    @Override
    protected DataSource getDataSource() {
        return dataSourceTest;
    }

    // XlsDataSetのコンストラクタでエクセルファイルが置いてあるディレクトリを参照
    @Override
    protected IDataSet getDataSet() throws Exception {
        return new XlsDataSet(new File(RESOURCE_PATH + testClassName + ".xls"));
    }

    // 親クラスのSetUpメソッドの呼び出し
    @Before
    public void setUp() throws Exception {
        super.setUp();
    }

    @Test
    public void ログイン成功の場合はSUCCESSページのVIEW名を返却() {
        // modelとprincipalのセットアップは省略
        String actual = sut.login(model, principal);
        String expected = "success";

        assertThat(actual, is(expected));
    }
}

テスト対象クラス自体をAutoWiredするって自分では思いつかないなあ。

DB接続の部分をMockを使わないでかけるから
より本番に則したテストができるのですねん。(たぶん)

今回はDBのデータを参照することしかしてないけど
インサートとか更新、削除がからんでくるとトランザクションに気をつけなきゃだったり…

インサートや更新時のテストも時間ある時においおいのっけていきたいです。

それではでは。おしまい!