【问题标题】:Unit testing a method that uses an external dll对使用外部 dll 的方法进行单元测试
【发布时间】:2016-03-28 14:19:05
【问题描述】:

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

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

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

  • 如何模拟完全在方法中创建的对象?
  • 我能否以某种方式更改项目 A 的 dll 引用并将其引用到创建模拟对象而不是真实对象的模拟 dll 中?
  • 我能否用我自己在 A.TestsAMicrosoft.WindowsAzure.Storage.Blob 内的类的调用/strong> 上课?

更新: 问题是我是否可以在不修改项目 A 的代码的情况下做到这一点。

谢谢!

【问题讨论】:

  • 最好为 CloudBlockBlob 创建一个非常简单的可模拟包装器,以提高代码的可测试性并使用依赖反转注入它。
  • 问题是我是否可以在不修改项目A的代码的情况下做到这一点?我知道一种选择是从外部注入 ICloudBlob。
  • 啊,好吧,你没有在问题中指定..

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


【解决方案1】:

如果不修改类 A 代码,您将无法使用 Moq UT ReadBlock 方法。您将能够使用代码编织工具(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();
        }
    }

但是,根据您的问题和您的 cmets,您的班级似乎“有依赖关系”而不是“需要依赖关系”。

(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,然后验证内部方法的行为。

通过阅读字里行间,我觉得您的课程是一种“遗留代码”,这意味着它可以完成工作,不会改变,验证它的行为可能是浪费时间。过去我曾发布过一张图表 (in this answer),可以帮助您决定处理此案的方式。

【讨论】:

  • 感谢您的详尽回答!
【解决方案2】:

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

【讨论】:

  • 问题是我是否可以在不修改项目A的代码的情况下做到这一点?
  • 我不认为有任何方法可以实现这一目标。如果你无法修改 A 的代码,那你为什么要为它创建测试呢?
  • 我只是不希望项目 A 的代码为了能够测试某些东西而复杂两倍。除了测试之外,该注入没有其他用途,并且它使代码变得比应有的复杂程度更高。
  • 依赖倒置也大大提高了代码的可读性和可维护性。如果您的测试很重要,那么 DI 值得付出代价
  • @GlenThomas 非常有帮助,谢谢。
【解决方案3】:

免责声明,我在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);
}

希望对你有帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-16
    • 2021-10-15
    • 2016-06-27
    相关资源
    最近更新 更多