TL;DR

さまざまな視点から見るソフトウェアテスト

こんにちは、ONDAでホテル運営管理ソリューションを開発しているバックエンド開発者のGunner(ガンナー、チョン・ジェフン)です。長年さまざまな分野でバックエンド開発を経験しながら、S/Wテスティング分野の範囲が広いだけでなく、テストが必須かどうかについても意見が分かれているのを感じてきました。

そこで今回はソフトウェアテストの種類と、開発者が作成すべきテストの基準について、2編に分けてお話ししていきます。

1. ソフトウェアテストの種類

ソフトウェアテストとは、ソフトウェアが正しく動作するか、バグはないかを発見するためのプロセスを指します。

S/Wテスティングは大きくブラックボックス(Black-Box)テストとホワイトボックス(White-Box)テストに分けられます。

  • Black-Box(Functional)テスト: ソフトウェアを「ブラックボックス」として見てテストする手法で、内部がどう動いているかは見えないのが特徴です。外部ユーザーの視点から機能をテストするため、ソースコードはテスト対象外。主にQA(Quality Assurance、サービスの機能と品質を検証する)チームが使用します。

  • White-Box(Structural)テスト: プログラムコードを見て検証しながらソフトウェア機能をテストする手法です。ソースコード、プログラム構造、データフローなどをテストするもので、主に開発者が使用します。

また、テストがアプローチするレベルの観点から次のように分類できます。

  • Unit(ユニット): プログラムを構成する基本単位をテストします。つまり、プログラムを構成する基本単位を他の部分から分離して独立実行し、その結果が期待値と一致するかを確認するものです。単位の基準はプログラミング言語やフレームワークによって異なります。たとえば関数型プログラミングでは、関数が1つの単位になるでしょう。

  • Integration(統合): システムは複数のコンポーネント間の相互作用で成り立っています。同一システム内、または他システムとのコンポーネント間の相互作用をテストすることを指します。

    ◦ Regression(回帰): 機能追加やバグ修正などの変更を加えた際、システムの既存機能に悪影響を及ぼしていないかを確認するために実行するテストです。

    ※Integration(統合)テストはRegression(回帰)テストになることもあれば、別物になることもあります。

  • System(システム): 完全に統合された1つのシステムが要求仕様を満たしているかを確認するテストです。内部を知る必要があるためブラックボックステストとは異なり、ブラウザを立ち上げてコードをテストできます。また、統合されたシステムという観点から機能をテストするため統合テストとも異なります。さらに機能的(Functional)テストだけでなく、非機能的(Non Functional)項目(例:安定性、拡張性など)の検証も含みます。

2. オブジェクト指向システムにおけるテスト

現在、多くのソフトウェアシステムはオブジェクト指向手法で開発されています。オブジェクト指向パラダイムは1960年代にシミュレーション目的で作られたSimula言語を通じて初めて登場しましたが、現在はさまざまな分野で使われています。

オブジェクト指向システムでユニットテストを行う際、単位の基準は何でしょうか?

オブジェクト指向システムでは、システムを構成する基本単位はオブジェクト(object)なので、単位基準はオブジェクトに関する情報を記述するクラス(class)と見るのが一般的です。

クラスはフィールド(field)とメソッド(method)、継承など複数の要素が絡み合って構成されます。実際にクラスをユニットテストする際、一般的にメソッドに対するテストを作成するため、メソッドを基本単位と考えることもできますが、本来メソッドはクラスと切り離して考えられないため、メソッドを基本単位と見るのは適切ではありません。

クラスを単位基準とした場合、ユニットテストは大きく2つの形態に分けられます。

  • イントラクラス(intra-class): 対象クラス内部のみでテストするケース
  • インタークラス(inter-class): テスト対象クラス以外の他クラスも参加するケース

ちょっと待って!インタークラス型ユニットテストと統合テストの違いは?

オブジェクト指向システムの特性上、1つのテストケースに複数のクラスが関与する場合が多く、このときユニットテストと統合テストの境界が曖昧になります。統合テストは「同一システム内、または他システムとのコンポーネント間の相互作用をテストする」ものだからです。

結局これは、コンポーネントの基準をどう見るかにかかっています。もしコンポーネントの基準を1つのクラスと見るなら、ユニットテストと統合テストの境界線は曖昧になるわけです。

正解はありませんが、一般的に次のようにユニットテストと統合テストの区分基準を設けることができます。もちろん絶対的な基準ではないため、オブジェクト指向システムでこれを明確に区別するのは依然として難しい部分として残っていますが。

  • ユニットテストは独立実行(他のシステムコンポーネントから隔離された状態で高速実行できるテスト)を重視するためモック(mock)の使用を推奨しますが、統合テストはモックを使わず実際のコードをテストに使用します。
  • ユニットテストに関与するクラス数は、統合テストに関与するクラス数より少ない傾向があります。

3. BDD(Behaviour-Driven Development)とTDD(Test-Driven Development)

こうした曖昧さは、振る舞い駆動開発(Behaviour-Driven Development, BDD)[1]やテスト駆動開発(Test-Driven Development, TDD)のようなテストツールでも垣間見えます。TDDがユニットテストにフォーカスするのに対し、BDDはTDDから派生した手法で、システムの振る舞いにフォーカスし、それを明確に表現してテストすることに重点を置きます。

BDDでテスト仕様を書く際に使う**「Given/When/Then」**ガイドラインは、このBDDの特性をよく表しています。

  • Given: テスト対象の振る舞いを引き起こすための初期セットアップを行います。
  • When: テスト対象の振る舞いを発生させるイベントを記述します。
  • Then: イベント発生時に期待する結果値を確認します。

「Given/When/Then」ガイドラインを使って書かれた例題Scalaテストケースコードを見てみましょう。

package com.acme.pizza

import org.scalatest.FunSpec
import org.scalatest.BeforeAndAfter
import org.scalatest.GivenWhenThen

class PizzaSpec extends FunSpec with GivenWhenThen {

  var pizza: Pizza = _

  describe("ピザ") {

    it ("トッピングを追加できるべき") {
      Given("新しいピザに")
      pizza = new Pizza

      When("トッピングが追加されると")
      pizza.addTopping(Topping("green olives"))

      Then("トッピング数が増加する")
      expectResult(1) {
        pizza.getToppings.size
      }

      And("追加したトッピングの種類は保存される")
      val t = pizza.getToppings(0)
      assert(t === new Topping("green olives"))
    }
  }
}

BDDで「Given/When/Then」を強調する理由は?

オブジェクト指向システムはオブジェクト間の相互作用で機能が動作する場合が多く、ユニットテストだけでは限界があります。そのため、テストケース(Test Case, TC)作成時に相互作用に集中してテストを作るのが良いのです。つまり、TC設計時にイントラクラスに範囲を限定せず、インタークラス範囲で考える方が良いということです。

もしJUnitのようなユニットテストツールを使う場合、1つのクラスに対して1つのテストファイルを作成するというJUnitの暗黙のルールとともに、TC設計もそのクラス1つに限定して考えがちです。しかし最初から「Given/When/Then」ガイドを意識すれば、自然とクラス間の相互作用を考えるようになるでしょう。

つまりBDDを利用すれば、クラス間のインタラクションを考慮してTCを作成することになり、これが自然と良質なインタークラステストを導きます。

もちろん「Given/When/Then」ガイドに従うだけで相互作用を考慮したテストが書けるわけではありません。「Given/When/Then」ガイドはユニットテストにも統合テストにも使えますし、何より重要なのはテスト作成者が考えるTCのレベルです。

では、TDD(テスト駆動開発)とは何で、これについてどんな意見があったのでしょうか?

参考

1. Behavior-driven development - Wikipedia