テスト駆動型開発
Qmonus SDKでは、テスト駆動型開発を行うためのデバッグ機能やプラグインのユニットテストをサポートする機能群を提供しています。
デバッグソリューション
Scenario
やATOM
のランタイムは、Frontalが提供する様々なモニタリング機能で追跡できます。しかしながらプラグインのコードレベルで問題箇所を特定するには、不十分です。Qmonus SDKでは、より、コードレベルでの問題発見を効率化するためにプラグインのランタイムコードトレース機能とプロファイリング機能を提供しています。
ランタイムコードトレーシング
ランタイムコードトレースは、REPLが提供するtraceon
traceoff
組込みコマンドで簡単に有効化、無効化できます。
traceon
コマンドは、プラグインの実行時に発行されるSQL文をトレースすることもできます。コマンドの詳細はDocs » リファレンス » REPL
を参照してください。
コードプロファイリング
コードプロファイリングは、REPLが提供するprofon
profoff
組込みコマンドで簡単に有効化、無効化できます。
コマンドの詳細はDocs » リファレンス » REPL
を参照してください。
コードカバレッジの計測
コードカバレッジ測定モードでテストを実行することにより、プラグインのコード走行率をリアルタイムで確認することができます。テストカバレッジの参考にしてください。カバレッジ計測モードの有効化、無効化は、Frontal画面上で行うか、coverage
組込みコマンドを使用する必要があります。
コマンドの詳細はDocs » リファレンス » REPL
を参照してください。
Note
ランタイムトレースモード、ランタイムプロファイリングモード、およびランタイムカバレッジモードは、排他的にのみ使用できます。
ドライランソリューション
外部サービスや他のプラグインへの通信や永続化などの入出力を伴うScenario
やATOM
などのプラグイン開発では、接続先のサービスのインタフェース動作を模擬するモックが必要になります。テスト駆動開発では、様々な入出力の応答パターンをテストしながら実装を進めていかなければなりません。このようなプラグイン開発者が意図した入出力を模擬するため、Faker
とIllusion
サービスが提供されています。
入出力の模擬パターンをFaker
サービスで定義し、プラグインのランタイムに適用する模擬パターンの組み合わせ(テストシーン)をIllusion
サービスで定義できます。Illusion
を指定してプラグインを実行することでプラグインが受信する外部サービスの応答を疑似させ、意図した動作が実装できているかを検査することが可能になります。
Faker
Faker
は疑似動作の定義セットです。以下の例に示す通り、タグ付けされたフェイク動作関数のリストを保有します。タグは後述するIllusion
にフェイク動作を割り当てる際のキーとなるため、同一Fakerの保持するフェイク関数の中でユニークでなければなりません。
category: example
name: exampleFaker
fakes:
normal:
script: |-
async def Success(*args, **kwargs):
return FakeHttpResponse(body={"status": True})
abnormal:
script: |-
async def BadRequest(*args, **kwargs):
return FakeHttpResponse(400, body={"status": False})
Tip
組込みオブジェクトFakeHttpResponse
は、HTTP疑似応答を作成するのに役立ちます。使用方法についてはDocs » リファレンス » ビルトインオブジェクト
を参照してください。
また、Faker
のフェイク関数は、Faker
組込みオブジェクトを使用してREPL上で動作確認することができます。
複雑な処理を行うフェイク関数をデバッグする場合、REPLの組込みコマンドであるtraceon
でコードトレーシングを有効にすることで解析効率を向上させることができます。
>>> f = await Faker.load("exampleFaker")↵
... r = await f.Success()↵
... print(r)↵
... ↵
{
"code": 200,
"headers": {
"Content-Type": "application/json"
},
"error": null,
"body": "{\"status\":true}"
}
↵
>>> r = await f.BadRequest()↵
... print(r)↵
... ↵
{
"code": 400,
"headers": {
"Content-Type": "application/json"
},
"error": true,
"body": "{\"status\":false}"
}
↵
>>>
Illusion
Illusion
は、フェイク関数の集合概念です。Scenario
やATOM
などのプラグインが動作するテストシーンにおいてプラグインの周辺サービスがどのような応答を返すかを定義しているイメージです。
Scenario
をIllusion
空間で実行するには、拡張ヘッダーを指定する必要があります。X-Xaas-Illusion
拡張ヘッダーでIllusion
名を指定します。
尚、REPLでは、REPLの実行空間にIllusion
を適用できます。Docs » リファレンス » REPL
のillusion
コマンドを参照してください。
category: example
name: normalScene
fakers:
exampleFaker: Success
category: example
name: subnormalScene
fakers:
exampleFaker: BadRequest
Fakerの使用方法
Faker
を使用するには、Scenario
やATOM
のカスタムスクリプトにfaker
宣言を記述する必要があります。
faker
宣言は、引数としてFaker
の名前を与える必要があります。faker
宣言を埋め込まれたコードは、指定されたFakerのフェイク関数を集約しているIllusion
適用空間においてのみフェイク関数に置換されます。置換されるコードは、faker
宣言の直後に記述されている関数呼び出しコードです。
Note
プラグインコードに記述されているfaker
宣言によるコードスワップは、Illusion
適用時にしか行われません。
例えば、次のHTTP通信を実行するスクリプトがあります。
r = await callout(url="https://www.google.com/")
if r.error:
raise Error(r.code, reason="Unable to access")
faker
宣言は、フェイク動作を適用したい入出力処理の直前に記述します。faker
は単一の引数としてFaker
の名前を指定する必要があります。
faker("exampleFaker")
r = await callout(url="https://www.google.com/")
if r.error:
raise Error(r.code, reason="Unable to access")
Faker
は、X-Xaas-Illusion
拡張ヘッダーで Illusion
名を指定することで動作します。
>>> r = await callout(path="/example1", headers={"X-Xaas-Illusion": "normalScene"})↵
... print(r.body)↵
... ↵
b'{"status":true}'
↵
>>> r = await callout(path="/example1", headers={"X-Xaas-Illusion": "subnormalScene"})↵
... print(r.body)↵
... ↵
b'{"errorCode":400,"errorMessage":"Unable to access","moreInfo":null}'
↵
>>>
テストフレームワーク
Qmonus SDKのテストフレームワークは、次の図に示す構造になっています。
上記のFaker
は、入出力のバリエーションを表現したモデルであり、テストの観点に相当します。Illusion
は、テスト実行時の外部環境の動作をモデル化したもので、テストシーンに相当します。
Testcase
は、テスト本体であり、1つのテストシーンに関連しています。Testsuite
は、テストの集合概念です。
Warning
現在、Testsuite
は、他のTestsuiteをコンポジットできるモデルで実装されていますが、管理が複雑化するため、Testsuiteのコンポジット定義は非推奨です。
Testcase
の実行ワークフローは以下の通りです。
サンプルテストケース
テストケースの名前空間には、Response
、Transaction
、およびTestcase
オブジェクトが含まれており、これらは各assert
関数によるチェックに使用できます。
category: example
name: exampleAbnormalDeleteFailed
target: example
illusion: exampleDeleteFailed
input:
body: |-
def randomBody():
import rstr
return dict(name=rstr.xeger("^[A-Z][a-zA-Z_0-9-]+$"), region=rstr.rstr(["jp1", "jp2"], 1))
headers:
Content-Type: application/json
method: POST
path: /examples
assertion:
output: |-
async def assertion():
assert Response.code==202, "Invalid response code %r" % Response.code
assert Response.body, "Empty body %r" % Response.body
assert "hogeID" in json.loads(Response.body), "Invalid response body %s" % Response.body
progress:
- index: 1
script: |-
async def assertion():
assert Transaction.xglobals.name and Transaction.xglobals.region, "name or region is None"
assert Transaction.xglobals.hogeID is not None, "hogeID is None"
end: |-
async def assertion():
assert Transaction.status=="Aborted", "Transaction is in an unexpected state %r" % Transaction.status
cleanup: |-
async def cleanup():
await Transaction.cancel(force=True)
assert Transaction.status=="ForceCancelled", "Force cancel failed %r" % Transaction.status
Learn More
Qmonus SDKでのTDDを理解するには、以下のムービーが参考になります。
TDD demonstration movie
さらにTDDについて詳しく知りたい場合は、Docs » チュートリアル » Practical Test-Driven Development
で実際に開発を体験することができます。