单元测试具体类

w00*_*977 4 vb.net unit-testing

我继承了一个没有接口或抽象类的项目,即只有具体的类,我想引入单元测试.这些类包含许多函数,包含业务逻辑和数据逻辑; 打破了SOLID的所有规则(http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29).

我有一个想法.我正在考虑为每个设计不良的类创建接口,公开所有功能.那么至少我可以模拟课程.

我对单元测试比较陌生(我有一个项目经验,在正确的地方使用接口非常发达).这样做是一个好主意,即为所有具体类创建接口(公开所有函数和子例程),仅用于单元测试?

我花了一些时间研究这个,但我没有找到答案.

jsa*_*hez 5

如果您的项目根本没有测试,那么在添加任何单元测试之前,我宁愿创建更高级别的测试(即验收,功能和/或集成测试).

当你进行这些测试时,你知道系统的行为应该是它应该的,并且它具有一定程度的"外部"质量(意味着你的程序的输入和输出是预期的).

一旦您的高级测试工作,您可以尝试将单元测试添加到已存在的类.

我敢打赌,如果您希望能够对它们进行单元测试,您将发现自己需要重构一些现有的类,这样您就可以使用高级测试作为安全网,告诉您是否已经破坏了任何东西.


Ste*_*art 2

是的,这是一个好的开始,但是,拥有接口的优先级不如注入依赖项。如果所有遗留类都获得接口,但隐藏在内部,它们仍然是相互依赖的,那么这些类仍然不会更容易测试。例如,假设您有两个如下所示的类:

Public Class LegacyDataAccess
    Public Function GetAllSales() As List(Of SaleDto)
        ' Do work with takes a long time to run against real DB
    End Function
End Class

Public Class LegacyBusiness
    Public Function GetTotalSales() As Integer
        Dim dataAccess As New LegacyDataAccess()
        Dim sales As List(Of SaleDto) = dataAccess.GetAllSales()
        ' Calculate total sales
    End Function
End Class
Run Code Online (Sandbox Code Playgroud)

我知道你已经在说什么...... “我希望遗留代码至少分层得那么好”,但是让我们使用它作为一些难以测试的遗留代码的示例。难以测试的原因是代码会访问数据库并在数据库上执行耗时的查询,然后计算结果。因此,为了在当前状态下测试它,您需要首先将一堆测试数据写入数据库,然后运行代码以查看它是否根据插入的数据返回正确的结果。必须编写这样的测试是有问题的,因为:

  • 编写代码来设置测试很痛苦
  • 该测试将很脆弱,因为它取决于外部数据库是否正常工作以及包含所有正确的支持数据
  • 测试运行时间太长

正如您所观察到的,接口对于单元测试非常重要。因此,按照您的建议,让我们添加接口,看看它是否更容易测试:

Public Interface ILegacyDataAccess
    Function GetAllSales() As List(Of SaleDto)
End Interface

Public Interface ILegacyBusiness
    Function GetTotalSales() As Integer
End Interface

Public Class LegacyDataAccess
    Implements ILegacyDataAccess

    Public Function GetAllSales() As List(Of SaleDto) _
            Implements ILegacyDataAccess.GetAllSales
        ' Do work with takes a long time to run against real DB
    End Function
End Class

Public Class LegacyBusiness
    Implements ILegacyBusiness

    Public Function GetTotalSales() As Integer _
            Implements ILegacyBusiness.GetTotalSales
        Dim dataAccess As New LegacyDataAccess()
        Dim sales As List(Of SaleDto) = dataAccess.GetAllSales()
        ' Calculate total sales
    End Function
End Class
Run Code Online (Sandbox Code Playgroud)

现在我们有了接口,但实际上,这如何使它更容易测试呢?现在我们可以轻松创建一个模拟数据访问对象,它实现相同的接口,但这并不是真正的核心问题。问题是,我们如何让业务对象使用模拟数据访问对象而不是真实的数据访问对象?为此,您需要通过引入依赖注入将重构提升到一个新的水平。真正的罪魁祸首是New业务类下面一行中的关键字:

Dim dataAccess As New LegacyDataAccess()
Run Code Online (Sandbox Code Playgroud)

业务类显然依赖于数据访问类,但目前它隐藏了这一事实。它在依赖关系上撒了谎。它说,来吧,这很简单,只需调用这个方法,我就会返回结果——仅此而已。事实上,需要的远不止于此。现在,假设我们阻止它谎报其依赖项,并使其毫不掩饰地陈述它们,如下所示:

Public Class LegacyBusiness
    Implements ILegacyBusiness

    Public Sub New(dataAccess As ILegacyDataAccess)
        _dataAccess = dataAccess
    End Sub

    Private _dataAccess As ILegacyDataAccess

    Public Function GetTotalSales() As Integer _
            Implements ILegacyBusiness.GetTotalSales
        Dim sales As List(Of SaleDto) = _dataAccess.GetAllSales()
        ' Calculate total sales
    End Function
End Class
Run Code Online (Sandbox Code Playgroud)

现在,正如你所看到的,这个类有很多容易测试。我们不仅可以轻松创建模拟数据访问对象,而且现在我们可以轻松地将模拟数据访问对象注入到业务对象中。现在我们可以创建一个模拟,它可以快速、轻松地返回我们希望它返回的数据,然后查看业务类是否返回正确的计算——不涉及数据库。

不幸的是,虽然向现有类添加接口很容易,但重构它们以使用依赖项注入通常需要做更多的工作。您可能需要计划首先解决哪些课程最有意义。您可能需要创建一些中间的老式包装器,它们按照代码过去的方式工作,这样您在重构代码的过程中就不会破坏现有代码。这不是一件快速而容易的事情,但如果你有耐心并长期坚持,是有可能做到的,而且你会很高兴你做到了。