单元测试使用外部 dll 的方法
Unit testing a method that uses an external dll
我有一个名为 A 的项目,其中有一个名为 ClassA 的 class。
ClassA 有一个名为 ReadBlock() 的方法,它创建一个 CloudBlockBlob 对象并调用其中一个方法。
CloudBlockBlob 是一个 class 位于 Microsoft.WindowsAzure.Storage.Blob 命名空间中 Microsoft.WindowsAzure.Storage.dll.
我的项目 A 有一个名为 A.Tests 的单元测试项目。
现在,我想测试方法 ReadBlock()。为了测试它,我需要模拟 CloudBlockBlob 对象并拦截对其方法的调用,return 自定义值并验证是否调用了这些方法。
- 如何模拟在方法内完全创建的对象?
- 我能否以某种方式更改项目 A 的 dll 引用并将其引用到创建模拟对象而不是真实对象的模拟 dll?
- 我可以用一个实现覆盖项目 A 对 Microsoft.WindowsAzure.Storage.Blob 中的 classes 的调用吗我自己在 A.Tests class?
更新:
问题是我是否可以在不修改项目 A 的代码的情况下做到这一点。
谢谢!
最好为 CloudBlockBlob 创建一个非常简单的可模拟包装器以提高代码的可测试性并使用依赖倒置注入它。
现在你可能有这样的东西:
public class A
{
public void ReadBlock()
{
var blockBlob = new CloudBlockBlob();
blockBlob.DoSomething();
}
}
相反,将包装器注入 A,这样 A 就不知道对 CloudBlockBlob 的依赖:
public class A
{
IBlockBlob _blockBlob
public A(IBlockBlob blockBlob)
{
_blockBlob = blockBlob;
}
public void ReadBlock()
{
_blockBlob.DoSomething();
}
}
如果不修改 class A
代码,您将无法使用 Moq 对 ReadBlock
方法进行 UT。您将能够使用代码编织工具(MsFakes、Typemock Isolator 等)对该方法进行 UT
例如(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();
}
}
但是,根据您的问题和评论,您的 class 似乎是 'has a dependency' 而不是 'needs a dependency'。
(Mark Siemens 写了一本关于 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
,然后验证内部方法的行为。
从字里行间,我觉得你的 class 是一种 "legacy code" 意味着它可以完成工作,不会改变,验证它的行为可能是浪费时间。之前我贴过一张图表(),可以帮助你决定如何处理这种情况。
免责声明,我在 Typemock 工作。
无需修改项目 A 的代码即可使用 Isolator。
有一个如何完成的简单示例:
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);
}
希望对您有所帮助!
我有一个名为 A 的项目,其中有一个名为 ClassA 的 class。 ClassA 有一个名为 ReadBlock() 的方法,它创建一个 CloudBlockBlob 对象并调用其中一个方法。
CloudBlockBlob 是一个 class 位于 Microsoft.WindowsAzure.Storage.Blob 命名空间中 Microsoft.WindowsAzure.Storage.dll.
我的项目 A 有一个名为 A.Tests 的单元测试项目。 现在,我想测试方法 ReadBlock()。为了测试它,我需要模拟 CloudBlockBlob 对象并拦截对其方法的调用,return 自定义值并验证是否调用了这些方法。
- 如何模拟在方法内完全创建的对象?
- 我能否以某种方式更改项目 A 的 dll 引用并将其引用到创建模拟对象而不是真实对象的模拟 dll?
- 我可以用一个实现覆盖项目 A 对 Microsoft.WindowsAzure.Storage.Blob 中的 classes 的调用吗我自己在 A.Tests class?
更新: 问题是我是否可以在不修改项目 A 的代码的情况下做到这一点。
谢谢!
最好为 CloudBlockBlob 创建一个非常简单的可模拟包装器以提高代码的可测试性并使用依赖倒置注入它。
现在你可能有这样的东西:
public class A
{
public void ReadBlock()
{
var blockBlob = new CloudBlockBlob();
blockBlob.DoSomething();
}
}
相反,将包装器注入 A,这样 A 就不知道对 CloudBlockBlob 的依赖:
public class A
{
IBlockBlob _blockBlob
public A(IBlockBlob blockBlob)
{
_blockBlob = blockBlob;
}
public void ReadBlock()
{
_blockBlob.DoSomething();
}
}
如果不修改 class A
代码,您将无法使用 Moq 对 ReadBlock
方法进行 UT。您将能够使用代码编织工具(MsFakes、Typemock Isolator 等)对该方法进行 UT
例如(MsFakes):
[TestMethod]
public void TestMethod1()
{
using (ShimsContext.Create())
{
ShimCloudBlockBlob.AllInstances.<the method you want to override> = (<the method arguments>) => {};
}
}
在 using
范围内,您将能够通过 属性 AllInstances
.
在下一节中,我将讨论您拥有的所有其他选项...
选项 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();
}
}
但是,根据您的问题和评论,您的 class 似乎是 'has a dependency' 而不是 'needs a dependency'。
(Mark Siemens 写了一本关于 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
,然后验证内部方法的行为。
从字里行间,我觉得你的 class 是一种 "legacy code" 意味着它可以完成工作,不会改变,验证它的行为可能是浪费时间。之前我贴过一张图表(
免责声明,我在 Typemock 工作。
无需修改项目 A 的代码即可使用 Isolator。 有一个如何完成的简单示例:
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);
}
希望对您有所帮助!