SSブログ

VB.NET用単体テストフレームワーク(簡易版) [プログラミング]

前回の内容で薄々分かっていたかもしれませんが、Visual Basic .NET用の単体テストフレームワークっぽいものを作ってみました。

.NET 用の単体テストフレームワークは NUnit が有名で、VB.NET でも使えるらしいので、NUnit を使うことをお勧めしておきます。

''' <summary>簡易単体テストフレームワーク</summary>
Public Class UnitTest

    ''' <summary>テスト失敗を通知する例外</summary>
    Private Class UnitTestAssertException
        Inherits Exception

        ''' <summary>失敗したテストの実装場所を表す情報</summary>
        Private sf_ As StackFrame

        ''' <summary>コンストラクタ</summary>
        ''' <param name="sf">失敗したテストの実装場所</param>
        ''' <param name="mes">何らかのメッセージ。不要なら Nothing</param>
        Sub New(ByVal sf As StackFrame, ByVal mes As String)
            MyBase.New(IIf(IsNothing(mes), "", mes))
            sf_ = sf
        End Sub

        ''' <summary>失敗したテストの実装場所</summary>
        Public ReadOnly Property PlaceTestCode() As StackFrame
            Get
                Return sf_
            End Get
        End Property

    End Class

    ''' <summary>テストメソッドに関する情報</summary>
    Private Class TestMethod
        ''' <summary>テストメソッドの実行対象となるオブジェクト</summary>
        Private obj_ As Object
        ''' <summary>テストメソッド</summary>
        Private method_ As Reflection.MethodInfo

        ''' <summary>コンストラクタ</summary>
        ''' <param name="obj">テストメソッドの実行対象となるオブジェクト</param>
        ''' <param name="method">テストメソッド</param>
        Sub New(ByVal obj As Object, ByVal method As Reflection.MethodInfo)
            obj_ = obj
            method_ = method
        End Sub

        ''' <summary>テストメソッドを実行する</summary>
        '''
        ''' <exception cref="System.Reflection.TargetInvocationException">
        ''' テストメソッド中に何らかの例外
        ''' </exception>
        Public Sub invoke()
            method_.Invoke(obj_, Nothing)
        End Sub

        ''' <summary>テストメソッドについての情報</summary>
        Public ReadOnly Property method() As Reflection.MethodInfo
            Get
                Return method_
            End Get
        End Property
    End Class

    ''' <summary>全てのテストが完了したことを通知するイベント</summary>
    ''' <param name="succeeded">成功したテストの件数</param>
    ''' <param name="total">全てのテストの件数</param>
    Public Event NotifyFinished(ByVal succeeded As Integer, ByVal total As Integer)

    ''' <summary>失敗したテストについて通知するイベント</summary>
    ''' <param name="sf">失敗したテストの実装場所</param>
    ''' <param name="mes">テスト結果判定時に指定したメッセージ</param>
    Public Event NotifyFailed(ByVal sf As StackFrame, ByVal mes As String)

    ''' <summary>何らかの例外が発生したテストについて通知するイベント</summary>
    ''' <param name="method">テストメソッドの情報</param>
    ''' <param name="ex">発生した例外</param>
    Public Event NotifyError(ByVal method As Reflection.MethodInfo, ByVal ex As Exception)

    ''' <summary>実施するテストメソッドの一覧</summary>
    ''' <remarks>TestMethod型のオブジェクトを格納している</remarks>
    Private listTestMethod_ As New ArrayList

    ''' <summary>テストコードを登録する</summary>
    '''
    ''' <remarks>
    ''' 指定したオブジェクトのクラスに存在する 「test」で始まるメソッド を、
    ''' テストコードとして登録する。
    ''' </remarks>
    '''
    ''' <param name="obj">テストメソッドの実行対象となるオブジェクト</param>
    Public Sub addTestCase(ByVal obj As Object)

        Dim methods As System.Reflection.MethodInfo() = _
            obj.GetType().GetMethods(Reflection.BindingFlags.Instance Or _
                                     Reflection.BindingFlags.Public Or _
                                     Reflection.BindingFlags.NonPublic Or _
                                     Reflection.BindingFlags.DeclaredOnly)

        Dim method As System.Reflection.MethodInfo
        For Each method In methods
            If method.Name.StartsWith("test") Then
                listTestMethod_.Add(New TestMethod(obj, method))
            End If
        Next

    End Sub

    ''' <summary>テストを実施する</summary>
    '''
    ''' <remarks>
    ''' テスト完了時には、イベント NotifyFinished を発行する。
    ''' テストに失敗したら、その都度、イベント NotifyFailed を発行する。
    ''' テストで何らかの例外が発生したら、イベント NotifyError を発行する。
    ''' </remarks>
    '''
    ''' <seealso cref="UnitTest.NotifyFinished" />
    ''' <seealso cref="UnitTest.NotifyFailed" />
    ''' <seealso cref="UnitTest.NotifyError" />
    Public Sub runTest()

        Dim total As Integer = 0
        Dim succeeded As Integer = 0

        Dim info As TestMethod
        For Each info In listTestMethod_
            If runTestMethod(info) Then
                succeeded = succeeded + 1
            End If
            total = total + 1
        Next

        RaiseEvent NotifyFinished(succeeded, total)

    End Sub

    ''' <summary>テストメソッドを実行する。</summary>
    '''
    ''' <remarks>
    ''' テスト完了時には、イベント NotifyFinished を発行する。
    ''' テストに失敗したら、その都度、イベント NotifyFailed を発行する。
    ''' テストで何らかの例外が発生したら、イベント NotifyError を発行する。
    ''' </remarks>
    '''
    ''' <param name="test">実行するテストメソッド</param>
    ''' <seealso cref="UnitTest.NotifyFinished" />
    ''' <seealso cref="UnitTest.NotifyFailed" />
    ''' <seealso cref="UnitTest.NotifyError" />
    Private Function runTestMethod(ByVal test As TestMethod) As Boolean

        runTestMethod = False
        Try

            test.invoke()
            runTestMethod = True

        Catch ex0 As Reflection.TargetInvocationException
            ' Invoke で呼んだ先での例外は全て↑の例外でラップ

            If TypeOf ex0.InnerException Is UnitTestAssertException Then
                ' テストに失敗

                Dim ex As UnitTestAssertException = ex0.InnerException

                Dim sf As StackFrame = ex.PlaceTestCode
                Dim mes As String = ex.Message

                Trace.WriteLine(sf.GetFileName & "(" & sf.GetFileLineNumber & ")" & _
                                " : assert: " & mes)

                RaiseEvent NotifyFailed(sf, mes)

            Else
                ' テスト失敗以外の何らかの例外

                RaiseEvent NotifyError(test.method, ex0.InnerException)
            End If
        End Try
    End Function


    ''' <summary>テストの判定を行う</summary>
    '''
    ''' <remarks>
    ''' テストメソッド内で、結果を判定するのに使う。
    ''' </remarks>
    '''
    ''' <param name="result">True = テストOK / False = テストNG</param>
    ''' <param name="mes">テストに失敗した時に使用する何らかのメッセージ</param>
    Public Shared Sub assert(ByVal result As Boolean, _
                             Optional ByVal mes As String = Nothing)
        If Not result Then
            Throw New UnitTest.UnitTestAssertException(New StackFrame(1, True), mes)
        End If
    End Sub

    ''' <summary>テストの判定を行う</summary>
    '''
    ''' <remarks>
    ''' テストメソッド内で、結果が期待する値と等しいかどうかを判定するのに使う。
    ''' </remarks>
    '''
    ''' <param name="o1">期待する値</param>
    ''' <param name="o2">結果</param>
    ''' <param name="mes">テストに失敗した時に使用する何らかのメッセージ</param>
    Public Shared Sub assertEquals(ByVal o1 As Object, ByVal o2 As Object, _
                                   Optional ByVal mes As String = Nothing)

        Dim result As Boolean = False

        If IsNothing(o1) Then
            If IsNothing(o2) Then
                result = True
            End If

        ElseIf o1.Equals(o2) Then
            result = True
        End If

        If IsNothing(mes) Then
            mes = String.Empty
        End If
        mes = mes & " [ " & o1 & " / " & o2 & " ]"

        If Not result Then
            Throw New UnitTestAssertException(New StackFrame(1, True), mes)
        End If
    End Sub

End Class

test で始まるメソッドを用意して、テストコードを書きます。
テストの判定は、UnitTest.assert() または UnitTest.assertEquals() で行います。

Private Sub testXXX()
    Dim target As New XXX()
    
    ' func()の戻り値が True になるのが正しい
    UnitTest.assert(target.func())

    ' getA()の戻り値が 10 になるのが正しい
    UnitTest.assertEquals(10, target.getA())

End Sub

テストの開始は、addTestCase メソッドで、テストコードのあるクラスのインスタンスを追加して、runTest で実行開始です。

    Dim ut As UnitTest

    ut = New UnitTest

    ut.addTestCase(New TestClass1)
    ut.addTestCase(New TestClass2)
    ut.addTestCase(New TestClass3)

    ut.runTest()

テストの結果はイベントで通知します。

' テスト完了時
Private Sub TestFinished(ByVal succeeded As Integer, ByVal total As Integer) _
            Handles ut.NotifyFinished
    MsgBox(succeeded & " / " & total, , "テスト結果")
End Sub

' テスト失敗時
Private Sub TestFailed(ByVal sf As StackFrame, ByVal mes As String) _
            Handles ut.NotifyFailed
    Dim info As String
    info = sf.GetMethod().DeclaringType.Name & "." & _
           sf.GetMethod().Name & _
           " (" & sf.GetFileLineNumber() & ")"

    If Not IsNothing(mes) AndAlso mes.Length > 0 Then
        info = info & vbCrLf & vbCrLf & mes
    End If

    MsgBox(info, , "テスト失敗")
End Sub

' テスト中に例外発生時
Private Sub TestError(ByVal method As Reflection.MethodInfo, ByVal ex As Exception) _
            Handles ut.NotifyError

    Dim mes As String
    mes = method.DeclaringType.Name & "." & method.Name
    mes = mes & vbCrLf & vbCrLf & ex.Message

    MsgBox(mes, , "テストエラー")

End Sub
[飛行機] 今日の一冊
現代語版 The・忠臣蔵

現代語版 The・忠臣蔵

  • 作者: いくみ
  • 出版社/メーカー: 五月書房
  • 発売日: 1991/10
  • メディア: 単行本

タグ:VB.NET .net
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。