2016-03-28 67 views
3

我有一个名为A的项目,它有一个名为ClassA的类。 ClassA有一个名为的方法ReadBlock()它创建一个CloudBlockBlob对象并调用其方法之一。单元测试使用外部dll的方法

CloudBlockBlob是它位于Microsoft.WindowsAzure.Storage.Blob命名空间是在Microsoft.WindowsAzure.Storage.dll的类。

我的项目A有一个单元测试项目名为A.Tests。 现在,我想测试方法ReadBlock()。为了测试它,我需要模拟对象并拦截对其方法的调用,返回自定义值并验证方法是否被调用。

  • 如何模拟一个方法内完全创建的对象?
  • 我可以以某种方式更改项目A的dll参考,并将其引用到一个模拟dll创建一个模拟对象,而不是真正的?
  • 我可以覆盖项目一个的呼吁Microsoft.WindowsAzure.Storage.Blob内部类与我自己的A.Tests类的实现?

UPDATE: 的问题是我是否能做到这一点,而无需修改项目一个的代码。

谢谢!

+0

可能最好为CloudBlockBlob创建一个非常简单的可打包包装,以提高代码的可测试性并使用依赖性反转注入它。 –

+2

问题是我是否可以在不修改项目** A **的代码的情况下做到这一点? 我知道一种选择是从外部注入ICloudBlob。 – shlatchz

+0

好的,你没有在问题中指定.. –

回答

5

没有对矫正类A代码你将无法使用Moq的UT方法ReadBlock。您可以使用代码编织工具UT这种方法(MsFakes,Typemock隔离,等...)

例如(MsFakes):

[TestMethod] 
public void TestMethod1() 
{ 
    using (ShimsContext.Create()) 
    { 
     ShimCloudBlockBlob.AllInstances.<the method you want to override> = (<the method arguments>) => {}; 
    } 
} 

里面的using范围,你就可以通过属性AllInstances覆盖CloudBlockBlob具有的任何方法。

在下一节中,我将讨论所有你有其他选项...

选项1:

public class A 
    { 
     private IBlockBlob _blockBlob; 

     public A(IBlockBlob blockBlob) 
     { 
      _blockBlob = blockBlob; 
     } 

     public void ReadBlock() 
     { 
      _blockBlob.DoSomething(); 
     } 
    } 

既然你创建一个新的实例每次调用ReadBlock(你的方法的目前的行为),你最好注入一个工厂,而不是包装,DoSomething应该是create;选项2:

public class A 
    { 
     private readonly IFactoryBlockBlob _blobFctory; 

     public A(IFactoryBlockBlob blobFctory) 
     { 
      _blobFctory = blobFctory; 
     } 

     public void ReadBlock() 
     { 
      var blob = _blobFctory.Create(); 
     } 
    } 

然而,根据你的问题,你的意见,似乎你的类“具有相关性”而不是“需要依赖”。

enter image description here

(马克西门子写DI一个伟大的书,这个图表是从his book拍摄)

有了这个新资料片的方法应该是这样的;方案3:

public class A 
    { 
     public void ReadBlock(ICloudBlob blob) 
     { 
     } 
    } 

但你并不想改变方法的签名:

public class A 
    { 

     public void ReadBlock() 
     { 
      ReadBlock(new CloudBlockBlob(<the params bla bla...>)); 
     } 

     internal void ReadBlock(ICloudBlob blob) 
     { 
     } 
    } 

添加InternalsVisibleToAttribute,然后验证内部方法的行为。

通过阅读,我觉得你的班级是一种“遗留代码”,这意味着它可以完成这项工作,不会改变,并且验证其行为可能浪费时间。在过去,我发布了一张图表(in this answer),可以帮助您决定如何处理这种情况。

+0

感谢您的精心解答! –

3

可能最好为CloudBlockBlob创建一个非常简单的可打包封装,以提高代码的可测试性并使用依赖倒置注入它。

现在你可能有这样的事情:

public class A 
{ 
    public void ReadBlock() 
    { 
     var blockBlob = new CloudBlockBlob(); 
     blockBlob.DoSomething(); 
    } 
} 

相反,以便在CloudBlockBlob的依赖是不知道一个注入你的包装分为A:

public class A 
{ 
    IBlockBlob _blockBlob 

    public A(IBlockBlob blockBlob) 
    { 
     _blockBlob = blockBlob; 
    } 

    public void ReadBlock() 
    { 
     _blockBlob.DoSomething(); 
    } 
} 
+2

问题是我是否可以在不修改项目** A **的代码的情况下做到这一点? – shlatchz

+0

我不认为有什么办法可以实现这一点。如果你无法修改A的代码,那么为什么你需要为它创建测试? –

+2

我只是不希望项目** A **的代码复杂一点,以便能够测试某些内容。 除了测试之外,没有其他用途用于该注入,它使代码更加复杂。 – shlatchz

1

免责声明,我在Typemock工作。

您可以使用隔离器修改项目A的代码。 有一个简单的例子,它如何完成:

public class Foo 
{ 
    public void ReadBlock() 
    { 
     var block = new CloudBlockBlob(new Uri("http://myUrl/%2E%2E/%2E%2E")); 
     var name = block.Name; 
    } 
} 

[TestMethod, Isolated] 
public void TestReadBlock() 
{ 
    //Arrange 
    var fakeBlock = Isolate.Fake.AllInstances<CloudBlockBlob>(); 
    Isolate.WhenCalled(() => fakeBlock.Name).WillReturn("Name"); 

    //Act 
    var foo = new Foo(); 
    foo.ReadBlock(); 

    //Assert 
    Isolate.Verify.WasCalledWithAnyArguments(() => fakeBlock.Name); 
} 

希望它有帮助!