结合测试驱动领悟依赖注入的正确使用

  最近正在给一个项目扩展一些功能,这个项目在处理依赖关系的时候使用了Spring.net,但是最让人头痛的是这个项目没有单元测试,这就让我不敢对代码进行重构,开发效率极其低下,最后还是下定决心还是给这个“遗留代码”添加单元测试吧。虽然这事后写单元测试的过程很是不爽,但是更郁闷的是写着写着发现有些代码是很难测试的?

  来看这样一段代码:

 public class HumanResourceService : IHumanResourceService
  {
        protected IDepartmentRepository DepartmentRepository { get; set; }

        protected IEmployeeRepository EmployeeRepository { get; set; }

        protected IRoleRepository RoleRepository { get; set; }

        protected ISkillRepository SkillRepository { get; set; }

        protected IEmployeeSkillRepository EmployeeSkillRepository { get; set; }

        protected ITrainingRepository TrainingRepository { get; set; }

        protected ITrainingReplyRepository TrainingReplyRepository { get; set; }

        protected ISessionData SessionData { get; set; }

        protected IMenuRepository MenuRepository { get; set; }

        protected IPermissionRepository PermissionRepository { get; set; }

        protected IDictionary<SkillCategory, IGetSkillDataesStrategy> SkillDataesStgDic { get; set; }

        protected IGetSkillDataesStrategy DefaultSkillDataesStrategy { get; set; }
}

  上述的代码中所依赖的属性都是通过Spring.net注入的,并且让我很头疼的是这些属性都是保护级别,那我怎么去写这个类的单元测试?

  方案一:可能你开始的时候,会跟我一样犯这样一个错误,我开始的观念的就是,不去修改本来的实现代码,通过反射去mock这些依赖,像这样

        [Test]
        public void TestIsCanCreateCloseService()
        {
            HumanResourceService hs = new HumanResourceService();
            //mock the DepartmentRepository 
            Mock<IDepartmentRepository> mockIDepartRepository = new Mock<IDepartmentRepository>();
            //set up some method by moq

            //reflector to set the dependency
            PropertyInfo idrPropInfo =
                typeof(HumanResourceService).GetProperty("DepartmentRepository", BindingFlags.NonPublic|BindingFlags.Instance);

            if (idrPropInfo == null)
            {
                idrPropInfo.SetValue(mockIDepartRepository.Object, hs, null);
            }

        }

  问题:先抛开反射的效率的问题,最为主要的问题是你很难对反射的代码进行重构,还有这里我们需要利用反射才能对依赖进行mock,一定是我们的设计出了问题,我们应该提供给客户端一个方便注入依赖的方法,那看来我们需要对原来实现的代码进行修改?

  等等,为测试修改实现代码?很多人会疑惑,为什么我仅仅为了方便测试需要去修改代码?当然我也疑惑过,也犹豫过该不该这样做?这样显然是必要,因为我们所做的事情是为了提高代码的质量,为什么不这样做呢?同时插个题外话,这里我还体会到,如果我用测试驱动去开发,就会在设计阶段避免这个问题,在设计阶段修改代码显然代码要小的多(因为有可能我这个时候还没有写实现代码!)好的,既然事已如此,虽然要花相对大的代价去修改代码,但是庆幸的是至少我们得出了一个结论,方案一采用反射不是一个很好的解决方法,我们需要从实现代码下手。

  思考:怎么去修改代码方便我们注入呢?其实这个问题很简单,让我们回顾一下依赖注入的三种方式:

  1)构造函数注入

  2)setter注入

  3)接口注入

  第三种接口注入的方法过于复杂,所以我们往往就不使用这种方法,所以:

  1)构造函数注入,即为函数添加一个构造函数具有上述所有的参数(那是多恐怖的参数,太长?我们等会来谈论这个问题)。

  2) setter 注入,即将原来的保护级的访问级别修改为Public.

  我们用哪一种注入方法好一些呢?

  我个人更喜欢也觉得构造函数的注入方法要好些

  1.它能保证对象按你期望的方式构造,2.我觉得所依赖的对象就该让客户端实实在在的知道。

  至于setter注入,客户端必须清楚以哪种顺序进行注入,会出现注入不完全的情况!

  好的,那我们就给类添加一个构造函数吧?

  难道我要添加一个参数那么长的构造函数?等等,我们还是思考一下是不是我们的设计出现问题了?对,的确是设计出现问题了,往往可以考虑是否职责划分清晰,我们的这个类承担的太多的职责!到这里能意识是设计的问题,我还是会感到很欣慰,但是这个时候我才去修改代码结构,是不是代价太大了?如果我采用的测试驱动开发,我一开始就会采用构造函数注入的方法,当我发现参数列表在增长的时候,我就会及时地得到反馈,以代价很小的方式就解决了这个问题。

  经历了这次思考,让我体会到怎么样去合理地使用IOC容器。更让我对测试驱动就这个问题有了更深的认识:

  1.测试驱动开发,能保证我的代码是可测的

  2.它能及时地给我反馈,让我在早期发现问题。

posted @ 2012-07-26 21:58  _小阳  阅读(473)  评论(0编辑  收藏  举报