分享到
梦想成现实:用xUnit.net在单元测试中实现构造函数依赖注入
 
作者:dudu,发布于2011-12-22
 

自从将开发架构迁移至DDD(领域驱动开发),就开始正式使用“构造函数依赖注入”,由CNBlogs.Infrastructure.CrossCutting.IoC(IoC容器抽象层,目前默认容器用的是Unity)负责。

通过构造函数进行依赖注入,避免了在代码中针对所依赖的接口创建相应的实例,这个工作改由IoC容器在运行时自动完成。我们主要在APS.NETMVC的Controller与应用层的服务实现中使用。

比如下面的APS。NETMVCController示例代码:

publicclassAdminController:Controller

{

    privateIBlogSiteManagementService_blogSiteService;

    publicAdminController(IBlogSiteManagementServiceblogSiteService)

    {

        _blogSiteService=blogSiteService;

    }

}

ASP.NETMVC会在运行时通过IoC容器获取IBlogSiteManagementService的实现,并传递给AdminController的构造函数。(这个操作是在DefaultControllerFactory.Create方法中完成的,参见ASP.NETMVC源代码DefaultControllerFactory.cs第259行)

注:要让ASP.NETMVC支持依赖注入,需要实现IDependencyResolver接口,并将所有MVC自带的IController的实现注册到你所用的IoC容器。

自从成功用上了依赖注入,差点成为依赖注入“控”,看到需要依赖的地方,就想着注入。

爱情是不是也可以依赖注入?在你心中声明一下你心仪的女孩的样子,然后丘比特就会给你注入。。。

进入正题。。。

改变博客园团队开发方式的不仅是DDD(领域驱动开发),还有TDD(测试驱动开发)。有了轻微的依赖注入“控”,在写测试代码时,我们不由自主地想到了测试类的构造函数依赖注入。

可是尝试了几个主流的.NET测试框架(MsTest,NUnit,MbUnit,xUnit.net),连带参数的构造函数都不支持,更别谈构造函数依赖注入。

搜遍互联网,也没发现有人试图解决这个问题。

刚开始TDD时,我们就被这个问题困扰,似乎是一个暂时无法解决的问题。。。于是,这就成为了一个可望而不可及的梦想。

不能注入,那只能在无参构造函数中手动获取所依赖接口的实现实例,示例代码(用的是xUnit.net)如下:

publicclassMyPostList

{

    privateIBlogSiteManagementService_blogSiteService;

    publicMyPostList()

    {

        _blogSiteService=IoCFactory.Instance.CurrentContainter

        .Resolve<IBlogSiteManagementService>();

    }

    [Fact]publicvoidGet_My_Recent_Admin_Posts()

    {

        Assert.NotNull(_blogSiteService);

    }

}

昨天开始,我们决定攻克这个难题,比较了几个测试框架,最终选择了xUnit.net(MsTest由于没有开源,根本没考虑)。

选择的理由是xUnit.net是NUnit的开发者开发的,扩展性很好。

解决的思路很简单:找到测试运行时测试类的实例化是在哪进行的。

有了源代码,让这个寻找过程轻松了很多。

xUnit.net在VisualStudio中也是通过TestDriven.net加载的,所以打开xUnit的源代码,直奔主题,进入xunit.runner.tdnet项目,打开TdNetRunner.cs,然后顺藤摸瓜:

Xunit.TestRunner()>Xunit.ExecutorWrapper.RunClass()>

Xunit.Sdk.Executor.RunTests()>TestClassCommandRunner.Execute()>

TestCommandFactory.Make()>LifetimeCommand.Execute()

在Xunit.Sdk.LifetimeCommand.Execute(object testClass)中发现了瓜:

publicoverrideMethodResultExecute(objecttestClass)

{

    if(testClass==null)

    testClass=method.CreateInstance();

}

method.CreateInstance()实际调用的代码是这样的:

publicobjectCreateInstance()

{

    returnActivator.CreateInstance(method.ReflectedType);

}

一看代码就知道为什么只支持无参的构造函数。

看到瓜,就容易找到解决方法,既然在testClass==null时才会创建测试类的实例,如果我们通过IoC容器在这个方法执行前创建testClass的实例并传递LifetimeCommand.Execute方法,不就可以解决问题了吗?

所以,继续顺藤摸瓜,找出在哪里调用了LifetimeCommand.Execute(objecttestClass)方法。。。

在Xunit.Sdk.TestClassCommandRunner.Execute()中找到:

MethodResultmethodResult=command.Execute(testClassCommand.ObjectUnderTest:;

testClassCommand的类型是ITestClassCommand接口,该接口的实现是Xunit.Sdk.TestClassCommand,看看它的ObjectUnderTest属性:

public object ObjectUnderTest

{????

    get { return null; }

}

只要这里从IoC容器返回测试类的实例,就可以实现依赖注入了。

进入解决方案...

注:解决方案不需要修改任何xUnit.net的源代码。

在单元测试项目中创建一个实现ITestClassCommand接口的IocTestClassCommand类,代码如下:

publicclassIocTestClassCommand:ITestClassCommand

{

    TestClassCommand_testClassCommand;

    publicIocTestClassCommand()

    :this((ITypeInfo)null){

  }

  publicIocTestClassCommand(TypetypeUnderTest)

  :this(Reflector.Wrap(typeUnderTest)){

  }

  publicIocTestClassCommand(ITypeInfotypeUnderTest)

  {

    _testClassCommand=newTestClassCommand(typeUnderTest);

  }

  publicobjectObjectUnderTest

  {

    get{returnIoCFactory.Instance.CurrentContainter.

    Resolve(_testClassCommand.TypeUnderTest.Type);}

  }

  publicRandomRandomizer

  {

    get{return_testClassCommand.Randomizer;}

    set{_testClassCommand.Randomizer=value;}

  }

  publicITypeInfoTypeUnderTest

  {

    get{return_testClassCommand.TypeUnderTest;}

    set{_testClassCommand.TypeUnderTest=value;}

  }

  [SuppressMessage(“Microsoft.Design”,“CA1062:Validateargumentsofpublicmethods”,

  MessageId=“0”,Justification=“Thisparameterisverifiedelsewhere.”)]

  publicintChooseNextTest(ICollection<IMethodInfo>testsLeftToRun)

  {

    return_testClassCommand.Randomizer.Next(testsLeftToRun.Count);

  }

  publicExceptionClassFinish()

  {

    return_testClassCommand.ClassFinish();

  }

  publicExceptionClassStart()

  {

    return_testClassCommand.ClassStart();

  }

  publicIEnumerable<ITestCommand>EnumerateTestCommands(IMethodInfotestMethod)

  {

    return_testClassCommand.EnumerateTestCommands(testMethod);

  }

  publicIEnumerable<IMethodInfo>EnumerateTestMethods()

  {

    return_testClassCommand.EnumerateTestMethods();}publicboolIsTestMethod(IMethodInfotestMethod)

  {

    return_testClassCommand.IsTestMethod(testMethod);

  }

}

红色字体部分就是实现依赖注入的代码,其他都是为了实现ITestClassCommand接口,将方法调用转发给Xunit.Sdk.TestClassCommand。

关键问题解决,但大功还没造成,还有两个问题需要解决:

1. 如何使用自己定义的IocTestClassCommand?

2. 如何向IoC容器注册所依赖接口的实现?

我们需要定义一个Attribute,继承自Xunit.RunWithAttribute,名叫IoCTestClassCommandAttribute,代码如下:

publicclassIoCTestClassCommandAttribute:RunWithAttribute

{

  publicIoCTestClassCommandAttribute()

  :base(typeof(IocTestClassCommand))

  {

    IBootStrapperbootStrapper=newDefaultBootStrapper();

    bootStrapper.Boot();

  }

}

bootStrapper.Boot是为了解决第二个问题,在其中完成IoC容器的注册。BootStrapper是一个独立的项目,ASP.NETMVC项目也是调用这个接口进行IoC容器的注册,这样实现了单元测试项目与Web项目对IoC注册的重用。

IoCTestClassCommandAttribute在构造函数中将IocTestClassCommand的类型信息传递给父类RunWithAttribute,解决了第一个问题。

进入激动人心的时刻。。。

如何在测试类中使用这个特性?

[IoCTestClassCommand]

publicclassMyPostList

{

  privateIBlogSiteManagementService_blogSiteService;

  publicMyPostList(IBlogSiteManagementServiceblogSiteService)

  {

    _blogSiteService=blogSiteService;

  }

  [Fact]

  publicvoidGet_My_Recent_Admin_Posts()

  {

   Assert.NotNull(_blogSiteService);

  }

}

只要在测试类上加上[IoCTestClassCommand]属性即可。

运行测试:

大功造成!只要坚持,梦想总能成现实!


相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践


LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...   
 
 
 
 
 
 
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号