xUnitファミリーの世界展開 — JUnitから広がった移植の波

1997年に登場したJUnitの成功は、Javaコミュニティにとどまらなかった。CppUnit、NUnit、PyUnit、Test::Unit、PHPUnitといった移植版が1999年から2002年ごろにかけて主要言語へ次々と広がり、「xUnit」という総称の下に一つのファミリーを形成する。Gerard Meszarosが2007年の著書で体系化したテストパターンとテスト臭(test smell)、そしてTestNGやxUnit.netという第二世代の実装までを辿り、単体テストが特定言語に縛られない業界標準になった経緯を追う。

testinghistoryxunitjunittest-patternsunit-testing

前章で見たJUnitの成功は、Javaコミュニティの中だけにとどまらなかった。1990年代末から2000年代初頭にかけて、JUnitと同じ骨格——TestCase、assertion、fixture、setUp/tearDown——を持つフレームワークが、ほぼすべての主要言語に次々と移植されていった。

この移植の波によって、「テストフレームワークを使ってテストを書く」というスタイルは、特定言語の流儀ではなく業界横断の標準的な実践になった。本章では、この展開の様子と、それらをひとまとめに「xUnit」と呼ぶことの意味を追う。

JUnitからの移植ラッシュ

言語 フレームワーク 主要人物 開始・初期の時期
C++ CppUnit Michael Feathers(Windows向け初期移植)、Jerome Lacoste(Unix/Solaris移植) 2000年前後
.NET NUnit Philip Craig(2000年、XP2000カンファレンスでデモ)、James Newkirk(2002年のNUnit 2で属性ベースに刷新) 2000年
Python unittest(PyUnit) Steve Purcell 1999年開発、Python 2.1(2001年)で標準ライブラリ入り
Ruby Test::Unit Nathaniel Talbott 2000年前後
PHP PHPUnit Sebastian Bergmann 2001年着手、2002年に初版公開
JavaScript JsUnitほか Edward Hieatt 他 2001年前後
Perl Test::More / Test::Simple Michael G Schwernら 2001年前後

移植の中心的な時期は1999年から2002年ごろに集中している。JUnitが1997年に登場してからわずか数年のうちに、主要言語のほぼすべてに同種のフレームワークが行き渡ったことになる。担い手は特定の企業や標準化団体ではなく、多くの場合各言語コミュニティに属する個人の開発者だった。オープンソース文化の広がりと、xUnitアーキテクチャそのものの単純さ・移植しやすさの両方が、この急速な展開を後押しした。

骨格の共通性 — どの言語でも同じ三点セット

興味深いのは、静的型付け言語(Java、C#、C++)であっても動的型付け言語(Python、Ruby、PHP、Perl)であっても、オブジェクト指向を前提とする言語であってもそうでなくても、ほぼ同じ骨格が違和感なく移植されている点である。

以下は、Pythonのunittestと Rubyの Test::Unitで、まったく同じ「電卓に2と3を足すと5になる」というテストを書いた例である。

# Python: unittest(PyUnit系)
import unittest

class CalculatorTest(unittest.TestCase):
    def setUp(self):
        self.calculator = Calculator()

    def test_adds_two_positive_numbers(self):
        self.assertEqual(5, self.calculator.add(2, 3))
# Ruby: Test::Unit系
require 'test/unit'

class CalculatorTest < Test::Unit::TestCase
  def setup
    @calculator = Calculator.new
  end

  def test_adds_two_positive_numbers
    assert_equal(5, @calculator.add(2, 3))
  end
end

TestCaseのサブクラス化、setUpassert*という三点セットは、言語が変わってもほとんど同一の形で現れる。単体テストという営みの本質が「前提条件を整える→対象を動かす→結果を検証する」という単純な三段階に還元できることを、この普遍性が物語っている。

「xUnit」という総称の定着

これらJUnit系フレームワーク群を指して「xUnit」と呼ぶ習慣が定着した。「x」の部分に各言語やツールの頭文字(J、N、Cpp、Py…)を当てはめればそのままフレームワーク名になるという言葉遊びであると同時に、「共通のアーキテクチャを持つ一つのファミリー」という認識を端的に表す呼び方でもある。

この総称が広く使われる決定打となったのが、次に見るGerard Meszarosの著書である。個々の言語やツールの違いを超えて共有される設計パターンとアンチパターンを整理する際、Meszarosはこれらの実装群をまとめて「xUnitファミリー」として扱った。これにより、「JUnitのやり方」でも「NUnitのやり方」でもなく「xUnitのやり方」という、特定の実装に縛られない議論が可能になった。

Gerard Meszaros『xUnit Test Patterns』(2007)

2007年、Gerard MeszarosはAddison-Wesleyから『xUnit Test Patterns: Refactoring Test Code』を出版した。それまで各コミュニティに暗黙知として蓄積されていたxUnit系フレームワークの使い方のノウハウを、パターン(pattern)とテスト臭(test smell)という形式で初めて体系的に整理した一冊である。

書名が示す通り、この本は「xUnit」という言葉をタイトルに冠した最も有名な文献であり、この総称を業界に定着させる上でも大きな役割を果たした。分厚い一冊だが、パターン集としての性格上、必要な箇所だけを辞書的に参照する使い方を前提に書かれている。

代表的なテストパターンには次のようなものがある。

  • Test Data Builder — テストに必要なオブジェクトを柔軟に組み立てるビルダー
  • Object Mother — よく使うテスト用オブジェクトの生成をまとめて提供するファクトリ的な仕組み
  • Test Stub / Mock Object — 依存先を置き換えるテストダブル(test double)の諸パターン

いずれも「テストのセットアップを読みやすく、かつ変更に強く保つにはどうすればよいか」という一貫した問題意識から生まれている。

一方、避けるべき「テスト臭」としては次のようなものが挙げられている。特定の言語やフレームワークに固有の問題ではなく、xUnitアーキテクチャを使う限りどの実装でも起こりうる共通の落とし穴として整理されている点が重要である。

テスト臭 内容
Fragile Test(壊れやすいテスト) プロダクションコードの些細な変更でテストが頻繁に壊れる
Obscure Test(不明瞭なテスト) 何を検証しているのか読み取りづらい
Mystery Guest テスト外部(ファイルやDBなど)への依存があり、前提が見えない
Test Code Duplication テストコード間の重複が多く保守コストが高い
Slow Tests 実行が遅く、フィードバックループを阻害する
Assertion Roulette 一つのテストに多数のassertionが並び、どれが失敗したのか分かりにくい

これらの分類は、今なお実務のコードレビューで参照される、テスト保守性をめぐる議論の直接の源流である。

第二世代のxUnit実装 — TestNGとxUnit.net

2000年代前半の移植ラッシュがひと段落した後も、xUnitアーキテクチャの上に立ちながら初期の設計上の制約を解消しようとする「第二世代」の実装が現れた。

  • TestNG — 2004年、Cédric Beustが開発しJavaコミュニティに公開した。当時のJUnit(JUnit 3系)がテストスイートの構成や並列実行、パラメータ化されたテストの記述で柔軟性に欠けると感じたことが動機とされる。JSR 175で導入されたJavaのアノテーション機構をいち早く活用し、テストのグループ化・並列実行・タイムアウト指定などを先駆けて実装した。
  • xUnit.net — 2006年ごろ、NUnitの原作者であるJames Newkirkが、Microsoftの同僚Brad Wilsonとともに開発を始めた.NET向けの新しいテストフレームワーク。2007年9月に公開された。長年の拡張でNUnitのコードベースが複雑化していた反省を踏まえ、[SetUp]/[TearDown]のような専用アトリビュートを廃し、コンストラクタやIDisposableでfixtureを表現するなど、よりシンプルなAPIを目指して一から書き直された。

これらの第二世代実装は、xUnitアーキテクチャの基本発想(TestCase、assertion、fixture)を継承しつつ、初期の移植版が抱えていた制約を一つずつ解消していった。xUnitファミリーが単なる移植の歴史ではなく、現在進行形で改善が続く生きたアーキテクチャであることを示している。

まとめ — なぜこの移植の波が単体テストを世界標準にしたか

JUnitからxUnitファミリーへの展開が持った意味は、単に「便利なツールが増えた」ということではない。重要なのは次の3点である。

  1. 語彙の共通化 — どの言語であっても「TestCase」「assertion」「fixture」「setUp/tearDown」という同じ言葉で単体テストを議論できるようになった
  2. 知識の移転可能性 — ある言語でxUnit系フレームワークの使い方を覚えた開発者は、別の言語に移っても同じ発想でテストを書き始められる
  3. パターンとアンチパターンの言語横断的な蓄積 — Meszarosの整理によって、「良いテスト」「悪いテスト」の判断基準が特定の言語やツールに縛られない形で共有されるようになった

この普遍的な基盤があったからこそ、後続のテスト駆動開発(TDD)、ふるまい駆動開発(BDD)、モックの体系化といった議論が、言語を問わず横断的に発展していくことができた。

この記事から次の記事へ

前章で見たJUnitの誕生から、わずか数年でxUnitファミリーが世界中に広がったことを辿った。だが、この共通基盤の上に「テストをどう書くか」という技法論が独立して発展し始める。次章では、エクストリーム・プログラミング(XP)の一プラクティスから出発し、やがて独立した技法として広まったテスト駆動開発(TDD)——そして2014年の大論争「TDD is dead」に至るまでの興隆と対立の歴史を辿る。

← Back to ソフトウェアテストの系譜