桩破除依赖2-构造函数注入

在构造函数层注入一个伪对象(构造函数注入)

代码地址:http://git.oschina.net/zhv/UnitTest

这个方法需要给被测试类添加一个新的构造函数,或给已有的构造函数添加一个新的参数,传入一个之前抽取出来的接口(IExtensionManager)类型对象。然后在被测试类中添加一个这个接口类型的局部字段,把传入的对象付给这个局部字段,供被测试方法或其他方法使用。

ClassUnderTest(IExtensionManager mgr) 

{ m_manager = mgr }

IExtensionManager m_manager;

IsValidFileName(string)
{
    if(m_manager.isvalid(file))
    ....
}

输入图片说明

以下是构造注入的代码

class LogAnalyzerConstructorInject
{
    //定义局部字段
    private IExtensionManager manager;

    //定义测试代码可以调用的构造函数
    public LogAnalyzerConstructorInject(IExtensionManager mgr)
    {
        manager = mgr;
    }

    public bool IsValidLogFileName(string fileName)
    {
        //使用构造函数传入的IExtensionManager类
        return manager.IsValid(fileName);
    }
}

public interface IExtensionManager
{
    bool IsValid(string fileName);
}

[TestFixture]
public class LogAnalyzerTests
{
    [Test]
    public void IsValidFileName_NameSupportedExtension_ReturnsTrue()
    {
        //准备一个返回true的桩
        FakeExtensionManager myFakeManager =
            new FakeExtensionManager();
        myFakeManager.WillBeValid = true;

        //传入桩
        LogAnalyzerConstructorInject log =
            new LogAnalyzerConstructorInject(myFakeManager);
        bool result = log.IsValidLogFileName("short.ext");
        Assert.True(result);
    }
}

//定义一个最简单的桩
internal class FakeExtensionManager : IExtensionManager
{
    public bool WillBeValid = false;

    public bool IsValid(string fileName)
    {
        return WillBeValid;
    }
}

使用构造函数注入伪对象可能会带来问题:

  • 如果被测试代码需要放置多个桩才能在没有依赖项的情况下正常工作,加入越来越多的构造函数或越来越多的构造函数参数,就变得很困难,还会降低代码可读性和可维护性。

    • public LogAnalyzer(IExtensionManager mgr, Ilog logger, IWebService service)这样多的参数会降低类的可维护性
  • 解决方法是创建一个特殊类,包含初始化一个类的所有值。依赖项多的话,这个类还是会失控。

  • 另一个方案是使用控制反转(Inversion of Control IoC)容器。

用伪对象模拟异常

首先给之前的FakeExtensionManager增加抛出异常的桩,然后再添加测试代码,注入代码中。 如果需要测试通过,就需要在被测试代码

//测试抛出异常的情况
[Test]
public void IsValidFileName_ExtManagerThrowsException_ReturnFalse()
{
    FakeExtensionManager myFakeManager = new FakeExtensionManager();
    myFakeManager.WillThrow = new Exception("This is fake");

    LogAnalyzerConstructorInject log =
        new LogAnalyzerConstructorInject(myFakeManager);
    bool result = log.IsValidLogFileName("anything.anyextension");
    Assert.False(result);
}

//定义一个最简单的桩,具有返回true、false和抛出异常的功能
internal class FakeExtensionManager : IExtensionManager
{
    //模拟返回true或false用的,需要测试时赋值
    public bool WillBeValid = false;

    //模拟返回异常的,需要测试时赋值
    public Exception WillThrow = null;
    public bool IsValid(string fileName)
    {
        if (WillThrow != null) { throw WillThrow; }

        return WillBeValid;
    }
}

增加了try-catch的被测代码

class LogAnalyzerConstructorInject
{
    //定义局部字段
    private IExtensionManager manager;

    //定义测试代码可以调用的构造函数
    public LogAnalyzerConstructorInject(IExtensionManager mgr)
    {
        manager = mgr;
    }

    public bool IsValidLogFileName(string fileName)
    {
        使用构造函数传入的IExtensionManager类
        //return manager.IsValid(fileName);

        //给上面代码增加try-catch块
        try
        {
            return manager.IsValid(fileName);
        }
        catch
        {
            return false;
        }
    }
}

使用构造函数注入的时机

如果想告诉API的使用者某些参数是必须的,在构造函数中使用这些参数是一个极好的办法,这些参数必须在创建对象时传入。 而如果想让这些依赖成为可选项,就可以使用属性注入的方法。

转载于:https://my.oschina.net/zhv/blog/908504

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值