65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用反射来测试您的代码

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.33/5 (4投票s)

2008年1月20日

CPOL

5分钟阅读

viewsIcon

42020

downloadIcon

417

演示了如何使用反射在已编译的程序集中运行测试函数。

目录

引言

如果你和我一样,拥有一个庞大的代码库,并且许多项目都使用了它,那么当你更改某个依赖项目的一个基类时,总有可能会破坏另一个依赖项目。这是我们在工作中遇到的一个问题,我们所有的项目都使用一个数据访问库,这个库可能会经常被更改,从而可能破坏许多项目。为了解决这个问题,我被指派了为每个类编写一个测试函数,该函数测试该类的所有公共方法。然后,我编写了一个应用程序,可以自动运行所有测试。因此,这篇文章是关于如何使用反射,因为我找不到任何真正能帮助我实现这一点的资料。

设置

项目分为三个部分:一个接口、待测试的项以及测试应用程序。

接口

该接口是一个单一的类库,包含两部分:接口本身和一个公共枚举,我用它来为测试提供结果类型。以下是完整的Testbench.vb代码列表。

Public Interface TestBench

    Function Test(ByVal log As List(Of String)) As TestResult

End Interface

Public Enum TestResult
    Fail = 0
    Fail_Data_Error
    Fail_Exception
    Fail_Timeout

    Pass = 20
    Pass_No_Test_Needed
End Enum

接口只有一个函数,一个`Test`函数,它提供了一个字符串列表用于反馈,并在测试结束时返回一个结果类型。文件中的另一项是一个带填充的枚举。枚举带有填充是为了以后可以添加更多的通过和失败状态。小于 20 的表示失败,大于或等于 20 的表示通过。

测试项

测试项也写得很简单。唯一的要求是对TestBench.dll有项目引用,并且你的类实现该接口。在示例中,我提供了三个类来展示你可以做什么。以下是一个待测试项的代码列表。

Public Class PassingItem
    Implements TestBench.TestBench

    Public Function Test(ByVal log As System.Collections.Generic.List(Of String)) _
           As TestBench.TestResult Implements TestBench.TestBench.Test
        log.Add("This is a test, one that passes")
        Return TestBench.TestResult.Pass
    End Function
End Class

正如你所见,我所做的就是添加接口并按 Enter 键,这会自动将函数放入你的类中。然后,我向日志中添加一个条目,并返回所需的结果类型。如果你在类中拥有该函数,但没有 `Implements` 语句,测试应用程序将无法识别它,因为它会查找实现了该接口的类。

测试应用程序

出于本文的讨论目的,测试应用程序很简单;它包含一个列表框和一个按钮。列表框显示测试日志,按钮启动测试。测试函数的.核心是以下代码。

Dim assembly As Reflection.Assembly = Reflection.Assembly.LoadFrom(path)

lst.Items.Add(String.Format("Assembly {0} Loaded", assembly.FullName))

For Each t As Type In assembly.GetTypes
    If t.IsClass AndAlso t.GetInterface("TestBench") IsNot Nothing Then
        lst.Items.Add(String.Format("---Starting test for {0}", t.Name))

        _log.Clear()

        Dim r As TestBench.TestResult = DirectCast(Activator.CreateInstance(t), _
        TestBench.TestBench).Test(_log)

        lst.Items.AddRange(_log.ToArray)

        lst.Items.Add(String.Format("---Finished Testing {0}, with result: {1}", _
        t.Name, r.ToString))

    End If
Next 

让我们逐行进行分析,看看它们的作用。

工作原理

Dim assembly As Reflection.Assembly = Reflection.Assembly.LoadFrom(path)

这一行创建了一个新的 Assembly,然后将其设置为所选 DLL 的内容。然而,这里有一个小问题:它会锁定 DLL,直到你的应用程序退出。虽然在某些情况下这可能不是问题,但如果你不断地重新编译目标 DLL,这会很烦人,因为 VS 会检测到 DLL 已被锁定并且不会写入输出文件。我解决这个问题的方法是 VB Dot Net Forums 的 John H. 向我提出的建议。他的建议是使用 `System.IO.File` 命名空间中的 `ReadAllBytes()` 函数。这允许你在不锁定 DLL 的情况下加载它。上面那行的替代方法是。

Dim assembly As Reflection.Assembly = Nothing
assembly = Reflection.Assembly.Load(System.IO.File.ReadAllBytes(path))

接下来是循环。

For Each t As Type In assembly.GetTypes
    If t.IsClass AndAlso t.GetInterface("TestBench") IsNot Nothing Then
        '...
    End If
Next 

这会遍历程序集中的所有类型。其中包含许多我们不感兴趣的类型,例如 `TestITem.My.MyProject+MyWebServices` 和 `TestITem.My.Resources.Resources`,因此我们需要检查加载的类型是否为类以及它是否实现了我们的接口。要做到这一点,我们使用 `t.GetInterface()` 并检查它是否不等于 `Nothing`;如果它等于 `Nothing`,则表示接口不存在;如果它不等于 `Null`,则表示我们有一个可测试的类。接下来的部分是测试过程中最重要的部分:创建类的实例并运行 `Test()` 函数。首先,我们创建一个变量来存储测试结果。

Dim r As TestBench.TestResult = Nothing

然后,我们使用 `Activator.CreateInstance()` 函数来创建我们类的实例,并使用 `DirectCast` 将其强制转换为接口,这允许我们执行接口中包含的任何内容,在本例中是 `Test()` 函数。

r = DirectCast(Activator.CreateInstance(t), TestBench.TestBench).Test(_log)

注意事项

我知道这段代码中没有错误处理。这是为了让代码非常易于阅读和理解,而你希望实现的错误保护级别由你自己决定。不错的起点包括实际测试中的超时和空引用异常,并将异常消息记录到日志中。你必须为要测试的类提供一个空构造函数。默认情况下,如果你在类中没有指定任何形式的 `Public Sub Main()`,它将正常工作,但如果你有一个带参数的构造函数,你也必须有一个空的构造函数,即使它只是为了你的测试平台而存在。

结论

希望本文对您有所帮助,并且您可以扩展此处提到的基本方法来为您的代码创建一些自动化测试。基于此方法的代码在工作中非常有用,有助于发现代码中的错误以及可能违反的新的数据库约束。

修订历史

  • 2008 年 1 月 20 日 - 文章发布。
© . All rights reserved.