単体テストを行うと、作成したコードの品質を検証するだけでなく、コードの作成を進めながら開発プロセスの改善を行えます。アプリケーションの開発が終わった後にテストを作成するのではなく、開発プロセスと並行して単体テストを作成することを検討してください。これにより、保守が簡単で再利用できる単体コードを作成できます。また、コードのテストをすばやく、徹底的に行うことができます。
ローカルで単体テストを行う場合、ユーザーの開発環境内ですべてのテストを行います。リモートのコンポーネントは使用しません。App Engine のテスト ユーティリティを使用すると、データストアや他の App Engine サービスをローカルに実装できます。コードを App Engine にデプロイする必要はありません。サービススタブにより、これらのサービスをローカルで利用できるようにします。
サービススタブはサービスの動作をシミュレートします。たとえば、データストアと Memcache のテストを作成するで説明するデータストアのサービススタブでは、実際のデータストアにリクエストを送信することなく、データストアのコードをテストできます。データストアの単体テストで生成されるエンティティは、データストアではなくメモリ上に格納され、テストの実行後に削除されます。データストア自体に依存することがないため、短時間で実行できる小規模なテストを実行できます。
このドキュメントでは複数のローカル App Engine サービスに対する単体テストを作成する方法を説明し、テスト用フレームワークを設定するうえでの詳細情報を提供します。
Python 2 テスト用ユーティリティの概要
App Engine の testbed
という Python モジュールでは、単体テスト用のサービススタブを使用できます。
以下のサービスのサービススタブが用意されています。
- App Identity(
init_app_identity_stub
) - Blobstore(
init_blobstore_stub
を使用) - Capability(
init_capability_stub
を使用) - Datastore(
init_datastore_v3_stub
を使用) - Files(
init_files_stub
を使用) - Images(dev_appserver のみ、
init_images_stub
を使用) - LogService(
init_logservice_stub
を使用) - Mail(
init_mail_stub
を使用) - Memcache(
init_memcache_stub
を使用) - Task Queue(
init_taskqueue_stub
を使用) - URL fetch(
init_urlfetch_stub
を使用) - User service(
init_user_stub
を使用)
すべてのスタブを同時に初期化するには、init_all_stubs
を使用します。
データストアと Memcache のテストを作成する
ここでは、データストア サービスと Memcache サービスの使用をテストするコードの作成例を示します。
テストランナーに、Python ロードパスに App Engine ライブラリ yaml
(App Engine SDK に同梱)を含む適切なライブラリとアプリケーション ルートが設定されていること、アプリケーション コードで前提とされているライブラリパス(ローカル ./lib
ディレクトリがある場合は、そのディレクトリなど)に対する変更がすべて適用されていることを確認します。例:
import sys
sys.path.insert(1, 'google-cloud-sdk/platform/google_appengine')
sys.path.insert(1, 'google-cloud-sdk/platform/google_appengine/lib/yaml/lib')
sys.path.insert(1, 'myapp/lib')
まず、テスト対象のサービスに関連する Python unittest
モジュールと App Engine モジュールをインポートします。この場合は、memcache
と ndb
(データストアおよび Memcache を使用)です。また、testbed
モジュールもインポートします。
次に、TestModel
クラスを作成します。この例では、エンティティが Memcache に格納されるかどうかをチェックする関数を使用します。この関数では、エンティティが見つからない場合、データストア内でエンティティの有無をチェックします。ndb
は Memcache 自体をバックグラウンドで使用するため、実際にはこのチェックは冗長であることが多いですが、それでもテストの OK パターンです。
次に、テストケースを作成します。どのサービスをテストする場合でも、テストケースでは Testbed
インスタンスを作成してアクティブ化する必要があります。また、関連するサービススタブを初期化する必要もあります。この場合は、init_datastore_v3_stub
と init_memcache_stub
を使用します(他の App Engine サービススタブを初期化するメソッドについては、Python テスト用ユーティリティの概要をご覧ください)。
init_datastore_v3_stub()
メソッドに引数が指定されていない場合は、初期状態では空となっているメモリ内データストアが使用されます。既存のデータストア エンティティをテストするには、そのパス名を init_datastore_v3_stub()
の引数として指定します。
setUp()
に加えて、testbed を無効にする tearDown()
メソッドを追加します。これにより、元のスタブが復元されるため、テストが互いに干渉することがなくなります。
次に、テストを実装します。
以上で、TestModel
を使用して、実際のサービスの代わりにデータストア サービスや Memcache サービスのスタブを使用するテストを作成できるようになります。
たとえば、下に示すメソッドでは 2 つのエンティティが作成されます。1 つ目のエンティティは number
属性のデフォルト値(42)を使用し、2 つ目のエンティティは number
のデフォルトではない値(17)を使用します。次に、このメソッドは TestModel
エンティティに対するクエリを作成しますが、そのクエリで対象とするのは、number
にデフォルト値が設定されているエンティティのみです。
一致するすべてのエンティティの取得後、このメソッドでは、エンティティが 1 つだけ見つかったこと、そのエンティティの number
属性値がデフォルト値であることを検証します。
別の例として、以下のメソッドはエンティティを作成し、上記で作成した GetEntityViaMemcache()
関数を使用してそのエンティティを取得しています。次に、このメソッドでは、エンティティが返されたこと、その number
の値が、以前作成したエンティティと同じであることを検証します。
最後に、unittest.main()
を呼び出します。
テストを実行するには、テストを実行するをご覧ください。
Cloud Datastore のテストを作成する
アプリで Cloud Datastore を使用する場合は、結果整合性に即してアプリケーションの動作を検証するテストを作成することをおすすめします。これは db.testbed
のオプションにより簡単になります。
PseudoRandomHRConsistencyPolicy
クラスでは、(祖先クエリでない)各グローバル クエリの前に書き込みが適用される可能性を制御できます。確率を 0% に設定すると、データストア スタブに対し、最大限の結果整合性で動作するよう指示することになります。最大限の結果整合性とは、書き込みが commit されても常に適用に失敗することを意味します。そのため、グローバル(祖先でない)クエリには一貫して、変更が反映されません。これはもちろん、本番環境での実行時にアプリケーションに反映される結果整合性の程度を表すものではありませんが、テストのためにローカル データストアを毎回このように動作するよう構成できれば非常に便利です。可能性をゼロ以外に設定すると、テストが一貫した結果になるように、PseudoRandomHRConsistencyPolicy
によって整合性の決定論的順序が決まります。
テスト用 API は、アプリケーションが結果整合性に即して適切に動作することを検証するために有用ですが、ローカルの高レプリケーション読み取り整合性モデルは、本番環境の高レプリケーション読み取り整合性モデルの近似モデルであり、厳密なレプリカではないことに注意してください。ローカル環境では、適用されない書き込みがあるエンティティ グループに属する Entity
の get()
を実行すると常に、その適用されない書き込みの結果が、以降のグローバル クエリで認識されます。本番環境の場合、これは該当しません。
メールテストを作成する
メール サービス スタブを使用して、メール サービスをテストできます。testbed でサポートされている他のサービスと同様に、まずスタブを初期化してから、Mail API を使用するコードを呼び出し、正しいメッセージが送信されたかどうかを検証します。
タスクキューのテストを作成する
タスクキュー スタブを使用して、タスクキュー サービスを使用したテストを作成できます。testbed でサポートされている他のサービスと同様に、まずスタブを初期化してからタスクキュー API を使用するコードを呼び出し、最後にタスクがキューに正しく追加されたかどうかを検証します。
queue.yaml
構成ファイルの設定
デフォルト以外のキューと対話するコードに対してテストを実行するには、アプリケーションで使用する queue.yaml
ファイルを作成し、そのファイルを指定する必要があります。下記の例 queue.yaml
をご覧ください。
使用できる queue.yaml オプションの詳細については、タスクキューの構成をご覧ください。
queue.yaml
の場所は、スタブの初期化時に指定されます。
self.testbed.init_taskqueue_stub(root_path='.')
この例では queue.yaml
はテストと同じディレクトリにあります。別のフォルダ内にあるとしたら、そのパスを root_path
で指定する必要があります。
タスクをフィルタリングする
タスクキュー スタブの get_filtered_tasks
を使用すると、キューに入ったタスクをフィルタリングできます。これにより、複数のタスクをエンキューするコードを検証しなければならないテストを簡単に作成できます。
遅延タスクのテストを作成する
アプリケーション コードで遅延ライブラリを使用する場合は、taskqueue スタブを deferred
と一緒に使用すると、遅延関数がキューに入り、正しく実行されることを確認できます。
デフォルトの環境変数を変更する
App Engine サービスは多くの場合、環境変数に依存しています。testbed.Testbed
クラスの activate()
メソッドでは、環境変数にデフォルト値を使用しますが、テストの必要性に応じて、testbed.Testbed
クラスの setup_env
メソッドでカスタム値を設定できます。
たとえば、データストアに複数のエンティティを保管するテストを実行する場合を考えてみましょう。このテストで保管するエンティティは、すべて同一のアプリケーション ID に関連付けられているものです。次に、保管されたエンティティに関連付けられているものとは異なるアプリケーション ID を使用して、同じテストをもう一度実行するとします。それには、新しい値を app_id
として self.setup_env()
に渡します。
例:
ログインをシミュレーションする
別の一般的な setup_env
の使用例として、管理者権限を持つユーザー、または持たないユーザーのログインをシミュレーションし、いずれの場合もハンドラが適切に動作するかどうかを確認します。
これで、たとえばテストメソッドで self.loginUser('', '')
を呼び出して、どのユーザーもログインしていないこと、self.loginUser('test@example.com', '123')
を呼び出して、管理権限を持たないユーザーがログインしていること、そして self.loginUser('test@example.com',
'123', is_admin=True)
を呼び出して、管理者ユーザーがログインしていることをシミュレーションできるようになります。
テスト用フレームワークを設定する
SDK のテスト用ユーティリティは、特定のフレームワークとは関連付けられていません。利用可能な App Engine テストランナー(nose-gae、ferrisnose など)をどれでも使用して、単体テストを実行できます。また、シンプルなテストランナーを独自に作成したり、下記のテストランナーを使用したりすることも可能です。
次のスクリプトでは Python の unittest モジュールを使用します。
スクリプト名は自由に設定できます。スクリプトを実行する際は、Google Cloud CLI または Google App Engine SDK のインストール パスと、テスト モジュールのパスを指定します。スクリプトは指定されたパスですべてのテストを検出し、標準的なエラー ストリームに結果を出力します。テストファイルの名前は、命名規則に従い test
で始まります。
テストを実行する
runner.py
スクリプトを実行するだけで、このようなテストを実行できます。このスクリプトの詳細については、テスト用フレームワークを設定するをご覧ください。
python runner.py <path-to-appengine-or-gcloud-SDK> .