BDD(振る舞い型駆動開発)ツールであるbehaveを使ってサーバテストはできないものか
環境
環境については、以下の通り。
1
2
3
4
5
|
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
|
今回の手順
利用方法を見ていくにあたって、今回は以下の手順に沿って行う。
- behaveをインストールする
- 振る舞いを記述する
- behaveを実行すると、テストの実体を実装するように指示される
- テストを実装する
- behaveを実行すると、テストに失敗する
- 意図した振る舞いをするサーバー設定を行う
- behaveを実行すると、テストに成功する
- 終わり
長い。
でもその後もちょっと続くぞい。
behaveのインストール
behaveのインストール自体は簡単。
1
2
|
$ sudo apt install -y python-pip
$ pip install behave
|
C拡張等も無いので追加権限も必要ない。必要になるのはテストコード次第なので、テストコード実装の段階になってから。
振る舞い features/*.feature
を記述する
振る舞いの記述には、 features ファイルを記述する必要があります。
まずは作ってみよう。動作の説明は下記の example1.feature に書いてある通り。
1
2
3
4
5
6
7
8
9
10
11
|
$ mkdir /tmp/example1
$ cd /tmp/example1
$ mkdir features
$ mkdir features/steps
$ vi features/example1.feature
Feature: Check server services
Scenario: Check services
Given 127.0.0.1でnginxが起動している場合
When TCPポート80にアクセスしたら
Then TCPセッションが確立できること
|
behaveを実行すると、テストの実体を実装するように指示される
では振る舞いを定義したので、behaveを実行してみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
$ behave
Feature: Check server services # features/example1.feature:1
Scenario: Check services # features/example1.feature:3
Given 127.0.0.1でnginxが起動している場合 # None
When TCPポート80にアクセスしたら # None
Then TCPセッションが確立できること # None
Failing scenarios:
features/example1.feature:3 Check services
0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 0 skipped
0 steps passed, 0 failed, 0 skipped, 3 undefined
Took 0m0.000s
You can implement step definitions for undefined steps with these snippets:
@given(u'127.0.0.1でnginxが起動している場合')
def step_impl(context):
raise NotImplementedError(u'STEP: Given 127.0.0.1でnginxが起動している場合')
@when(u'TCPポート80にアクセスしたら')
def step_impl(context):
raise NotImplementedError(u'STEP: When TCPポート80にアクセスしたら')
@then(u'TCPセッションが確立できること')
def step_impl(context):
raise NotImplementedError(u'STEP: Then TCPセッションが確立できること')
|
振る舞いを検証するためのテストコードが足りないと言われるので、これをコピペしてテストコードを実装していこう。
テスト features/steps/*.py
を実装する
以下のようにテストを実装してみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
$ vi features/steps/example1_test.py
# -*- coding: UTF-8 -*-
from behave import *
import socket
@given(u'127.0.0.1でnginxが起動している場合')
def step_impl(context):
pass
@when(u'TCPポート80にアクセスしたら')
def step_impl(context):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 80))
context.connection = True
@then(u'TCPセッションが確立できること')
def step_impl(context):
assert context.connection
|
- ちょっと説明端折ってますが context にデータ突っ込んでおくとそれは引き継げます。
behaveを実行すると、テストに失敗する
そしてbehaveを実行すると、テストに失敗します。(成功してしまった君は既にWebサーバが動いているから、ある意味正しい)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
$ behave
Feature: Check server services # features/example1.feature:1
Scenario: Check services # features/example1.feature:3
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:5 0.001s
When TCPポート80にアクセスしたら # features/steps/example1_test.py:9 0.002s
Traceback (most recent call last):
File "/tmp/example1/local/lib/python2.7/site-packages/behave/model.py", line 1456, in run
match.run(runner.context)
File "/tmp/example1/local/lib/python2.7/site-packages/behave/model.py", line 1903, in run
self.func(context, *args, **kwargs)
File "features/steps/example1_test.py", line 12, in step_impl
client.connect(("127.0.0.1", 80))
File "/usr/lib/python2.7/socket.py", line 228, in meth
return getattr(self._sock,name)(*args)
error: [Errno 111] Connection refused
Then TCPセッションが確立できること # None
Failing scenarios:
features/example1.feature:3 Check services
0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 0 skipped
1 step passed, 1 failed, 1 skipped, 0 undefined
Took 0m0.003s
|
ちなみに、途中でテストに失敗すると残りのテストはスキップされます。
意図した振る舞いをするサーバー設定を行う
簡単のため、nginxを入れるだけです。
1
|
$ sudo apt install -y nginx
|
behaveを実行すると、テストに成功する
ではもう一度behaveを実行します。
1
2
3
4
5
6
7
8
9
10
11
12
|
$ behave
Feature: Check server services # features/example1.feature:1
Scenario: Check services # features/example1.feature:3
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:4 0.000s
When TCPポート80にアクセスしたら # features/steps/example1_test.py:7 0.001s
Then TCPセッションが確立できること # features/steps/example1_test.py:12 0.000s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.001s
|
小休憩
まずは動くところまで確認できました。
いったん休憩したら、もう少し慣れてみましょう。
もう少し慣れてみる
この辺の記法 http://pythonhosted.org/behave/gherkin.html を見ながら、もう少し慣れてみましょう。
簡単な変数化
シナリオファイル( *.feature
)からは、部分的に変数化することができるので、以下のように書くことができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
$ vi features/steps/example1_test.py
# -*- coding: UTF-8 -*-
from behave import *
import socket
@given(u'{host}でnginxが起動している場合')
def step_impl(context, host):
context.host = host
@when(u'TCPポート{port:n}にアクセスしたら')
def step_impl(context, port):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((context.host, port))
context.connection = True
@then(u'TCPセッションが確立できること')
def step_impl(context):
assert context.connection
|
シナリオファイル( *.feature
)には一切手を付けないまま、もう一度実行します。
1
2
3
4
5
6
7
8
9
10
11
12
|
$ behave
Feature: Check server services # features/example1.feature:1
Scenario: Check services # features/example1.feature:3
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:4 0.001s
When TCPポート80にアクセスしたら # features/steps/example1_test.py:7 0.005s
Then TCPセッションが確立できること # features/steps/example1_test.py:12 0.001s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.006s
|
問題なさそうですね。
次に、シナリオファイル( *.feature
)に新しいポート番号を追加してみましょう。
コピペするだけだから簡単だね。
1
2
3
4
5
6
7
8
9
10
11
12
|
$ vi features/example1.feature
Feature: Check server services
Scenario: Check services
Given 127.0.0.1でnginxが起動している場合
When TCPポート80にアクセスしたら
Then TCPセッションが確立できること
Scenario: Check services
Given 127.0.0.1でnginxが起動している場合
When TCPポート22にアクセスしたら
Then TCPセッションが確立できること
|
では実行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$ behave
Feature: Check server services # features/example1.feature:1
Scenario: Check services # features/example1.feature:3
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:4 0.001s
When TCPポート80にアクセスしたら # features/steps/example1_test.py:7 0.010s
Then TCPセッションが確立できること # features/steps/example1_test.py:12 0.000s
Scenario: Check services # features/example1.feature:8
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:4 0.000s
When TCPポート22にアクセスしたら # features/steps/example1_test.py:7 0.004s
Then TCPセッションが確立できること # features/steps/example1_test.py:12 0.000s
1 feature passed, 0 failed, 0 skipped
2 scenarios passed, 0 failed, 0 skipped
6 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.017s
|
後ろで動くスクリプトを柔軟に作っておけば、日本語でテストシナリオを書くだけでテストが増やせます。
プログラム書けない人でも、もとい、プログラムを書かない人たちにも、このシナリオファイルを見せながらなら、どんな試験をしているか説明し易いね。
テーブルを使った表現
でも、数が多くなってきたら冗長じゃない?
そこでテーブル表現ですよ。
今度は、裏で動いているpythonスクリプトはそのままに、シナリオファイル( *.feature
)だけを変更して対応してみよう。
1
2
3
4
5
6
7
8
9
10
11
12
|
$ vi features/example1.feature
Feature: Check server services
Scenario Outline: Check services
Given <host>でnginxが起動している場合
When TCPポート<port>にアクセスしたら
Then TCPセッションが確立できること
Examples: Services
| host | port |
| 127.0.0.1 | 80 |
| 127.0.0.1 | 22 |
|
そしてbehaveを実行する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$ behave
Feature: Check server services # features/example1.feature:1
Scenario Outline: Check services -- @1.1 Services # features/example1.feature:10
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:4 0.001s
When TCPポート80にアクセスしたら # features/steps/example1_test.py:7 0.005s
Then TCPセッションが確立できること # features/steps/example1_test.py:12 0.000s
Scenario Outline: Check services -- @1.2 Services # features/example1.feature:11
Given 127.0.0.1でnginxが起動している場合 # features/steps/example1_test.py:4 0.000s
When TCPポート22にアクセスしたら # features/steps/example1_test.py:7 0.005s
Then TCPセッションが確立できること # features/steps/example1_test.py:12 0.000s
1 feature passed, 0 failed, 0 skipped
2 scenarios passed, 0 failed, 0 skipped
6 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.012s
|
シナリオファイル( *.feature
)もスッキリ見やすくなった。
書き方の工夫は色々ありそうだけど、まずはこんなところ。
おしまい
behaveがどういうものが大体分かりましたね。
きっとこういったものはプログラムを書く人の興味範疇なのかなぁ、と思うのだけど、今回動かしてみた感じでは、例えば Infrataster のような立ち位置に近い気がしている。
個人的にはInfratasterよりbehaveの方が性に合う感じ。
テストコードを自分で書かなきゃいけないのは一緒なので、上手く誰かが作ったものを使いまわせる世界になると良いんだよね。
運用的にはnagiosプラグインとのラッパーなんかと組み合わせて、上手くログを集められたりすると抽象化された記述はこれ1本で行けそうなんだけど、やっぱりそう上手くいかないよねぇ。
と言う感じでしたとさ。おわり。