2013-08-26 138 views
4

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

我有一个想法。我正在考虑为每个设计不佳的类创建接口,从而暴露出所有功能。那么至少我可以模拟这些课程。

我对单元测试相对较新(我有一个项目的经验,这个项目在正确的地方使用界面非常完善)。这是否是一个好主意,即为所有具体的类创建接口(公开所有函数和子例程),仅用于单元测试?

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

回答

1

是的,这是一个良好的开端,但是,其接口比有依赖注入少一个优先的。如果你所有的遗留类都获得了接口,但是在内部隐藏,它们仍然是相互依赖的,这些类仍然不容易测试。举例来说,假设你有一个看起来像这样两类:

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 

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

不幸的是,虽然向现有类添加接口非常简单,但重构它们以使用依赖注入通常需要更多的工作。您可能需要计划出哪些类最适合首先处理。您可能需要创建一些中介的旧式封装,这些封装符合代码的习惯,因此您在重构代码的过程中不会破坏现有代码。这并不是一件快速简单的事情,但如果你耐心并且长期坚持下去,就有可能做到这一点,你会很高兴你做到了。

+1

感谢DI提醒。 +1。 – w0051977

+1

并提供全面的解答。 – w0051977

+1

谢谢。我将不胜感激,如果你会看看我的数据仓库/ VB.NET问题在这里:http://stackoverflow.com/questions/18495622/data-warehouse-type-solution#18495622。你提供了设计模式类型问题的好答案,所以我想我会问。 – w0051977

1

我会建议你去interface路线,但如果你想支付的解决方案,然后尝试其中之一:

+0

付费路线+1。你知道任何可以用来模拟具体课程的免费工具吗?如果需要,我相信我的公司会支付。 – w0051977

+0

我认为'微软鼹鼠'和'微软假货'做到了,但不确定。 “Microsoft Fakes”也不是真正免费的,因为你必须拥有Visual Studio的Ultimate版才能获得它。 “微软鼹鼠”可能值得一看。 –

0

创建界面测试这些类不是一个坏主意 - 单元测试的目标是在一个类的函数按照预期运行的情况下运行。根据你正在使用的类,这可能比说起来容易 - 如果对全局状态有很多依赖,你将需要相应地进行嘲讽。

考虑到单元测试的重要性,将一些工作放到其中(限制)会使您和您使用的开发人员受益。

3

这是一个难以解决的问题。我认为你在正确的轨道上。你最终会得到一些难看的代码(比如为每个单一类创建header interfaces),但这应该只是一个中间步骤。

我建议投资Working Effectively with Legacy Code的副本。首先,您可以先阅读this distillation

除了Karl的选项(可让您通过拦截模拟)之外,您还可以使用Microsoft Fakes & Stubs。但这些工具不会鼓励您重构代码以遵守SOLID原则。

+0

为书链接+1。 – w0051977

5

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

当您准备好这些测试,你知道,该系统表现,因为它应该而且也在于它具有“外部”质量一定水平(这个意思是你的程序的输入和输出所预计的) 。

一旦您的高级别测试正在运行,您可以尝试将单元测试添加到已存在的类中。

我敢打赌,你会发现自己在需要重构现有的一些类的,如果你希望能够进行单元测试他们,你可以用你的较高水平测试作为一个安全网,将告诉你如果破坏了任何东西。

+0

最后一段为+1。我正在使用测试驱动开发重构代码基础。是否有任何测试工具可以推荐用于集成测试?功能测试或验收测试? – w0051977

+0

对于需要查看几个类一起工作的集成测试,您可以使用其中一个xUnit库。对于更高级别的测试,它将取决于您的程序的界面:看看诸如Selenium Webdriver,Fitnesse等... – jsanchez

+0

我非常同意高级别测试应该是此处的首要任务。修正错误并能够断言系统正常工作通常比改进代码设计更紧迫。我建议坚持一个xUnit框架,只是测试你可以用这个框架实现的最高级别。 –

0

我更喜欢创建接口和类,因为你需要测试的东西,而不是所有的前期。

除了接口,您可以使用一些技术来测试遗留代码。我经常使用的是“Extract and Override”,在这里你可以从其他方法中提取一些“不可测试”的代码片断,并使其覆盖。他们派生你想要测试的类,并用一些传感代码覆盖“不可测试”的方法。

使用模拟框架就像将关键字Overridable添加到方法一样简单,并使用模拟框架设置返回。

你可以在“Working Effectively with Legacy Code”这本书上找到很多技巧。

有关现有代码的一件事情是,有时候最好编写集成测试而不是单元测试。在测试完你的行为后,你可以创建单元测试。

另一个技巧是从模块/类开始的依赖性较少,这样,您就可以更轻松地熟悉代码。

让我知道,如果你需要有关“提取物和覆盖”的例子。)