BDD(Behavior-Driven Development、振る舞い駆動開発) は「システムが利用者や業務の観点でどのように振る舞うべきか」を具体例として定義しながら開発する手法。
TDDが「コード単位のテスト」から設計を進める傾向が強いのに対し、BDDは「利用者から見た振る舞い」を中心に考える。
SpecBDD¶
SpecBDDは、低レベルの実装ユニットとその間の通信に関する仕様を開発することに重点を置いている。
低レベルなテスト、いわゆる単体テストで用いられる。
SpecBDDフレームワークの例:RSpec
# in spec/calculator_spec.rb
RSpec.describe Calculator do
describe '#add' do
it 'returns the sum of its arguments' do
expect(Calculator.new.add(1, 2)).to eq(3)
end
end
endStoryBDD¶
StoryBDDは、アプリケーションの機能に関する、人間が読みやすいビジネス向け動作記述を作成する手法。DDDと機能テストを融合させた進化形。
StoryBDDでは、ステークホルダー向けのシナリオを作成する。これらのシナリオはテストではなく、ビジネス(ステークホルダー)にとって重要な機能以外を対象とするものではない。
シナリオはテストではないが、StoryBDD用のフレームワークを使うことで受け入れフェーズのテストを自動化できる。
StoryBDDのフレームワークの例¶
Gherkin形式の Given / When / Then で振る舞いを記述し、テストコードと対応付けて実行するBDDツール。
JavaScript、Python、Rubyなど複数言語に対応しており、PythonのBehaveやPytest
典型的には、次の形式(Gherkin記法)で仕様を書く。
Feature: ユーザーのログイン
Scenario: 正しい認証情報でログインできる
Given ログイン画面を開いている
When 正しいメールアドレスとパスワードを入力する
Then マイページが表示されるStoryBDDでは、上記のような低レベルの記述から始めることができますが、最終的な目的は「コードをテストする」ことではなく、「ビジネスのドメイン領域を記述する」ことであるべきです。
BDDのツール¶
代表的なツール
Cucumber¶
Gherkin形式の Given / When / Then で振る舞いを記述し、テストコードと対応付けて実行するBDDツール。
JavaScript、Python、Rubyなど複数言語に対応しており、PythonのBehaveやPytest
Behave(Python)¶
pythonのBDDパッケージ
# -- FILE: features/example.feature
Feature: Showing off behave
Scenario: Run a simple test
Given we have behave installed
When we implement 5 tests
Then behave will test them for us!# -- FILE: features/steps/example_steps.py
from behave import given, when, then, step
@given('we have behave installed')
def step_impl(context):
pass
@when('we implement {number:d} tests')
def step_impl(context, number): # -- NOTE: number is converted into integer
assert number > 1 or number == 0
context.tests_count = number
@then('behave will test them for us!')
def step_impl(context):
assert context.failed is False
assert context.tests_count >= 0Pytest-BDD(Python)¶
# content of publish_article.feature
Feature: Blog
A site where you can publish your articles.
Scenario: Publishing the article
Given I'm an author user
And I have an article
When I go to the article page
And I press the publish button
Then I should not see the error message
And the article should be published# content of test_publish_article.py
from pytest_bdd import scenario, given, when, then
@scenario('publish_article.feature', 'Publishing the article')
def test_publish():
pass
@given("I'm an author user")
def author_user(auth, author):
auth['user'] = author.user
@given("I have an article", target_fixture="article")
def article(author):
return create_test_article(author=author)
@when("I go to the article page")
def go_to_article(article, browser):
browser.visit(urljoin(browser.url, '/manage/articles/{0}/'.format(article.id)))
@when("I press the publish button")
def publish_article(browser):
browser.find_by_css('button[name=publish]').first.click()
@then("I should not see the error message")
def no_error_message(browser):
with pytest.raises(ElementDoesNotExist):
browser.find_by_css('.message.error').first
@then("the article should be published")
def article_is_published(article):
article.refresh() # Refresh the object in the SQLAlchemy session
assert article.is_publishedRSpec(Ruby)¶
RSpecはRubyのテストフレームワーク。Gherkin記法は使わないがBDDを謳っている
Behaviour Driven Development for Ruby.
Making TDD Productive and Fun.
https://rspec.info/
RSpec.describe 'Post' do #
context 'before publication' do # (almost) plain English
it 'cannot have comments' do #
expect { Post.create.comments.create! }.to raise_error(ActiveRecord::RecordInvalid) # test code
end
end
end