是的,这是一个好的开始,但是,拥有接口的优先级低于注入依赖项。如果您的所有遗留类都获得了接口,但在内部隐藏它们仍然是相互依赖的,那么这些类仍然不会更容易测试。例如,假设您有两个如下所示的类:
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
我知道你已经在说什么了…… “我希望遗留代码至少分层得那么好”,但是让我们用它作为一些难以测试的遗留代码的例子。很难测试的原因是代码到达数据库并在数据库上执行耗时的查询,然后从中计算结果。因此,为了在当前状态下对其进行测试,您需要首先将一堆测试数据写入数据库,然后运行代码以查看它是否根据插入的数据返回正确的结果。必须编写这样的测试是有问题的,因为:
- 编写代码来设置测试很痛苦
- 测试会很脆弱,因为它依赖于外部数据库是否正常工作以及是否包含所有正确的支持数据
- 测试运行时间过长
正如您正确观察到的,接口对于单元测试非常重要。因此,按照您的建议,让我们添加接口以查看它是否更容易测试:
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
所以现在我们有了接口,但实际上,这如何使它更容易测试呢?现在我们可以轻松地创建一个模拟数据访问对象,它实现了相同的接口,但这并不是真正的核心问题。问题是,我们如何让业务对象使用模拟数据访问对象而不是真实的?为此,您需要通过引入依赖注入将重构提升到一个新的水平。真正的罪魁祸首是New
业务类以下行中的关键字:
Dim dataAccess As New LegacyDataAccess()
业务类显然依赖于数据访问类,但目前它隐藏了这一事实。它在撒谎它的依赖关系。就是说,来吧,很简单,只要调用这个方法,我就会返回结果——就是这样。真的,它需要的远不止这些。现在,假设我们阻止它谎报它的依赖关系,并让它毫不掩饰地声明它们,如下所示:
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
现在,如您所见,这个类更容易测试。我们不仅可以轻松创建模拟数据访问对象,而且现在我们可以轻松地将模拟数据访问对象注入到业务对象中。现在我们可以创建一个模拟,它可以快速轻松地准确返回我们希望它返回的数据,然后查看业务类是否返回正确的计算——不涉及数据库。
不幸的是,虽然向现有类添加接口是一件轻而易举的事,但重构它们以使用依赖注入通常需要更多的工作。您可能需要计划首先解决哪些课程最有意义。您可能需要创建一些中间的老式包装器,它们按照过去的代码方式工作,因此在重构代码的过程中不会破坏现有代码。这不是一件快速而容易的事情,但如果你有耐心并且长期坚持下去,是有可能做到的,你会很高兴你做到了。