SUnit

SUnit（エスユニット）とは、Smalltalkにおける単体試験スーツである。エクストリーム・プログラミングの提唱者でもあるケント・ベックによって書かれた。CppUnitなどXUnitの原型となっている。2017年現在の最新バージョンはSUnit 4.0である。

概要

SUnitはSmalltalk上で完結しておりSmalltalkのみで試験の記述と実施ができる。 SUnitは試験項目をコードで記述することにより毎回同じ試験ができる。このためSUnitの登場により人がプログラムを操作していたときと比べ正確な単体試験ができるようになった。しかしソースコードを畫く知識が求められるため書けない人を試験担当者できないという弱点も発生した。中核はライブラリーであるが、一般的にはSmalltalk環境ごとのGUIと連動するためのツールキットと共にパッケージにして配布されている。

歴史

SUnitはケント・ベックの著書「Kent Beck's Guide to Better Smalltalk」の第30章「Simple Smalltalk Testing」および「Simple Smalltalk Testing：With Patterns」（1989）にて発表された。

SUnitとリファクタリング

SUnitによる試験の自動化はそれまで禁忌とされていた試験済みソースコードの改変を可能にした。SUnitによりオブジェクトの入出力のみを保証するブラックボックス試験を記述するればSUnitから見えるオブジェクトのプロトコル（あるはインターフェース）が変わらない限り試験対象のクラスをどんなに改編しようと同じ試験メソッドを使いつづけることができ改編後も動作に問題が無いことを保証することができる。例えばメソッドを派生クラスから基底クラスに移動、メンバーの変数を別のオブジェクトに委譲、あるいは複数のメソッドで共通するメッセージ送信を新しいメソッドとして抽出など、これらの修正はブラックボックス試験である限り試験メソッドと動作には影響はない。この考えに基き積極的に試験済みのソースコードを改編する手法としてリファクタリングが生まれた。
なお単体試験においてホワイトボックス試験はリファクタリングを妨げるためすべきでないとされる。

試験ケース

SUnitでは試験をクラスとして記述する。このクラスを試験ケースと呼ぶ。
次に可変長配列であるOrderedCollectionを試験する試験ケースの例を記述する。
TestCase
    subclass:               #OrderedCollectionTest
    instanceVariableNames:  'sourceIndex sourceValue expectedValue'
    classVariableNames:     ''
    poolDictionaries:       ''
    category:               'Example'.
    
OrderedCollectionTest
    createGetMethod:    'sourceIndex';
    createSetMethod:    'sourceIndex';
    createGetMethod:    'sourceValue';
    createSetMethod:    'sourceValue';
    createGetMethod:    'expectedValue';
    createSetMethod:    'expectedValue';
    yourself.OrderedCollectionTest methodsFor: 'accessing'!targetClass
    ^ OrderedCollection.
!!

OrderedCollectionTest methodsFor: 'setUp-tearDown'!setUp
    "(4)"

    super setUp.
    self
        sourceIndex: 1;
        sourceValue: 1;
        expectedValue: 1.!tearDown
    "(5)"
    
    super tearDown.
!!

OrderedCollectionTest methodsFor: 'test'!runAt0
    | target |
    
    target := self targetClass new.

    target add: self sourceValue.
    "(6)"
    self assert: self expectedValue = ( target at: self sourceIndex ).!testAt0_0
    "(2)"
    
    self runAt0.!testAt0_1
    "(2)"

    self
        sourceValue: 2;
        expectedValue: 2.
    
    self runAt0.!runAt1
    | target |
    
    target := self targetClass new.

    target add: self sourceValue.
    self deny: self expectedValue = ( target at: self sourceIndex ).!testAt1_0
    "(2)"

    self sourceValue: 2.
    
    self runAt1.!runAt2
    | target |
    
    target := self targetClass new.
    target add: self sourceValue.
    
    "(7)"
    self 
        should:
        [
            target at: self sourceIndex.
        ]
        raise: SystemExceptions.IndexOutOfRange.!testAt2_0
    "(2)"

    self sourceIndex: 2.
    
    self runAt2.
!!

| result |"(3)"result := OrderedCollectionTest suite run.result printOn: Transcript.
試験ケースは（1）の様にTestCaseを直接または間接的に継承したクラスで殆どの場合試験対象となるクラスと一対にすることが多い。試験ケースの中にある（2）の様なtestがついたセレクターを持つメソッドは試験メソッドと呼び、開発者は試験メソッドの中に試験方法を記述する。試験方法を試験メソッドとして記述しておけばSUnitにより複数の実行条件、複数の実行単位により実行されるようになる。実行条件は単に試験の合格状況を確認するための失敗しても停止しない通常実行とデバッグ修正を目的とした失敗した時点で停止するデバッグ実行がある。実行単位では、試験メソッド個別、試験ケース単位、TestCaseの派生全てといったものがある。この例では（3）により試験ケース単位で実行している。ただし、実際の試験の実行はGUIで行うため（3）のように明示的なメッセージを書くことは無い。
（4）（5）の#setUpや#tearDownをセレクターを持つメソッドは試験メソッドの前後実行されるメソッドでそれぞれ試験ケースに共通する変数の初期化やファイルの削除などを実行するために使われる。#setUpは試験メソッドの実行前、#tearDownは試験メソッドの実行後に実行される。
（6）（7）の#assert:や#should:raise:は評価メッセージで実行結果を評価し試験の合否を判定する。#assert:は引数がtrueになれば合格とするメッセージで、#should:raise:はshould:の引数として与えたブロックからraise:の引数に指定した例外が発生すれば合格とするメッセージである。なお#assert:には#deny:、#should:raise:には#shouldnt:raise:という評価が逆転したメッセージが存在する。

試験用資源

SUnitでは複数の試験ケースで共有する資源の初期化と破棄を管理する仕組みとして試験用資源が存在する。試験用資源もクラスとして記述しDB接続等に利用される。
次にDB接続を管理する試験用資源の例を記述する。
"(1)"TestResource
    subclass:               #SomeResource
    instanceVariableNames:  'connection'
    classVariableNames:     ''
    poolDictionaries:       ''
    category:               'Example'.
    
SomeResource
    createGetMethod:    'connection';
    createSetMethod:    'connection'.SomeResource methodsFor: 'setUp-tearDown'!setUp
    "(3)"
    super setUp.
    self connection:
    (
        DBI.Connection
             connect:   'DBI:PostgreSQL:dbname=somedb:host=localhost'
             user:      'someone'
             password:  'password'
    ).!tearDown
    "(3)"
    self close.
    super tearDown.!!TestCase
    subclass:               #SomeTest
    instanceVariableNames:  ''
    classVariableNames:     ''
    poolDictionaries:       ''
    category:               'Example'.SomeTest methodsFor: 'accessing'!resources
    "(2)"
    ^ Array with: SomeResource.
!!

SomeTest methodsFor: 'test'!testSome
    "(4)"
    ( SomeResource current connection select: 'select * from SomeTable' ) do:
    [ :row |
        self assert: 0 = row at: 'SomeColumn'.
    ].
!!
試験用資源は基本的に試験ケースと同じで試験ケースの基底クラスを（1）の様なTestResourceに変更し、試験メソッドを無くしたクラスになっている。試験用資源は（2）の様に試験ケースに#resourcesというセレクターを持ったメソッドを登録することで紐づけることができ、試験用資源に紐づいた全ての試験ケースが始まる前と後に（3）の#setUpと#tearDownを実行する。（4）の様に試験用資源に#currentメッセージを送ることで試験ケースから試験用資源を参照できる。