- TDD とは、最初に失敗するテストを作成し、次に合格するのに十分なコードを作成してから整理することを意味します。
- BDD は、通常、Given、When、Then などの共有言語で動作を説明します。
- TDD は主にコード設計を指導します。 BDD は主に共通理解を構築します。
- これらはライバルではなく、多くのチームは機能には BDD を使用し、その下の詳細には TDD を使用します。
TDD 対 BDD の議論は、どちらの側を選択するかのように組み立てられることがよくあります。実際には、彼らはさまざまな質問に答えます。テスト駆動開発では、コードが開発者が意図したとおりに動作するかどうかが問われますが、動作駆動開発では、ソフトウェアが何をすべきかについてチームが同意しているかどうかが問われます。
違いを具体的にするために、各アプローチで小さな割引機能を 1 つ構築します。標準ライブラリを使用して Python 3.13 でコードを実行しました。以下の結果はその実行結果です。 BDD 部分ではメカニズムを示すために独自の小さなランナーを使用するため、実際のプロジェクトでは代わりに Cucumber などのツールを使用するか、behave を使用します。
短い答え
テスト駆動開発 (TDD) はコーディング手法です。失敗する小さなテストを作成し、最も単純なコードで合格させてから、設計を改善します。テストはコードで記述され、主に開発者によって作成されます。ケント・ベックはこの習慣を広めました。
行動駆動開発 (BDD) は、TDD から発展したコラボレーションの実践であり、Dan North がこの用語を導入しました。開発者、テスター、ビジネス関係者は、システムがどのように動作するべきかの例を平易な言葉で記述し、合意することで、それらの例を自動化できます。
TDD と BDD を並べて表示
| アスペクト | TDD | BDD |
|---|---|---|
| 主な焦点 | 正しく、適切に設計されたコード | 行動に対する共通理解 |
| で書かれています | プログラミング言語 | 平易な言語 (Gerkin など) |
| 作者 | 開発者 | ビジネス、QA、開発者が一体となって |
| 読み取り可能者 | 開発者 | チームのみんな |
| 典型的なレベル | ユニット | 特徴または受け入れ |
| 出発点 | 失敗したテスト | 例を交えた会話 |
| サイクル | 赤、緑、リファクタリング | 発見、定式化、自動化 |
| 共通ツール | pytest、JUnit、Jest | Cucumber、behave、SpecFlow、Reqnroll |
特徴
私たちが実装するビジネス ルールは次のとおりです。 1000 個以上の注文は 10% オフになります。メンバーはこれらの注文からさらに 5% オフになります。
それは明白に聞こえるかもしれませんが、すでに曖昧です。 500 を購入したメンバーは 5% オフになりますか?文には「それらの命令」と書かれているので、そうではないものと仮定します。これはまさに、BDD が早期に明らかにするように設計された種類の質問であるため、この点に留意してください。
TDD で構築する
TDD は、red、green、refactor として知られる短いループに従います。
| ステップ | あなたがすること |
|---|---|
| 赤 | まだ存在しない動作に対するテストを 1 つ作成し、それが失敗するのを観察します |
| 緑 | 合格する最も単純なコードを書く |
| リファクタリング | テストをグリーンに保ちながらコードを改善する |
これらはルールごとに 1 つずつのテストです。
class DiscountTests(unittest.TestCase):
def test_no_discount_below_1000(self):
self.assertEqual(discount(999, False), 0)
def test_ten_percent_from_1000(self):
self.assertEqual(discount(1000, False), 100)
def test_member_gets_extra_five_percent(self):
self.assertEqual(discount(1000, True), 150)
次に、コードを 3 段階で記述し、それぞれの段階でテストを実行しました。厳密な TDD セッションでは一度に 1 つのテストが追加され、これは同じ進行を示しています。
ステージ 1: 最も単純なこと
def discount(total, is_member):
return 0
# 3 tests, 2 failed
# test_member_gets_extra_five_percent
# test_ten_percent_from_1000
ゼロを返すのはばかげているように見えますが、これはテストが実際に失敗する可能性があり、合格した 1 つのテストが「割引なし」ルールであることを証明しています。
ステージ 2: しきい値を追加する
def discount(total, is_member):
if total >= 1000:
return total * 0.1
return 0
# 3 tests, 1 failed
# test_member_gets_extra_five_percent
ステージ 3: メンバーの追加とクリーンアップ
def discount(total, is_member):
rate = 0.1 if total >= 1000 else 0
if is_member:
rate += 0.05
return round(total * rate, 2)
# 3 tests, 0 failed
3人とも合格。ただし、このバージョンをよく読んでください。 500 を購入したメンバーには 5% の割引が与えられますが、これは私たちが想定していたルールの意味とは異なります。このケースをカバーしたテストはなかったため、何もフラグはありませんでした。 TDD は、テストしようと考えた動作のみを保護します。これが実際の制限であり、例についての会話が非常に重要である理由です。
なぜサイクルが重要なのか
- 赤はテストが機能することを証明します。一度も失敗したことのないテストは、何もテストしていない可能性があります。
- 緑は集中力を維持します。必要なコードのみを作成できるため、オーバーエンジニアリングを防ぐことができます。
- リファクタリングにより品質を高く保ちます。 テストにより、安全に設計を変更する自信が得られます。
- 設計に影響します。 テストが難しいコードは多くの場合、結合が強すぎるため、すぐに気づきます。
pytest チュートリアルでは、このようなテストを作成するためのより便利な方法を示し、境界値分析では、値として 999 と 1000 を選択した理由を説明しています。
BDD で構築する
BDD はツールではなく会話から始まります。多くの場合、それは スリー アミーゴ として知られる短い会議であり、製品担当者、開発者、テスターが具体的な例について話し合います。
Tester: What about a member spending 500?
Product: Good question. No discount. It has to reach 1000 first.
Dev: And exactly 1000 counts?
Product: Yes, 1000 or more.
この 1 回のやり取りで、TDD コードの問題が発見されたでしょう。次に、Gherkin と呼ばれる構造化フォーマットで例を記録します。
Feature: Order discounts
Scenario: Member reaches the discount threshold
Given a member with a cart worth 1000
When they view the amount to pay
Then the amount to pay is 850
Scenario: Member below the threshold gets no discount
Given a member with a cart worth 500
When they view the amount to pay
Then the amount to pay is 500
| キーワード | 目的 |
|---|---|
| 与えられた | スタートの状況 |
| いつ | アクションやイベントは |
| それから | 期待される結果 |
チームの誰もがそれを読んで、「はい、それは正しい」または「いいえ、それは間違っています」と言うことができます。たとえ自動化しないとしても、シナリオはすでに有用な作業を行っています。
ステップをコードに接続する
ツールは、平易な言語の各行をステップ定義と呼ばれる関数に接続します。このアイデアを示すために、私たちは自分たちで小さなバージョンを作成し、最初のシナリオを実行しました。
@step("a member with a cart worth 1000")
def given(ctx):
ctx["cart"] = Cart(is_member=True)
ctx["cart"].add(1000)
@step("they view the amount to pay")
def when(ctx):
ctx["amount"] = ctx["cart"].payable()
@step("the amount to pay is 850")
def then(ctx):
assert ctx["amount"] == 850
# ok Given a member with a cart worth 1000
# ok When they view the amount to pay
# ok Then the amount to pay is 850
実際のツールではパラメーターが追加されるため、1 つのステップで「1000」と「500」の両方に一致し、さらにレポートが作成されます。その結果、生きたドキュメントが完成しました。これは、真実でなくなった場合に大声で失敗するシステムの平易な説明です。
ヒント: BDD の価値は会話にあります。コードの完成後に 1 人の担当者が単独で Gherkin を作成するチームは、ツールのコストだけを受け取りますが、メリットはほとんどありません。
良いシナリオを書く
| 弱い | より良い |
|---|---|
ID「btn-77」のボタンをクリックすると |
クーポンを適用すると |
そうすると結果は正しいです |
その場合、支払う金額は850です |
| 1 つのシナリオに 20 のステップが含まれる | シナリオごとに 1 つの動作 |
| 画面の説明をします | ビジネス上の意図を説明する |
クリックとフィールド ID を説明するシナリオは再設計のたびに壊れ、ビジネス読者はシナリオを読むのをやめてしまいます。ユーザー インターフェイスの詳細をステップ コードに保持します。これらのシナリオは、テスト ケースとテスト シナリオの例に近いもので、ユーザー受け入れテストでもよく使用されます。
両方を一緒に使用する
この 2 つは、「アウトサイドイン」ワークフローに自然に適合します。
- チームは動作について合意し、BDD シナリオを作成します。まだ何も存在しないため、失敗します。
- 開発者は TDD を使用して、必要な小さなユニットを構築します。
- 単体テストは合格し、最終的にはシナリオも合格します。
- すべては CI/CD パイプライン で実行されます。
それぞれを適切なレベルに保ちます。重要な動作についてはいくつかのシナリオを用意し、詳細については迅速な単体テストを多数用意する必要があります。 単体テストと統合テストに関するガイドでは、バランスについて説明しています。
ツール
| 言語 | TDD (単体テスト) | BDD |
|---|---|---|
| パイソン | pytest、単体テスト | 行動してください、pytest-bdd |
| JavaScript | ジェスト、ヴィテスト | Cucumber.js |
| ジャワ | JUnit、TestNG | キュウリ-JVM |
| .NET | xUnit、NUnit | Reqnroll、SpecFlow |
それぞれをいつ使用するか
TDD は次の場合に適しています。
- 価格設定、解析、権限などの複雑なロジックを作成しているとします。
- 失敗したテストで最初にバグを再現できるため、バグを修正することになります。
- あなたは頻繁に変更されるコードに取り組んでいます。
BDD は次の場合に適切に適合します。
- ビジネスルールは複雑であり、誤解は高くつくものです。
- 技術者ではない人でもシナリオを読んで議論するでしょう。
- いくつかのチームは、何かがどのように動作するかについての共有定義を必要としています。
次のような場合には、どちらもやりすぎになる可能性があります。使い捨てのプロトタイプを作成している場合、または不明確な技術的アイデアを検討している場合。多くの開発者は、最初にスパイクを付けてから、テストで書き直します。エンジニアリング外部の誰もシナリオを検討しないのであれば、BDD ツールも投資としては不十分です。その場合、適切な名前を付けた単純なテストの方が簡単です。
よくある間違い
- BDD をテスト ツールとして扱う。 コラボレーションのない Cucumber は、単なる冗長なテスト作成方法です。
- TDD でのテスト実装。 プライベートな詳細に関連付けられたテストは、リファクタリングのたびに中断されます。
- リファクタリング手順をスキップします。 乱雑なコードの上でテストに合格します。
- コードの後にテストを記述し、それを TDD と呼びます。 これでも便利ですが、設計のフィードバックが失われます。
- UI シナリオを書きすぎると、 UI シナリオが遅くなり不安定になります。 不安定なテストを参照してください。
- カバレッジの数字を追う。 この例が示すように、優れたカバレッジでも、誰も考慮していない行動は依然として見逃されます。
まとめ
TDD は正しいものを構築するのに役立ち、BDD は正しいものを構築するのに役立ちます。私たちの割引の例では、両方の側面が示されました。コードはすべてのテストに合格しましたが、簡単な会話で間違った仮定が明らかになりました。次の小さな機能で試してみてください。コードを作成する前に、同僚と具体的な例を 3 つ書き、失敗するテストを一度に 1 つずつ作成します。
よくある質問
TDD と BDD の主な違いは何ですか?
TDD は、開発者が実装前に失敗するコード テストを作成することに重点を置いています。 BDD は、わかりやすい例を使用してチーム全体の行動に同意することに重点を置いています。
TDD と BDD を併用できますか?
はい、多くのチームがそうしています。 BDD シナリオは外部から機能を定義し、TDD はその下にある小さなコード単位を駆動します。
BDD は TDD よりも優れていますか?
解決する問題が異なるため、全体的にはどちらが優れているというわけではありません。 BDD は理解の共有に役立ち、TDD はコード設計と迅速なフィードバックに役立ちます。
ガーキンとは何ですか?
Gherkin は、Given、When、Then などのキーワードを使用してシナリオを作成するための構造化された平易な言語形式です。 Cucumber などのツールで実行できます。
TDD は開発を遅らせますか?
最初は、特に学習中は遅く感じることがあります。チームは多くの場合、バグを減らし、より安全な変更を行うことで時間を取り戻すことができますが、その効果はプロジェクトによって異なります。
学び続ける
テスト、WordPress、または SEO についてサポートが必要ですか?
私たちは、チームがより優れたソフトウェアを出荷し、サイトを成長させるのを支援します。あなたが取り組んでいることを教えてください。明確な次のステップを提案します。
詳しいガイドはこちら テスト、AI、最新の開発
この記事は気に入りましたか?トピックを選択して、最新のチュートリアルに直接アクセスしてください。
AI 幻覚の説明: 言語モデルが物事をでっち上げる理由とそれを軽減する方法
AI 幻覚について説明しました。幻覚とは何か、言語モデルが幻覚を生成する理由、一般的な種類、グラウンディングとプロンプトで幻覚を軽減する方法、そして私たちが実行した簡単なチェックです。
記事を読む →DevOps環境変数の説明: .env ファイル、シークレット、および設定が正しく行われている
実行した Python の例を使用して環境変数について説明します。環境変数の読み取りと設定、すべての値が文字列である理由、.env ファイル、プロセスの継承、シークレット、検証について説明します。
記事を読む →ソフトウェア開発API レート制限の説明: アルゴリズム、ヘッダー、および 429 エラーの処理
API レート制限については、実行したシミュレーションで説明します。トークン バケット、固定ウィンドウ、スライディング ウィンドウ、429 応答、レート制限ヘッダー、バックオフによる再試行、およびテスト制限です。
記事を読む →テスト自動化Python でのモッキング:unittest.mock、patch、およびテストを嘘にする間違い
Python でのunittest.mockによるモック、テスト済み: モックとreturn_value、side_effect、名前が使用されている場所のパッチ、仕様と自動仕様、日付のモック、およびよくある間違い。
記事を読む →ソフトウェアテストコードカバレッジの説明: 回線カバレッジとブランチカバレッジ、および 100% の実際の意味
実際のcoverage.pyの実行でコードカバレッジを説明: 行、分岐、条件のカバレッジ、レポートの読み取り、100%のカバレッジでもバグを見逃す可能性がある理由、および賢明なターゲット。
記事を読む →データベースとSQLSQL における DELETE、TRUNCATE、DROP: 違い、ロールバック、およびそれぞれをいつ使用するか
DELETE、TRUNCATE、DROP について、テスト済み SQL を使用して説明します。それぞれの削除内容、ロールバック、トリガー、ID リセット、速度、PostgreSQL、MySQL、SQL Server、SQLite の違いについて説明します。
記事を読む →データベースとSQLSQL ビューの説明: 作成、使用、落とし穴の回避、例を示します
SQL ビューについて、実行したクエリとともに説明しました。ビューの作成、ライブ結果、列の非表示、パフォーマンス、更新可能なビューと具体化されたビュー、壊れた依存関係、ベスト プラクティスについて説明します。
記事を読む →ソフトウェア開発TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト
TypeScript インターフェイスと型、実際のコンパイラでチェック: 両方でできること、片方だけでできること、宣言のマージ、拡張と交差、および従うべき簡単なルール。
記事を読む →ソフトウェア開発Python *args と **kwargs の説明: 柔軟な関数と明確な例
Python の *args と **kwargs について、実行したコードで説明しました。パッキングとアンパッキング、パラメーターの順序、キーワードのみの引数と位置のみの引数、デコレーター、一般的なエラーです。
記事を読む →ソフトウェア開発Python Lambda 関数: 構文、実際の用途、および使用すべきでない場合
Python のラムダ関数について、実行したコードとともに説明します。構文、キーによるソート、マップとフィルター、クロージャー、遅延バインディング トラップ、通常の def がより適切な選択である場合。
記事を読む →人工知能AIエージェントとは何ですか?シンプルなエージェント ループを実行できる仕組み
AIエージェントとは何ですか?エージェント ループ、ツール、メモリ、ガードレール、実行した小さな Python ループ、実際のリスク、およびシンプルなチャットボットがより良い選択である場合についてのわかりやすいガイドです。
記事を読む →DevOpsモニタリングと可観測性: ログ、メトリクス、トレースによる本当の違い
モニタリングと可観測性について説明: 既知および未知の障害、ログ、メトリクスとトレース、チェックアウト インシデントのウォークスルー、ゴールデン シグナル、OpenTelemetry、および便利なアラート。
記事を読む →DevOpsDocker ボリュームとバインド マウント: コンテナ データを正しい方法で保持する
Docker ボリュームとバインド マウントについて説明します。コンテナ データが消える理由、名前付きボリューム、バインド マウント、tmpfs、Compose の例、バックアップ、アクセス許可、およびそれぞれをいつ使用するかについて説明します。
記事を読む →テスト自動化TDD と BDD: 主な違い、1 つの機能が両方の方法で構築され、それぞれをいつ使用するか
TDD と BDD は、両方の方法で構築された 1 つの割引機能で説明されています。実行したコードによるレッド-グリーン-リファクタリング、Given-When-Then シナリオ、ツール、トレードオフ、およびそれぞれをいつ使用するかです。
記事を読む →ソフトウェア開発Git mergeとrebaseの違い:例と使い分け
Git mergeとrebaseの違いを、実際のログ出力、比較表、rebaseの黄金律、安全なチームワークフローで解説します。
記事を読む →ソフトウェア開発Pythonのリスト内包表記:構文・例・使うべきでない場面
Pythonのリスト内包表記を解説:構文、フィルタ、if-else、ネストしたループ、実測した処理時間、そして通常のforループの方が良い場面。
記事を読む →データベースとSQLSQL JOINを例で解説(Inner・Left・Right・Full)
サンプルのテーブルとクエリでSQL JOINを順に解説。INNER、LEFT、RIGHT、FULL、CROSS、SELFに加え、よくある間違いとパフォーマンスのコツも紹介します。
記事を読む →