テストの第三世代
Robの意見:永続化とテストに関して、私たちは現在「テストの第三世代」にいると考えています。
(I) 第一世代 - すべての永続化をモック/スタブ化
第一世代では、実データベースに対するテストは一般的に遅すぎ、コストがかかりすぎ、困難でした。テストでは、すべての永続化(多くの場合、リポジトリAPIを介して)をモック/スタブ化していました。テストは、「統合サーバー」でのみ実データベースに対して実行されました。
(II) 第二世代 - インメモリDB
第二世代のテストでは、H2のようなインメモリデータベースが利用可能になりました。これにより、開発者はすべての永続化APIをモックアウトするのではなく、これらのインメモリデータベースを使用できるようになりました。
これにより、テストで使用されるモック/スタブの量が大幅に減少しました。
H2のようなインメモリDBを使用したテストの制限は、PostgresやOracleなどの「実際の」ターゲットデータベースと比較した機能の違いにあります。型(UUID、配列、Json、Hstore、範囲型など)と機能(SQL関数、高度なロック、テーブルパーティショニングなど)の違いです。
(III) 第三世代 - Docker DBを使用したテスト
H2に対するテストの制限は、この「永続化テストの第三世代」で対処できるようになりました。Dockerにより、開発マシンへのデータベースのインストール/セットアップを自動化することが容易になりました。開発マシンは、(Dockerバージョンのデータベースは本物と同じ機能を持つため)「実際の」ターゲットデータベースに対して十分な速度でテストを実行できるほど強力です。
- テストはデータベース固有の機能と型を網羅できます(テストカバレッジに関する言い訳はありません)
- Dockerテストコンテナの使用の容易さは、H2の使用の容易さに匹敵する必要があります。
- 新しい/クリーンな一時データベースに対してテストを実行したいと考えています。
- テストを高速に実行する必要があります - インメモリデータベースのエクスペリエンスに匹敵します。
- CIサーバーで実行されるビルド/テストは、開発マシンで実行されるビルド/テストと一致する必要があります。
ebean-test
は、Dockerテストコンテナに対するテストをH2を使用するのと同じくらいシンプルで良好にするために提供されています。プロジェクトに不慣れな開発者は、git clone
とmvn clean test
を実行するだけで、セットアップ手順なしで「動作」します。
Postgres、MySql、SQL Serverの場合、今日ではDockerテストコンテナを非常にうまく使用できます。Oracle、SAP Hana、DB2のような比較的重いデータベースの場合、成功する可能性がありますが、状況によってはH2を使い続けるという議論があるかもしれません。
個人的には、過去数年間にわたってebean-test/Dockerテストコンテナ(主にPostgres)を非常にうまく使用してきました。優れたテストカバレッジ、よりシンプルなテストコード、開発者、CI、本番環境間の差異の削減を目指して、このアプローチを検討することを強くお勧めします。
ebean-testは、ElasticSearchおよびRedisコンテナの使用もサポートしています。
ebean-test依存関係
1. テスト依存関係としてebean-testを追加する
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.0</version>
<scope>test</scope>
</dependency>
2. application-test.yamlを追加する
テストに使用するapplication-test.yaml
設定ファイルをsrc/test/resources
に追加します。
ebean:
test:
platform: h2 #, h2, postgres, mysql, oracle, sqlserver, hana, clickhouse, sqlite
ddlMode: dropCreate # none | dropCreate | migrations | create
dbName: my_app
このテスト設定を変更して、実行されるDDL(create-all.sqlまたはマイグレーション)と、テスト対象のデータベースプラットフォーム(Dockerを使用する場合もある)を制御できます。
3. ~/.ebean/ignore-docker-shutdownを追加する
mkdir ~/.ebean
touch ~/.ebean/ignore-docker-shutdown
CIサーバーでテストを実行する場合、通常はテストが完了したらDockerコンテナを停止して削除します。ただし、ローカル開発では、Dockerコンテナを実行したままにして、テストの実行速度を速くしたいと考えています。
マーカーファイル~/.ebean/ignore-docker-shutdown
を追加すると、Dockerコンテナは実行されたままになります(ローカル開発に適しています)。
ebean-testは、以下を処理します。
- DDLの生成と実行モード
- データベースプラットフォームに基づくDockerテストコンテナのセットアップと実行
- まだ指定されていない場合の現在のユーザーとテナントプロバイダー(@Whoプロパティなどのテストを容易にするため)
ebean.test.platform - application-test.yaml
ebean.test.platform
を使用して、テストの実行時に使用するデータベースプラットフォームを指定します。たとえば、h2、postgres、mysqlなどをプラットフォームとして指定してテストを実行できます。
例:Postgresに対するテスト
ebean:
test:
platform: postgres # h2, postgres, ...
ddlMode: dropCreate # none | dropCreate | migrations | create
dbName: my_app
例:MariaDBに対するテスト
ebean:
test:
platform: mariadb
ddlMode: dropCreate # none | dropCreate | migrations | create
dbName: my_app
各データベースプラットフォームの詳細については、以下を参照してください。
clickhouse、cockroach、db2、h2、hana、mariadb、mysql、nuodb、oracle、postgres、sqlite、sqlserver、yugabytedb
DDLモード
ほとんどの場合、dropCreate
モードを使用します。これは、すべてのテストを実行する前にデータベースが削除されてから再作成されることを意味します。
データベースのマイグレーションをテストするためにマイグレーションを使用します。
モード | 説明 |
---|---|
dropCreate | すべてのテーブルなどを削除してから作成します。最も一般的に使用されるモードです。 |
none | DDLを実行しません。DDLの変更なしで特定のテストを実行したい場合に役立ちます。 |
migrations | DBマイグレーションを実行しますが、最初にデータベースを削除して、マイグレーションが新しいデータベースに対して実行されるようにします。 |
create | create-all.sql DDLスクリプトを実行しますが、最初にデータベースを削除して再作成します。 |
dropCreate
はdb-create-all.sql
とdb-drop-all.sql
スクリプトを生成し、これらはMavenのtarget
ディレクトリまたはGradleのbuild
ディレクトリにあります。
Dockerテストコンテナ
Ebeanは、Postgres、MariaDB、MySql、SqlServer、Oracle、Hana、DB2、Clickhouse、CockroachDB、YugabyteDB、Redis、ElasticSearchに加えて、DynamoDBとLocalstackのDockerテストコンテナを提供します。
ebean-testは、Dockerコンテナを自動的に管理し、テストの実行の準備が整うように設定します。開発者はプラットフォーム(例:postgres)を指定するだけで、Ebeanがそれ以降の処理を行います。
- Dockerコンテナを起動する
- コンテナの準備ができるまで待つ
- 必要な権限を設定してデータベースとユーザーを作成する
- 準備ができたら、テストを実行できるようにする
io.ebean.docker
のロギングをTRACE
に増やすことで、何が起こっているかを確認/レビューできます。そうすると、次のようなログメッセージが表示されます。
... Docker test container start and setup
15:15:02.537 INFO io.ebean.docker.commands.Commands - Container ut_postgres running with port:6432 db:test_ex user:test_ex mode:Create shutdown:
15:15:02.538 DEBUG io.ebean.docker.commands.Commands - docker exec -i ut_postgres pg_isready -h localhost -p 5432
15:15:02.645 DEBUG io.ebean.docker.commands.Commands - docker exec -i ut_postgres psql -U postgres -c select datname from pg_database
15:15:02.753 DEBUG io.ebean.docker.commands.Commands - docker exec -i ut_postgres psql -U postgres -c select rolname from pg_roles where rolname = 'test_ex'
15:15:02.871 DEBUG io.ebean.docker.commands.Commands - docker exec -i ut_postgres psql -U postgres -c select 1 from pg_database where datname = 'test_ex'
15:15:02.960 DEBUG io.ebean.docker.commands.Commands - create database extensions hstore,pgcrypto
15:15:02.960 DEBUG io.ebean.docker.commands.Commands - docker exec -i ut_postgres psql -U postgres -d test_ex -c create extension if not exists hstore
15:15:03.058 DEBUG io.ebean.docker.commands.Commands - docker exec -i ut_postgres psql -U postgres -d test_ex -c create extension if not exists pgcrypto
15:15:03.143 DEBUG io.ebean.docker.commands.Commands - waitForConnectivity ut_postgres ...
15:15:03.143 DEBUG io.ebean.docker.commands.Commands - checkConnectivity on ut_postgres ...
15:15:03.190 DEBUG io.ebean.docker.commands.Commands - connectivity confirmed for ut_postgres
15:15:03.190 DEBUG io.ebean.docker.commands.Commands - Container ut_postgres ready with port 6432
...
15:15:03.239 [main] INFO o.a.datasource.pool.ConnectionPool - DataSourcePool [db] autoCommit[false] transIsolation[READ_COMMITTED] min[2] max[200]
15:15:03.277 [main] INFO io.ebean.internal.DefaultContainer - DatabasePlatform name:db platform:postgres
... DDL Execution
15:15:03.618 [main] INFO io.ebean.DDL - Executing extra-dll - 0 statements
15:15:03.618 [main] INFO io.ebean.DDL - Executing db-drop-all.sql - 26 statements
15:15:03.649 [main] DEBUG io.ebean.DDL - executing 1 of 26 alter table if exists address drop constraint if exists fk_address_country_code
15:15:03.651 [main] DEBUG io.ebean.DDL - executing 2 of 26 drop index if exists ix_address_country_code
...
15:15:03.701 [main] INFO io.ebean.DDL - Executing db-create-all.sql - 28 statements
15:15:03.701 [main] DEBUG io.ebean.DDL - executing 1 of 28 create table address ( id bigserial not null, line1...
15:15:03.709 [main] DEBUG io.ebean.DDL - executing 2 of 28 create table contact ( id bigserial not null, first_n...
...
15:15:03.841 [main] INFO io.ebean.DDL - Executing extra-dll - 1 statements
15:15:03.842 [main] DEBUG io.ebean.DDL - executing 1 of 1 create or replace view order_agg_vw as select d.order_id as id, d.order_id as or...
コンテナの起動
Dockerコンテナを起動するために、ebean-test
はEbeanライフサイクルにフックします。これは、テストがIDE、Maven、Gradle、または任意のビルドツールから実行されるかに関係なく、「動作」することを意味します。以前のイテレーションでは、Dockerコンテナの起動はMavenライフサイクルに特異的にフックされていましたが、これは理想的ではありませんでした。このアプローチでは、テストコードを変更する必要もなくなります。
このDockerテストコンテナの統合は、H2データベースを使用する場合と同様の開発者エクスペリエンスを提供します。つまり、ebean-testは(必要に応じて)データベースを起動し、データベースユーザー、ロール、スキーマなどを設定し(必要に応じて)、DDLを実行することでテストの準備が整ったデータベースを取得します(通常はテーブルの削除と再作成など)…そしてすべてのテストを実行します。
プロジェクトに不慣れな開発者は、git clone
とmvn clean test
を実行するだけで、セットアップ手順なしで「動作」します(開発マシンにDockerがインストールされている限り)。
コンテナの停止
開発マシンでは、一般的にDockerコンテナを実行したままにして、テストの実行速度を速くしたいと考えています。IDEを介して単一のテストを実行する開発者にとって、コンテナはすでに実行されているため、ほとんどの場合、テーブルは削除されて再作成されるだけで、インメモリH2を使用する場合の速度に近づきます。
開発マシンでDockerコンテナを実行したままにするには、~/.ebean/ignore-docker-shutdown
にマーカーファイルを作成します。
CIサーバーでは、テストの完了時にDockerコンテナを停止して削除したいと考えています。これを行うには、shutdown
をremove
(コンテナを停止して削除する)またはstop
(コンテナを停止するだけ)に設定します。
ebean:
test:
shutdown: remove # stop | remove
platform: postgres # h2, postgres, mysql, oracle, sqlserver
ddlMode: dropCreate # none | dropCreate | migrations
dbName: my_app
Dockerを使用しない場合
Dockerコンテナを起動して実行したくない場合、代わりに別の既存のデータベースに対してテストを実行するには、useDocker: false
を設定します。
以下の設定は、既存のPostgresデータベースに対して実行されます。通常、Dockerを使用しない場合、ユーザー名、パスワード、URLを適切な値に設定する必要があります。
useDocker: false
を使用する場合、データベースとユーザーはすでに存在している必要があります。
ebean:
test:
useDocker: false ## DO NOT USE DOCKER
platform: postgres # h2, postgres, mysql, oracle, sqlserver
ddlMode: dropCreate # none | dropCreate | migrations | create
dbName: test
postgres:
username: test
password: test
url: jdbc:postgresql://:5432/test
現在のユーザーとテナント
ebean-testは、現在のユーザープロバイダーと現在のテナントプロバイダーを自動的に登録します。これらは、自分で設定しない場合にのみ設定されます。
つまり、何もせずに、テストで@WhoCreated / @WhoModified
を使用でき、io.ebean.test.UserContext
を介してテストで現在のユーザーとテナントを設定できるということです。
// set the current userId which will be put
// into 'WhoCreated' and 'WhoModified' properties
UserContext.setUserId("U1");
DDL生成プロパティ
ebean-test
を使用していない場合は、DDLの生成とdb-create-all.sql
およびdb-drop-all.sql
の実行を制御する適切なプロパティを設定する必要があります。以下のプロパティを使用して、DDLの生成とdb-create-all.sql
およびdb-drop-all.sql
の実行を制御します。
プロパティ | 説明 |
---|---|
ddl.generate | db-create-all.sql とdb-drop-all.sql のDDLスクリプトを生成するには、trueに設定します。 |
ddl.run | db-create-all.sql 、db-drop-all.sql 、および追加のDDLスクリプトを実行するには、trueに設定します。 |
ddl.createOnly | db-create-all.sql を実行するが、db-drop-all.sql を実行しないようにするには、trueに設定します。データベースにデータが入力されていない/削除するテーブルがないことがわかっている場合、主にH2データベースのインメモリテストで使用されます。 |
ddl.initSql | create-all ddlの実行前に実行するSQLスクリプトを指定します。 |
ddl.seedSql | create-all DDLを実行した後に実行するSQLスクリプトを指定します。通常、これはテストデータベースにシードデータ挿入します。 |
ebean.migration.run | EbeanServerの起動時にマイグレーションを実行するかどうかをtrueまたはfalseで設定します。 |
application-test.propertiesの例
ebean.db.ddl.generate=true
ebean.db.ddl.run=true
ebean.db.ddl.initSql=initialise-test-db.sql
ebean.db.ddl.seedSql=seed-test-db.sql
datasource.db.username=sa
datasource.db.password=
datasource.db.databaseUrl=jdbc:h2:mem:tests
datasource.db.databaseDriver=org.h2.Driver
DDL/SQLスクリプトランナー
ScriptRunner
を使用してDDLとSQLスクリプトを実行できます。通常、これらはシードSQLスクリプトやTRUNCATE SQLスクリプトなど、テストに使用されるスクリプトです。
スクリプトはそれぞれ独自のトランザクションで実行され、正常に完了するとコミットされます。
簡単な使用例
Database database = DB.getDefault();
database.script().run("/scripts/test-script.sql");
スクリプト内でプレースホルダーを使用する例
Map<String,String> placeholders = new HashMap();
placeholders.put("tableName", "e_basic");
Database database = DB.getDefault();
database.script().run("/scripts/test-script.sql", placeholders);
SQLスクリプトでプレースホルダーを参照する方法は次のとおりです。
delete from ${tableName}
select count(*) from ${tableName}
スクリプトへのパスは「/」で始まる必要があることに注意してください。Ebeanはthis.getClass().getResource(PATH_TO_RESOURCE)
を使用してリソースとしてスクリプトをロードするため、リソースはクラスパスで使用可能である必要があります。
ElasticSearch
Ebeanを使用すると、他のデータベース(PostgreSQLなど)なしでElasticSearchを単独で使用したり、他の[真実のソース]データベース(PostgreSQLなど)と組み合わせてElasticSearchを使用したりできます。
DockerコンテナとしてElasticSearchを自動的に起動するには、下記のようにapplication-test.yamlにebean.docstore
プロパティを設定します。
ebean:
test:
platform: h2
ddlMode: dropCreate # none | dropCreate | migrations | create
dbName: myapp
docstore:
url: http://127.0.0.1:9201
active: true
generateMapping: true
dropCreate: true
elastic:
version: 5.6.0
port: 9201
詳細については、データベース/Elasticsearchを参照してください。
Redis
L2キャッシングにRedisを使用する場合、ebean-test
を使用してRedis Dockerテストコンテナを自動的に起動できます。これを行うには、下記の例のようにebean.test.redis=latest
プロパティを追加します。
ebean:
test:
redis: latest
platform: h2 # h2, postgres, mysql, oracle, sqlserver, sqlite
ddlMode: dropCreate # none | dropCreate | migrations | create
dbName: my_app
詳細については、データベース/Redisを参照してください。