アジャイル トランスペアレンシー ~アジャイル開発における透明性の確保について~

第5回BDDとATDD

はじめに

本連載では、⁠透明性」というキーワードで、アジャイル開発について説明しています。前回は、テスト駆動開発(TDD)の持つ、開発を促進する設計作業としての側面にスポットを当てて説明しました。今回は、それらの考えを展開させてみたいと思います。具体的には、振る舞い駆動開発(BDD)と受け入れテスト駆動開発(ATDD)という二つのトピックを、ご紹介します。

テストのレビュー

前回、テスト駆動開発におけるテストを、設計作業であるとする考え方をご紹介してきました。これは、個々のクラスのインターフェース(=振る舞い)を、テストにより明確にしてゆくというものです。結果的に、作られたテストは、実装との乖離を自動的に検出できる設計書となります。

仕様変更やリファクタリングに伴うソフトウェアの改変に際して、バグの混入を自動的に検知できるので、実装と設計の同期が取れるという大きなメリットがあります。

しかし、当然デメリットはあります。第一に、テストの可読性の問題です。テストはコードになりますので、従来の設計書と比べると、読み易さやフォーマットの柔軟性という面で劣ります。また実際にテストを書く開発者の意識の問題もあります。テスト駆動開発というと、どうしても品質保証に必要なテストを想定してしまい、設計をわかりやすく表現しようという工夫はおざなりにされがちです。

第二に、テストのスコープの問題です。実際に記述されるクラスの振る舞いは、粒度が実装に寄りすぎていて仕様との整合性を確認しにくいといった側面があります。

アジャイル開発で設計情報の共有が上手くいかず、結局、従来型の重厚なドキュメント作成作業に従事することになる原因は、これらの問題点にあることが多いようです。アジャイル開発を進める上では、これらのデメリットを理解して上手に設計情報の共有を図る為の工夫が必要です。

さまざまな試みがなされていますが、今回は、テストの可読性の問題を解消する試みとして、振る舞い駆動開発(BDD) をご紹介します。そして、テストのスコープの問題を解消する試みとして、受け入れテスト駆動開発(ATDD)をご紹介します。

図1
図1

振る舞い駆動開発について

振る舞い駆動開発(Behavior Driven Development)とは、振る舞いを事前に実行可能な形式で記述し、そのあとで記述した振る舞いにのっとった形で実装を書いてゆく方法です。振る舞いの記述の実態は、xUnit 等のツールを使ったテストコードになりますので、やっていることの本質はテスト駆動開発(TDD)とは大差ありません。

主な特徴は以下のとおりです。

まず、一つ目にテストという用語を排した点にあります。テストとは設計である。という主張はやはり定義矛盾を含んでおり、納得はできても他人に説明しづらい側面を持っています。そこで、名称を変えることで、誤解を根本的に解消しようとしたものです。開発者も、テストを書くのではなく、クラスの振る舞いを設計する作業が、開発を駆動するということが、直感的に分かりやすくなります。何を目的として何をすべきなのかを明確にすることが大切で、テストは設計かそうでないか? などというのは、不毛な議論です。それを排したという意味で、個人的には大きな転回であると考えています。

「テスト」という用語の代わりに用いたのは、⁠ビヘイビア」という用語で、振る舞いのことです。振る舞いとは、オブジェクトの相互作用のことで、インターフェース(によって表現されるもの)と考えて構いません。

次の特徴として、クラスの振る舞いを表現しやすいように、より可読性の高い方法で振る舞いを書けるようにしたことです。具体的には、DSL と呼ばれる専用の言語で振る舞いを記述します。これにより、開発言語に精通していなくても、読みやすい設計を表現することが可能となります。また開発者側も、DSL という型にはまることで、作成したテストは、おのずと振る舞いを表現するようなものになり、TDD を行う技術者の TDD の熟練度に依存しなくても、分かりやすいテストが書けるようになります。

BDD の具体例(easyb)

それでは、具体的な BDD の例を見てみましょう。

Java で、以下のクラス図に示すようなテストを記載するとします。

図2
図2

JUnit で記載した場合の例は以下のとおりです。

import static junit.framework.Assert.*;
import org.junit.*;

public class CalculatorTest {

		:

	/**
	 * 割り算テスト
	 */
	@Test
	public final void testDiv() {
		Calculator cal = new Calculator(0);
		//0/10=0
		assertEquals(0, cal.div(10).getValue());
		//(0+60)/2/3=10
		assertEquals(10, cal.add(60).div(2).div(3).getValue());
	}

	/**
	 * 割り算テスト(異常系)
	 */
	@Test(expected = IllegalArgumentException.class)
	public final void testDivZero() {
		Calculator cal = new Calculator(0);
		cal.div(0);
	}

これを、Groovy を使った easyb で記述すると、このようになるでしょう。

import Calculator

scenario "10の値を持つ計算機オブジェクトに5を除算する", {
    given "値が10の計算機オブジェクトを生成する", {
        calculator = new Calculator(10)
    }
    when "計算機オブジェクトに5を除算する", {
        calculator.div(5)
    }
    then "計算機が保持する値は2である", {
        calculator.value.shouldEqual 2
    }
    and "計算機が保持する値は正である", {
    	ensure(calculator.plus)
    }
}
scenario "計算機オブジェクトを0で除算して例外を発生させる", {
    given "計算機オブジェクトを0で除算する", {
        var = {
        	(new Calculator(10)).div(0)
        }
    }
    then "例外が発生する", {
        ensureThrows(IllegalArgumentException) {
        	var.call()
        }
    }
}

あえて容易な題材に大量のコメントを入れていますが、ニュアンスは分りやすくなったと思います。記載内容は、テストでも振る舞いでも同じです。ですが、振る舞いの場合、シナリオ形式で書きますので、given, when, then のリズムで記述することが保証されます。この形式が徹底されることで、以下のように、ストーリーとして振る舞いを出力することが可能となります。

Stories Text
8 scenarios executed successfully.

  Story: calculator

	:

    scenario 10の値を持つ計算機オブジェクトに5を除算する
      given 値が10の計算機オブジェクトを生成する
      when 計算機オブジェクトに5を除算する
      then 計算機が保持する値は2である
      then 計算機が保持する値は正である

    scenario 計算機オブジェクトを0で除算して例外を発生させる
      given 計算機オブジェクトを0で除算する
      then 例外が発生する

振る舞いといっても、テストシナリオそのものです。そして、可読性は、普通にテストを書いていた場合と比べ、格段に改善します。

このように、振る舞い駆動開発では、記述のルールを強制することで、可読性の高いテストシナリオ(=振る舞い仕様)を、定めることが可能となります。

このようなDSLを用いて可読性を高めるツールは、その特性から、受け入れテスト(機能テスト)にのみ用いられるように考えられがちです。あらゆるレイヤーで、オブジェクトの振る舞いを明示する際に利用することが可能です。ただし、BDD においては、TDD同様に、あくまでも開発を駆動する設計作業として、DSLを用います。そして、振る舞い(=テスト)の記述は、開発者が実施します。

つまり、BDD のコンテキストにおいては、顧客と開発者の間の認知ギャップに対する対策にはなりきれていません。そこで、テスト駆動開発の考え方を、顧客が作成できる、機能テスト(=受け入れテスト)にまで広げようとする考え方があります。次にご紹介する受け入れテスト駆動開発(ATDD)です。

受け入れテストの位置づけ

TDD や BDD では、テスト作成と実装を交互に繰り返しながら、ソフトウェアを成長させてゆくやり方を取ります。そして、テストは、クラスないしはコンポーネント(意味上の一つのクラス群)に対しての単体テストレベルのテストが中心となります。それらのオブジェクトの使い方を明らかにすることが大きな目的の一つだからです。

一つ一つのオブジェクトの振る舞いについての透明性をあげることは、開発者間の意思疎通不全を解消するのに効果はありますが、顧客と開発者の間の認識違いを解消することはできません。

顧客に分かりやすい言葉でソフトウェアを表して、顧客と開発者の間の認識違いを解消する必要があります。以前、ストーリーによる仕様の表現で、この問題に対処する方法をご説明しました。また細かくイテレーションを切って、簡易リリースを繰り返す方法もご説明しました。それらを補完し、この問題を解消する考え方が、受け入れテストの早期実施とその自動化です。

各イテレーションの最後に、受け入れテストをしてもらい、成果物を確認してもらいます。その受け入れテストは自動化しておいて、次回イテレーションの終わりには再び流してデグレードしていないかどうか確認してゆきます。そうすることで、開発期間最後のイテレーションにおけるテストをより軽量化することが可能となります。

自動化ツールの多くは、ユーザーの操作を記録して再実行させることが可能です。その為、イテレーションの最後に受け入れテストの実装と、その自動化の作業を組み込むことになります。

ですが、その考え方をさらに進めて、テスト駆動開発のレベルで受け入れテストの考え方を組み込んでしまうという ATDD のアプローチがあります。

受け入れテスト駆動開発

受け入れテスト駆動開発(Acceptance TDD)とは、TDD の開発を駆動するテストとして、受け入れテスト(機能テスト)を使おうとする試みです。こちらも テスト駆動開発を行うという意味では変わらないのですが、テストの粒度をより受け入れテストに近いものとして実装していく点に違いがあります。開発者が TDD のリズムを刻みながら実装を進めるものですから、受け入れテストとは粒度は異なります。

とはいえ、単体テストレベルのTDD にくらべ、テストの粒度は大きくなりますので、実装と並行してテストを進化させるようなリズミカルなコーディングスタイルにはなりません。ある程度の受け入れテストを事前に作成しておいて、それを通過するように実装を進めるといった作業になり、レッドの状態(テストが通過しない状態)は、相対的に長くなります。

しかし、事前に自動化されたテストを実行可能にすることは、受け入れレベルのテストだと困難なことがあります。ツールで、実際の動作を記録して自動化した方が楽なケースが多いからです。

  • 誰がテストケースを書くのか?
  • どのタイミングでテストコードを実装するのか?
  • ユニットレベルのTDDとの住み分けをどうするのか?

などなど工夫の余地は大きいですが、受け入れテストを可能な限り前倒しで用意し、自動化しておくことのメリットは非常に大きいです。

まとめ

テストは、アジャイル開発においては、品質を担保するのみならず、開発を促進する潤滑油であり、開発の透明性を高める情報共有ツールでもあります。テストにより開発を駆動するという考え方を共有し、その効果を最大限に発揮できるよう創意工夫をこらすことが、アジャイル開発においては大切です。

おすすめ記事

記事・ニュース一覧