皆さんはMockを使っていますか?私は結構使っています。DBをモックにしたらテストかんたんにできていいじゃないですか?そう思いますか?
私はそう思わなくなりました。
モックのやなポイント
モックはMockitoなんかだと、java.sql.Connectionをモックにしてクエリ発行してこう来たらこう!ああ来たらあぁ。。。っていう感じにかけるんですけど、普通に考えて問題点があります。
- クエリがおかしくてもこういう結果返してね!と言ったら帰ってきてしまう
- 何でもできるがゆえにコードがぐちゃぐちゃだとテストもぐちゃぐちゃになっていく
- モックに甘えてコードがおかしくなっていく気がする
DB接続をモックにしていいのかどうか
個人的には「やめたほうがいいと思っている」が答えですが、理由は上述のとおりです。だからといって生のDBを使うとなると悩ましいポイントがあります。
つかいおわったDBどうすればいいのか?とか、専用のDBサーバー立ち上げるのか?とか。
テストするときに勝手にセットアップしてくれて、使い終わったら勝手に消えてくれるDBがほしい、そう、H2DBのメモリDBです。
H2DBのメモリDBなら良さそう
そう思って試したのが今回のエントリーになります。H2DBは互換モードというのがあるのでクエリの実行とその結果の検証ならそうそう問題なさそうです。使い終わったら勝手に消えてくれるし、インメモリなのでそもそも高速ですから、単体テストで使うにはいいと思います。
何よりもliquibaseを使うため、ステージングや本番環境で実DBと同じスキーマであることが期待できます。
DAOはモックにしてもいいと思う
DAOそのもののテストがしっかりと行えていて、DAOの挙動がきちんと担保されているならDAOから先はモックにしてもいいと思いますが、DIを使うようにしないとモッキングが嘘みたいにノッキングになっちゃいますよね。
やり方
pom.xmlに依存性を足します。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.25.0</version>
</dependency>
かんたんなDAOを作りました。SQLインジェクションどんとこいですね。
package org.tonouchi.collectionsdb;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class App
{
private Connection con;
public App(Connection con) {
this.con = con;
}
public int insertData(int id, String data) throws SQLException {
Statement stmt = con.createStatement();
return stmt.executeUpdate("insert into test_table(test_id,test_column) values("+id+",'"+data+"')");
}
public List<TestTable> getAllData() throws SQLException {
Statement stmt = con.createStatement();
ResultSet rset = stmt.executeQuery("select * from test_table");
List<TestTable> testTables = new ArrayList<>();
while(rset.next()) {
testTables.add(new TestTable(rset.getInt("test_id"), rset.getString("test_column")));
}
return testTables;
}
}
これのJUnitを書きます。
JUnitのテスト
@BeforeAllでDBのマイグレーションをして、@AfterAllでDBを捨てます。この辺はテストツイート作ってそこでやってもいいと思います。
private static Liquibase liquibase;
@BeforeAll
public static void beforeAll() throws Exception {
Map<String, Object> config = new HashMap<>();
Scope.child(config, () -> {
Class.forName ("org.h2.Driver");
Connection conn = DriverManager.getConnection ("jdbc:h2:mem:test", "sa","");
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(conn));
liquibase = new liquibase.Liquibase("changelog.xml", new ClassLoaderResourceAccessor(), database);
CommandScope updateCommand = new CommandScope(UpdateCommandStep.COMMAND_NAME);
updateCommand.addArgumentValue(DbUrlConnectionCommandStep.DATABASE_ARG, database);
updateCommand.addArgumentValue(UpdateCommandStep.CHANGELOG_FILE_ARG, "changelog.xml");
updateCommand.execute();
});
}
@AfterAll
public static void afterAll() throws LiquibaseException {
liquibase.close();
}
この辺実は公式のコピペです。えへへ
あとは、DBがある体でテストを書いていきます。Connectionが実際にDBを利用しているのが解ると思います。
private Connection connection;
private App app;
@BeforeEach
public void setUp() throws SQLException {
connection = DriverManager.getConnection ("jdbc:h2:mem:test", "sa","");
app = new App(connection);
}
@AfterEach
public void tearDown() throws SQLException {
app = null;
connection.close();
}
@Test
public void dataCheck() throws SQLException {
app.insertData(1, "ABC");
app.insertData(2, "DEF");
app.insertData(3, "GHI");
assertEquals(3, app.getAllData().size());
}
書き方としてはかなり雑ですし生のJDBCを使っているのは、今回やりたかったのがliquibaseを使ってDBマイグレーションしてっテストしたいと考えたからです。本当はHibernateとか使うんでしょうし、データの準備にはあいかわらすDBUnitなんでしょうかね。
ところでliquibaseってなに
liquibaseはDBマイグレーションツールです。DBに対する変更をファイル単位で書いていき、liquibaseはファイルを頭から順次実行していきます。こうすることでどのDBに対しても同じクエリが実行された状態にする、というものです。
バイバイモック!・・・なるか?
モック自体は便利でいいんですが、かと行って乱用するとテストが信用できないものになりかねないと感じていましたが、DB周りをどうやればいいのかというのも結構考えものでした。liquibaseとメモリDBを併用することで、DBのセットアップから実際にDBを使ったテストを書くことができるようになったのでこれはかなり行けていると思います。
Spring Bootなんかだともうこの辺もできるようになっているぽかったのですが、ポイントを絞ってみたかったのでこうなりました。
終わりです。