Factory patternでデータベースのテストを効率化する

Atsushi Sumita
Oct 13, 2020 · 13 min read
An factory
An factory
An image of a factory from https://unsplash.com/photos/6xeDIZgoPaw

はじめに

こんにちは, FinatextグループのNowcastでデータエンジニア/データサイエンティストをやっている隅田(@yummydum)と申します.

  • Fixture Pattern
  • Factory Pattern

言語とフレームワーク

言語はPythonでSQLAlchemyでデータベースとやりとりします.データエンジニアリングの文脈ではよくあるチョイスでしょう.またテストフレームワークはpytestを使用します.これらについては特に説明しませんが, ソースコードの要点は自然言語で説明するので, これらについて知らなくとも本記事で伝えたいことは伝わると思います.

テーブル定義

本記事の例で使用するテーブルの実装を示します.記事を読み進める上では, 次の三点が分かれば問題ないので, 気になる人以外はコードを読み飛ばして進んで下さい.

  • メールアドレスのモデルであるAddressテーブルが存在する
  • AddressUserの主キーを外部キーとして参照している

テストの対象

テストの対象としてget_addressesis_hogemailsという関数を想定します.話を簡単にするためのtoy exampleなので, この関数何に使うんや??といった疑問は一旦置いておいて下さい. 実際の例としては複雑なSQLを投げている関数などをイメージして頂ければと思います. 関数の中身については以下の点だけ理解すれば記事を読み進めるには十分です.

  • テストデータの要件: 正常なAddressのレコードが複数入っていること
  • テストデータの要件: 不正なhogemailアドレスを保持しているAddressが一つ以上入っていること

テストの種類

テストといっても様々な種類が存在します. Unit test, integration test, functional test, end2end testなど様々な用語が(しばしば異なる意味で)用いられていますが, ナウキャストではgoogleが提唱しているTestSizeの基準に則って開発を行っています.

3つのパターン

前置きが長くなりましたが,以下で具体的に3つのパターンを見ていきましょう.なお, コードは全てこちらで公開しています.

ベタ書き

それぞれのテスト毎に必要なデータを愚直にsetupするパターン(?)です.Addressのレコードをデータベースに格納する際に外部キー制約を満たすためにUserレコードを用意する必要がある点に注意してください.辛そうな雰囲気だけ伝われば良いので流し読みでおkです.

Fixtureパターン

上記のコードを見ると, テスト間で共有するデータを一つの箇所にまとめてしまえば冗長さが減って嬉しいと考えるのは自然でしょう.この共有されるデータがfixtureと呼ばれるものです(pytest.fixtureとは違う概念なので注意). 以下のtest_data() がそれに該当します.

Factoryパターン

重複はなくしたいが柔軟性も保ちたい.その上可読性も良くしたい.そこで活躍するのがfactoryパターンです.このパターンの精神は, テストに関係ないデータは隠蔽するということです. 例えば, Addressテーブルに正常なレコードが3つ入ってさえいれば十分なテストデータについて, どのような引数でUserを初期化しデータベースに格納するかという操作は重要ではないので, 裏側でよしなにやってくれれば良い訳です. 逆にテストに関係しているデータは明示的にしておきます. 例えば不正なemail_addressが必要ならば, 不正なemail_addressがどのようなデータなのかが一目で分かるように記述します.

Factory boy

上記の例はfactory boyで実装されています. これはデータベースのテストにおいてfactory patternを実装するためのライブラリです. 上記の例を見て, "外部キー制約を満たすためのUserの初期化はどうなっているのか?"と思った方もいるかもしれませんが, 実はfactory boyの機能で必要な外部キー制約を満たすように自動でUserレコードを生成し永続化してくれています. このように便利なfactory boyなのですが, 検索してもあまり情報が出てこないし, 慣れない内は複雑なので, この記事をきっかけに入門して頂ければと思います.

Factoryクラスの定義

まずはどのようなテストデータを生成したいかをクラスで定義します.基本的にはmodelsと一対一で定義すれば良いです. 例えばUserFactoryUserのファクトリーメソッドの基となるクラスで, user_idなどのUserクラスのattributeをどのように埋めるべきかが宣言的に記述されています.

Sequence

factory boyにはFakerもサポートされており, ランダムに氏名やメールアドレスを埋めることが可能なのですが, 個人的にはSequenceを使用する方がおすすめです. というのも, ランダムに生成すると各attributeがユニークなのか重複しているのかが分からなくなるからです. ユニークなものを重複させるのは簡単ですが逆は難しいので, 基本的にはユニークをデフォルトにした方が良いと思います.ユニークにするためにはSequenceを使う方法が公式でも推奨されています.

SubFactory

別のクラスのオブジェクトがattributeとなる場合に, そのクラスのファクトリーメソッドを呼び出してattributeにセットしてくれます. SQLAlchemyのモデルにrelationship()によりuserを指定しておかないと対応するattributeがありませんと怒られるので注意.

SelfAttribute

自身の他のattributeから動的に別のattributeを生成するためのメソッドです. この例では単にuser_idを自らのattributeにセットしています.

Smallテストをどう実装するか?

ここまでmediumテストを前提に話して来ましたが, smallテストでも同じコードを使いまわしたいはずです. factory boyはデータベースに接続せずにモックオブジェクトを生成することにも使えます.

  • small testにはマーカーを付与しておき, small test以外のテストをスキップ出来るようにする
  • small testの内部ではFactory.build()だけを使用する

他のfactory patternの実装

非常に便利なfactory boyなのですが, 学習コストが高く導入しづらいというデメリットがあるのも確かです. そこでもう少しお手軽で導入しやすい別のfactory patternの実装についても軽く触れておきます.

終わりに

本記事ではfactory patternを用いることで柔軟性/可読性/保守性の高いテストを実現出来ることを紹介しました. 今回のテーマ以外にもパイプラインのテストには様々なポイントがあるので, 是非次節の参考資料をきっかけに情報収集して見て下さい.

参考資料

テストの定義や原則について

https://testing.googleblog.com/2010/12/test-sizes.html
https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html
https://testing.googleblog.com/2019/12/testing-on-toilet-tests-too-dry-make.html

データベースのテストについて

https://www.youtube.com/watch?v=a713rcagoYU
https://www.youtube.com/watch?v=ZBLaHL1mTW0

Factory boyについて

https://github.com/FactoryBoy/factory_boy
https://github.com/FactoryBoy/factory_boy/blob/92cc94d50ec892a0bada258be661dd544b45219d/docs/introduction.rst

Finatext

THE Finatext Tech Blog

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store