So-net無料ブログ作成
検索選択

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
  • メディア: 単行本

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

nice! 0

コメント 0

コメントを書く

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

トラックバック 0