单元测试使用外部dll的方法

时间:2016-03-28 14:19:05

标签: c# unit-testing mocking moq azure-storage-blobs

我有一个名为 A 的项目,其项目名为 ClassA ClassA 有一个名为 ReadBlock()的方法,它会创建一个 CloudBlockBlob 对象并调用其中一个方法。

CloudBlockBlob 是一个位于 Microsoft.WindowsAzure.Storage.Blob 命名空间的类,位于 Microsoft.WindowsAzure.Storage.dll

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

  • 如何模拟在方法内完全创建的对象?
  • 我可以以某种方式更改项目 A 的dll引用,并将其引用到创建模拟对象而非真实对象的模拟dll中吗?
  • 我可以覆盖项目 A Microsoft.WindowsAzure.Storage.Blob 中的类的调用,并在 A中使用我自己的实现.Tests class?

更新 问题是我是否可以在不修改项目 A 的代码的情况下执行此操作。

谢谢!

3 个答案:

答案 0 :(得分:5)

如果不修改类A代码,您将无法使用Moq来使用ReadBlock方法。您将能够使用代码编织工具(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),可以帮助您决定处理此案例的方法。

答案 1 :(得分:3)

最好为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();
    }
}

答案 2 :(得分:1)

免责声明,我在Typemock工作。

您可以在不使用Isolator修改项目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);
}

希望它有所帮助!