👉
Behaveでの非同期処理のやり方
このエントリでは、Behaveの非同期処理を試したメモを残します。
Behaveの非同期処理については、以下で紹介されています。
Testing asyncio Frameworks | Noteworthy in Version 1.2.6 | behave
非同期処理には、以下の2種類があります。
- Async-steps ... ステップ内で非同期処理の開始~完了待ちを行う
- Async-dispatch and async-collect ... ステップ間を跨がり、非同期処理の開始~完了待ちを行う
Async-steps ... ステップ内で非同期処理の開始~完了待ち
1つ目の非同期処理は、ステップ内での非同期処理の開始~完了待ちです。
これは単に、ステップ関数内でasync/awaitを使えるようになるという話です。
以下にコード例を示します。
# -- FILE: sample.feature
Feature: sample feature
Scenario: sample scenario
Given sample step
# -- FILE: steps/sample.py
from behave import *
from behave.api.async_step import async_run_until_complete
import asyncio
@step('sample step')
@async_run_until_complete
async def step_impl(context):
tasks = []
for _ in range(10):
tasks.append(asyncio.create_task(asyncio.sleep(5)))
await asyncio.gather(*tasks)
実行結果は次の通りです。
5秒待ち合わせを10タスク実施しているのに、5秒程度でテストが完了している事に注目。
$ behave sample.feature
Feature: sample feature # sample.feature:2
Scenario: sample scenario # sample.feature:3
Given sample step # steps/sample.py:6 5.006s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
1 step passed, 0 failed, 0 skipped, 0 undefined
Took 0m5.006s
Async-dispatch and async-collect ... ステップ間を跨がり、非同期処理の開始~完了待ち
2つ目は、ステップ間を跨がるケースです。
接続システムへの初期化やデータ登録などを行うGivenがずらっと並んでいるシナリオの場合など、
上手く使えば、テスト実行の所要時間短縮に役立てられるかと思います。
以下にコード例を示します。
# -- FILE: sample.feature
Feature: sample feature
Scenario: sample scenario
When run async process
And run async process
And run async process
Then await async process
# -- FILE: steps/sample.py
from behave import *
from behave.api.async_step import async_run_until_complete
from behave.api.async_step import use_or_create_async_context
import asyncio
@step('run async process')
def step_impl(context):
async_context = use_or_create_async_context(context, "async_context")
async_context.tasks.append(async_context.loop.create_task(asyncio.sleep(5)))
@step('await async process')
@async_run_until_complete
async def step_impl(context):
await asyncio.gather(*context.async_context.tasks)
実行結果は次の通りです。
run async processは0秒で完了し、await async processで5秒かかる事に注目。
$ behave sample.feature
Feature: sample feature # sample.feature:2
Scenario: sample scenario # sample.feature:3
When run async process # steps/sample.py:7 0.000s
And run async process # steps/sample.py:7 0.000s
And run async process # steps/sample.py:7 0.000s
Then await async process # steps/sample.py:12 5.005s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
4 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m5.006s
各非同期処理の結果(成功/失敗)も考慮
実際のテストシナリオでは、
Thenの待ち合わせの箇所で、各非同期処理が成功したか否かを判断する事になると思うので、
次のようなコードになると思います。
# -- FILE: sample.feature
Feature: sample feature
Scenario: sample scenario
When run async process ok
And run async process ng
Then await async process
# -- FILE: steps/sample.py
from behave import *
from behave.api.async_step import async_run_until_complete
from behave.api.async_step import use_or_create_async_context
import asyncio
@step('run async process ok')
def step_impl(context):
async def async_ok():
await asyncio.sleep(5)
return True
async_context = use_or_create_async_context(context, "async_context")
async_context.tasks.append(async_context.loop.create_task(async_ok(), name="task_ok"))
@step('run async process ng')
def step_impl(context):
async def async_ng():
await asyncio.sleep(5)
return False
async_context = use_or_create_async_context(context, "async_context")
async_context.tasks.append(async_context.loop.create_task(async_ng(), name="task_ng"))
@step('await async process')
@async_run_until_complete
async def step_impl(context):
await asyncio.gather(*context.async_context.tasks)
for e in context.async_context.tasks:
assert e.result(), f'{e.get_name()} failed'
実行結果は次の通りで、task_ngの方が失敗したことがわかります。
$ behave sample.feature
Feature: sample feature # sample.feature:2
Scenario: sample scenario # sample.feature:3
When run async process ok # steps/sample.py:7 0.000s
And run async process ng # steps/sample.py:15 0.000s
Then await async process # steps/sample.py:23 5.006s
Assertion Failed: task_ng failed
Failing scenarios:
sample.feature:3 sample scenario
0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 0 skipped
2 steps passed, 1 failed, 0 skipped, 0 undefined
Took 0m5.006s
以上。
Discussion