テスティングフレームワークとは

テストデータとコードの分離論

繰り返しテストにおけるテスト仕様書論

リンク集




Testing Framework

テスティングフレームワークとは

■序論
あなたがプログラマで、テスタが別にいるという立場にいる場合、次のように言われることがないですか?
「テストしたらこういう結果になったんだけど、これは正しいの?」
また、このように言うことがないですか。
「再現しないんだけど、どのようなデータを入力したの?」
動作の結果が正しいかどうかをテストするのですから、本来は動作のパタンとそれに対する正しい結果を先に認識していないといけないですね。
そして、よいテストというのは、動作のパタンと正しい結果を網羅してそれを効率よく確認していくというものでしょう。

■テスティング「フレームワーク」
 テスティングフレームワークはUnitTestプログラムを記述する際のフレームワークであり、これを利用することにより、テストプログラムが「簡単に」作れることになります。あるべき結果とならない場合の認識も「簡単」になります。 もっとも、「簡単に」というのは多分適切でなくて、「統一的に」とか「余計なことに煩わされずに」というのが正解でしょうか?
 この「統一的」ということは重要でしょう。確かに、テストプログラムを作成し、テストを自動化することだけでも、繰り返しテストが可能となり、開発効率が向上し、プログラム修正にあたる開発者に勇気をもたらします。しかし、これだけではその効果はプロジェクト限りの話で終わってしまうかもしれません。ところが、さらにフレームワークとして「統一化」されていることにより、この効果がプロジェクトを越えて(≒時と場所を越えて)現れます。

■テスティングフレームワークいろいろ
 ・JUnit
 ・vbUnit(vbUnitを活用する
 ・NUnit
 総称してxUnitと呼びます。

テストデータとコードの分離論

■分離の必要性
 xUnitの各ツールは、詳細にテストを書いておくことこそが生命線であり、何か怪しいところがあったらそのテストをすぐに記述し、実行するという行為が必要になるはずです。そのときに、テストを書くことが面倒になってはいけないですし、あるいはxUnitバージョンアップによりメソッド名の変更があったような場合にもスムーズに移行できなければいけないわけです。
 そこで、xmlの活用ということが考えられます。すなわち、テスト内容はデータとしてxmlファイルに置いておき、テストプログラムはデータを読み込んで自動実行するわけです。これにより、テストプログラムを知らなくてもテストを追加することができますし、テスト追加後にテストプログラムをコンパイルする必要もありません。場合によってはテスト仕様書の自動生成というような副産物も生み出すことができるかもしれません。

 テスト・コードからテスト・データを分離することについて[資料1]は3つの利点を挙げています。
 ・テスト・データは編集可能である、これによってテスト・ケースを簡単に追加できる
 ・テスト・データは検証可能である、これによって「間違った失敗」を減らすことができる
 ・テスト・データは外部で生成したり、製造工程から取得することができる

 (VisualStudio.NETの事情)
 VS.NETは重いといわれることが多いと思います。「重い」というのは主観的な話になってしまいますが、個人的にはデバッグのためにもビルドを要求されることで、「重さ」を頻繁に感じてしまうように思います。これはテストプログラムについても同じなので、テストケースを追加してテストし、アプリを修正してテストし、というリズミカルなテストファーストを邪魔します。
 そこで、せめて、テストプログラムについてはビルドしなくても済ませたい、という独特の事情が存在します。

■JUnitにはJXUnit
 JUnitでテストを作成する際に、テストデータを分離するためのフレームワークとして、JXUnitが存在します。
 大まかに紹介すると、まず、XMLファイルの中で、セットする値、期待する値、失敗時のメッセージ等を書いておくことができます。
 これだけだと、XMLでテストのコーディングをするのに近くなってしまうので、外部テキストファイル名を指定することにより、セットするデータを外部テキストに追い出すことができます。
 さらに、新しいファイルを追加する際にいちいちXMLファイルを触らなくてよいように、ディレクトリとファイルの要素を指定することにより、自動的にデータファイルを探すように指定することもできます。
 (詳細については[資料2]参照。また、サンプルはhttp://www.hyuki.com/jn/#i41
 もっとも、何でもできるようにしようとするあまり、ちょっと難しいような気がします。

■NUnitではどうするか
 今のところ、NUnitに対応するNXUnitのようなものは見当たりません。本来はそれを作ればよいのかもしれませんが、とにかくデータ分離の効果を実感してみるために、さしあたり次のような方法で行うことにしました。

○テスト対象クラス<Schedule.vb>
Public Class Schedule

    Public ErrMessage As String

    Public Function SetSchedule(ByVal startDateTime As String _
     , ByVal endDateTime As String, ByVal schedule As String) As Boolean
        '実装
    End Function

End Class
SetScheduleメソッドは引数を3つ受け取り、値を保存できればTrue、 保存できない場合はErrMessageにメッセージをセットしてFalseを返すとします。
このメソッドのテストを考えます。

○テストデータ読み込み用ヘルパクラス<NUnitDataHelper.vb>
Imports System.IO

Public Class NUnitDataHelper
    Private Const MyDir As String = "D:\\app\\ClassLibrarySample\\Tests\\"

    Public Function GetDSTestData(ByVal p_FileName As String) As DataSet

        Try
            'Console.WriteLine("XmlDataDocument を作成します")
            Dim datadoc As New System.Xml.XmlDataDocument()

            ' Infer the DataSet schema from the XML data and load the XML Data
            Dim reader As StreamReader
            reader = New StreamReader(MyDir & p_FileName)
            datadoc.DataSet.ReadXml(reader, XmlReadMode.InferSchema)
            reader.Close()
            reader = Nothing

            Return datadoc.DataSet

        Catch e As Exception
            'Console.WriteLine("例外: " & e.ToString())
        End Try

    End Function

End Class
テストクラスを書く前に、簡単なヘルパーを用意しました。これはXMLファイルをDataSetに格納するためのものです。
本来、DTD等でスキーマを定義しておいて、それにしたがって読み込むわけですが、.NETでは便利な機能があります。
datadoc.DataSet.ReadXml(reader, XmlReadMode.InferSchema)
で実際の読み込みを行いますがXmlReadMode.InferSchemaとすることにより、 スキーマがなくてもデータに基づいて勝手に解釈をしてくれます。
(参考:http://ja.gotdotnet.com/quickstart/howto/doc/Xml/DataSetMapXMLData.aspx
ただし、逆にXMLデータをうまく作ってあげないとうまく読んでくれないことになります。

○テストクラス<ScheduleTest.vb>
Imports NUnit.Framework

<TestFixture()> Public Class ScheduleTest : Inherits Assertion
    Private target As ClassLibrarySample.Schedule

    Private dsTestData As DataSet
    Private testDataFile = "ScheduleTest.xml"

    <SetUp()> Public Sub Init()
        target = New ClassLibrarySample.Schedule()

        Dim helper As New NUnitDataHelper()
        dsTestData = helper.GetDSTestData(testDataFile)
    End Sub

    <TearDown()> Public Sub Destroy()
        target = Nothing
        dsTestData = Nothing
    End Sub

    <Test()> Public Sub TestSetSchedule()
        Dim ret As Boolean
        Dim startDateTime As String = "2002/11/3 12:00"
        Dim endDateTime As String = "2002/11/3 13:00"
        '保存
        ret = target.SetSchedule(startDateTime, endDateTime, "title")
        Me.Assert("保存できるか" & target.ErrMessage, ret)

        '入力チェック
        Dim row As DataRow
        For Each row In dsTestData.Tables("TestSetSchedule").Rows
            ret = target.SetSchedule(row("StartDate") _
             , row("EndDate"), row("input_title"))
            Me.Assert(row("message"), ret = False)
            Me.AssertEquals(row("message") _
            , 1, InStr(target.ErrMessage, row("expected")))
        Next

    End Sub

End Class
「保存」とコメントのある部分が、NUnitでの普通のテストの書き方になります。
この方法では、テストを増やしたい場合はここに追記していくことになります。 このことはテストファーストでコーディングしている段階ではなんら問題にはなりません。
しかし、一通り作った後にテストをする場合を考えると、まず、テストを追加するために再ビルドが必要になること、 また、テストを追加するためにはVBの環境がなくてはならず、VBを書ける必要があること、これらが気になるわけです。
この問題への対応として、「入力チェック」の方では外部ファイルに分離されているテストデータを使用しています。
まず、InitでScheduleTest.xmlの内容をDataSetに格納しておきます。
すると今回の例ではTestSetScheduleというテーブルができますので、そのデータを順に引数として渡してテストを行います。

○テストデータ<ScheduleTest.xml>
<?xml version="1.0" encoding="utf-8" ?> 
<Test>
    <TestSetSchedule
     message="件名は40バイト" expected="件名 40バイト以内で入力してください。"
     StartDate="2002/11/10 12:00"
     EndDate="2002/11/10 13:00"
     input_title="********10********20********10********20*"
     />
    <TestSetSchedule
     message="件名は必須" expected="件名 未入力です。"
     StartDate="2002/11/10 12:00"
     EndDate="2002/11/10 13:00"
     input_title=""
     />
    <TestSetSchedule
     message="開始日時" expected="開始日時 誤りがあります。"
     StartDate="2002/11/10 25:00"
     EndDate="2002/11/10 13:00"
     input_title="title"
     />
    <TestSetSchedule
     message="終了日時" expected="終了日時 誤りがあります。"
     StartDate="2002/11/10 12:00"
     EndDate="2002/11/10 25:00"
     input_title="title"
     />
    <TestSetSchedule
     message="日時 開始終了" expected="日時 開始終了に誤りがあります。"
     StartDate="2002/11/10 12:00"
     EndDate="2002/11/9 13:00"
     input_title="title"
     />
    <TestSetSchedule
     message="期間" expected="日付 20日以上の期間の登録はできません。"
     StartDate="2002/11/1 12:00"
     EndDate="2002/11/21 13:00"
     input_title="title"
     />
</Test>

■分離の方針
(参考:JUnit 実践講座 - プログラミングスタイルガイド
 ところで、このようにしてテストデータを分離するとしても、すべてを分離する必要はないと思います。前提として、テストはそれに対するテストなしにメンテナンスが可能である必要があります。追加したいときにどんどん追加できないといけません。したがって、分かりやすさが最重要です。テストコードを見て何をやっているかわからなくなってはいけません。
 このような考えとテストデータの分理論は一見矛盾するかにも思われます。しかし、単にパターンを網羅したいだけの場合には、個々のデータをその場で見る必要はありません。この部分で色々なパターンをテストしている、ということが分かればよいのです。
 そこで、基本方針は
・分離が煩雑になるような場合は分離しない
・分離してしまうとテストコードの意味が分からなくなるような場合は分離しない
・パターンが大量の場合は分離する
となります。
 ただし、全テストデータをXMLにしておかないと、文書化の面で不利になります。この点の工夫は近い将来の課題とします。

繰り返しテストにおけるテスト仕様書論

必要事項
・テストの再利用 ・外部リソースとの連携 特にコード(テストコード含む)からのテスト仕様吐き出し  結局、EXCELで書くことには無理がある。DBを使って、 ・テンプレートの呼び出し可能 ・テストパタンの蓄積 ・柔軟な取り込み ・CSV・EXCEL等への出力 という機能を備えたシステムが必要

資料・リンク集

・JXUnit(日本語訳)[資料1]
 http://www.metabolics.co.jp/XP/JXUnit2.0.0/

・JXUnit
 http://jxunit.sourceforge.net/

・Practical Java Lesson第40回 XMLでテストデータを記述するJXUnit(UNIX USER 2001/10)[資料2]
 (ML上の関連議論http://www.freeml.com/message/patterns@freeml.com/0000843等)


select