web-dev-qa-db-ja.com

MongoDBを使用した単体テスト

私が選んだデータベースはMongoDBです。クライアントアプリケーションから実装の詳細を抽象化するデータレイヤーAPIを記述しています。つまり、本質的に単一のパブリックインターフェイス(IDLとして機能するオブジェクト)を提供しています。

TDD方式でロジックをテストしています。各ユニットテストの前に、@Beforeメソッドが呼び出されてデータベースシングルトンが作成されます。その後、テストが完了すると、@Afterメソッドは、データベースを削除するために呼び出されます。これは、単体テスト間の独立性を促進するのに役立ちます。

ほぼすべての単体テスト、つまりコンテキストクエリの実行では、事前に何らかの挿入ロジックを実行する必要があります。私の公開インターフェースは挿入メソッドを提供します-しかし、このメソッドを各ユニットテストの前処理ロジックとして使用するのは間違っているようです。

本当にある種のモックメカニズムが必要ですが、モックフレームワークの経験はあまりなく、GoogleはMongoDBで使用するモックフレームワークについて何も返さないようです。

これらの状況で他の人は何をしますか?つまり、人々はデータベースとやり取りするコードをどのように単体テストしますか?

また、私のパブリックインターフェイスは外部構成ファイルで定義されたデータベースに接続します-ユニットテストにこの接続を使用するのは間違っているようです-再び、ある種のモックの恩恵を受ける状況ですか?

58
wulfgarpro

Sbridgesがこの投稿で書いたように、ロジックからのデータアクセスを抽象化する専用のサービス(リポジトリまたはDAOとも呼ばれる)を持たないことは悪い考えです。次に、DAOのモックを提供することにより、ロジックをテストできます。

私が行う別のアプローチは、Mongoオブジェクトのモック(例えばPowerMockito)を作成し、適切な結果を返すことです。これは、データベースが単体テストで機能するかどうかをテストする必要はありませんが、さらに適切なクエリがデータベースに送信されたかどうかをテストする必要があるためです。

Mongo mongo = PowerMockito.mock(Mongo.class);
DB db = PowerMockito.mock(DB.class);
DBCollection dbCollection = PowerMockito.mock(DBCollection.class);

PowerMockito.when(mongo.getDB("foo")).thenReturn(db);
PowerMockito.when(db.getCollection("bar")).thenReturn(dbCollection);

MyService svc = new MyService(mongo); // Use some kind of dependency injection
svc.getObjectById(1);

PowerMockito.verify(dbCollection).findOne(new BasicDBObject("_id", 1));

それもオプションです。もちろん、モックの作成と適切なオブジェクトの返送は、上記の例としてコーディングされています。

29
rit

技術的には、データベースと通信するテスト(nosqlまたはそれ以外)は ユニットテスト ではありません。テストは外部システムとの相互作用をテストするものであり、孤立したコードのユニットをテストするだけではありません。ただし、データベースと通信するテストは非常に有用であることが多く、他の単体テストと一緒に実行できるほど高速です。

通常、データベースを処理するためのすべてのロジックをカプセル化するサービスインターフェイス(UserServiceなど)があります。 UserServiceに依存するコードは、模擬バージョンのUserServiceを使用でき、簡単にテストできます。

Mongoと通信するサービス(MongoUserServiceなど)の実装をテストする場合、ローカルマシンでmongoプロセスを開始/停止するJavaコードを記述し、MongoUserServiceを取得するのが最も簡単です。それに接続するには、これを参照してください いくつかの注意事項の質問

MongoUserServiceのテスト中にデータベースの機能をモックしようとすることもできますが、通常はエラーが発生しやすく、実際にテストしたいもの(実際のデータベースとの相互作用)をテストしません。そのため、MongoUserServiceのテストを作成するときに、各テストのデータベース状態を設定します。データベースでこれを行うためのフレームワークの例については、 DbUnit をご覧ください。

61
sbridges

JavaでMongoDB偽の実装を作成しました: mongo-Java-server

デフォルトはインメモリバックエンドであり、ユニットおよび統合テストで簡単に使用できます。

MongoServer server = new MongoServer(new MemoryBackend());
// bind on a random local port
InetSocketAddress serverAddress = server.bind();

MongoClient client = new MongoClient(new ServerAddress(serverAddress));

DBCollection coll = client.getDB("testdb").getCollection("testcoll");
// creates the database and collection in memory and inserts the object
coll.insert(new BasicDBObject("key", "value"));

assertEquals(1, collection.count());
assertEquals("value", collection.findOne().get("key"));

client.close();
server.shutdownNow();
17

今日、ベストプラクティスは testcontainers library(Java)または testcontainers-python port on Pythonを使用することだと思います。単体テストでDockerイメージを使用できます。コンテナをJavaコードで実行するには、GenericContainerオブジェクトをインスタンス化するだけです( example ):

GenericContainer mongo = new GenericContainer("mongo:latest")
    .withExposedPorts(27017);

MongoClient mongoClient = new MongoClient(mongo.getContainerIpAddress(), mongo.getMappedPort(27017));
MongoDatabase database = mongoClient.getDatabase("test");
MongoCollection<Document> collection = database.getCollection("testCollection");

Document doc = new Document("name", "foo")
        .append("value", 1);
collection.insertOne(doc);

Document doc2 = collection.find(new Document("name", "foo")).first();
assertEquals("A record can be inserted into and retrieved from MongoDB", 1, doc2.get("value"));

またはPython(- ):

mongo = GenericContainer('mongo:latest')
mongo.with_bind_ports(27017, 27017)

with mongo_container:
    def connect():
        return MongoClient("mongodb://{}:{}".format(mongo.get_container_Host_ip(),
                                                    mongo.get_exposed_port(27017)))

    db = wait_for(connect).primer
    result = db.restaurants.insert_one(
        # JSON as dict object
    )

    cursor = db.restaurants.find({"field": "value"})
    for document in cursor:
        print(document)
7
Eugene Lopatkin

これまで誰も fakemongo を使用することを勧められなかったことに驚いています。 mongoクライアントを非常によくエミュレートし、すべて同じJVMでテストを実行します。したがって、外部システムとの対話が行われないため、統合テストは堅牢になり、技術的には真の「単体テスト」に近くなります。埋め込みH2を使用してSQLコードを単体テストするようなものです。データベース統合コードをエンドツーエンドでテストする単体テストでfakemongoを使用して非常に満足しました。テストスプリングコンテキストでこの構成を検討してください。

@Configuration
@Slf4j
public class FongoConfig extends AbstractMongoConfiguration {
    @Override
    public String getDatabaseName() {
        return "mongo-test";
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        log.info("Creating Fake Mongo instance");
        return new Fongo("mongo-test").getMongo();
    }

    @Bean
    @Override
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(), getDatabaseName());
    }

}

これにより、スプリングコンテキストからMongoTemplateを使用するコードをテストできます。また、 nosql-unitjsonunit などと組み合わせて、mongoクエリコードをカバーする堅牢なユニットテストを取得できます。 。

@Test
@UsingDataSet(locations = {"/TSDR1326-data/TSDR1326-subject.json"}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
@DatabaseSetup({"/TSDR1326-data/dbunit-TSDR1326.xml"})
public void shouldCleanUploadSubjectCollection() throws Exception {
    //given
    JobParameters jobParameters = new JobParametersBuilder()
            .addString("studyId", "TSDR1326")
            .addString("execId", UUID.randomUUID().toString())
            .toJobParameters();

    //when
    //next line runs a Spring Batch ETL process loading data from SQL DB(H2) into Mongo
    final JobExecution res = jobLauncherTestUtils.launchJob(jobParameters);

    //then
    assertThat(res.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
    final String resultJson = mongoTemplate.find(new Query().with(new Sort(Sort.Direction.ASC, "topLevel.subjectId.value")),
            DBObject.class, "subject").toString();

    assertThatJson(resultJson).isArray().ofLength(3);
    assertThatDateNode(resultJson, "[0].topLevel.timestamp.value").isEqualTo(res.getStartTime());

    assertThatNode(resultJson, "[0].topLevel.subjectECode.value").isStringEqualTo("E01");
    assertThatDateNode(resultJson, "[0].topLevel.subjectECode.timestamp").isEqualTo(res.getStartTime());

    ... etc
}

Mongo 3.4ドライバーで問題なくfakemongoを使用しましたが、コミュニティは3.6ドライバーをサポートするバージョンをリリースすることに本当に近いです( https://github.com/fakemongo/fongo/issues/316 )。

1
int21h