.NET Core TDD 前传: 编写易于测试的代码 -- 构建对象
该系列第1篇: 讲述了如何创造"缝".? "缝"(seam)是需要知道的概念. 本文是第2篇,介绍的是如何避免在构建对象时写出不易测试的代码.?本文的概念性内容大部分都来自Misko Hevery的这篇博客文章. 构建还是用上文里汽车的例子. 通常情况下,我们是先去建造汽车,组装好汽车后,我们再去驾驶它. 软件开发也类似,我们应该把对象构造完毕之后,再去用它. 但是有时候,开发者会在构造过程中添加一些程序逻辑. 这就相当于车还没造完,我们就驾驶它去兜风了. 这样做是不太好的. 构造函数是类用来创建其实例对象的方法,这里的代码是用来准备该对象的. 但有时开发者会在构造函数里做一些其它的工作,例如构建依赖项,执行初始化逻辑等等. 在构造函数(或者更大一点,指构建的过程)里,做这些额外的工作会让测试变得异常困难. 这是因为像初始化依赖项,调用服务,设置状态的逻辑等这些工作会把用于测试的"缝"弄丢. 导致无法进行mock. 总之在构造的过程中做太多的工作会妨碍测试. ? 危险信号
? 如何解决问题?
总之就是要避免对象的构建和对象的行为混合到一起,因为它们在一起就会很难进行测试. ? 最后还有一点,首先你需要知道,根据angular的创始人Misko Hevery所说: 对象的构造分两类,一种是可注入的,一种是可new的. 可注入的对象可以由其它的一堆可注入对象组成. 它们可以为 可new的 对象工作. 可注入的对象通常是实现了接口的service,像什么IUnitOfWork,IRepository,IxxxService等等. 可new的对象就是对象图里的终点,例如实体或者值对象(Value Object)等. 为了易于测试,针对这两类构造,有下列规则: 可注入的对象可以在构造函数请求(注入)其它的可以注入对象,但是不能在构造函数请求可new的对象. 反过来,可new的对象可以在构造函数请求其它的可new对象,但是不能在构造函数请求可注入的对象. ? 例子第一个例子这是不对的,构建的过程中直接new的话,就会造成紧耦合,也无法在测试中使用Test Double来代替它们了. 如果测试中不代替它们的话,有些服务的开销可能会很大. ? 正确的写法是使用依赖注入: 第二个例子该例中,UserController只需要UserService和LoggingService两个依赖项. 但是UserService又依赖于UserRepository.? 但是这样写就不对了,这会造成UserController和UserRepository间的紧耦合,而且配置UserService也并不是UserController的责任. ? 正确的写法是: 而UserService也最好是注入依赖. ? 而如果UserService并不是在构造函数注入UserRepository的话: 那么Controller里就应该这样写: 不过最好还是使用构造函数注入的写法. ? 第三个例子仔细的说,该例有不止一处错误. 首先它有条件判断逻辑代码; 此外它还使用了ApplicationState.IsRunning这个静态变量(就是全局状态); 而且在构造函数里还做了UserService的配置工作,这不是UserController的责任. 尽量要避免全局变量,它无法进行隔离,测试会遇到麻烦,例如并行测试时其中一个测试改变了静态变量的值就可能导致另一个测试失败. 但是粗略的说,该例可以说就是一个错误,如何配置UserService并不是UserController的责任,所以,正确的做法是把UserService配置相关的代码移出去,让它自己去管理吧: ? 第四个例子该例子中,LoggingService的Log方法需要一个Area类型的对象,它是一个值对象. 所以它的错误就是,不应该把可new的对象注入到可注入的对象里. 这么做的话,测试就不好做隔离了. ? 正确的做法应该是,作为方法的参数传递进来: 第五个例子如果出现类类似initalize()或类似意思的方法,很有可能说明该对象的责任太多了. ? 修改它很简单,让各自的类负责自己的内容即可. 去掉initialize()方法即可. ? 例子就举这些,并不全,详细请看Angular作者的博文. ? 测试/运行时如何建立对象上面例子里的UserController就是我们需要使用的对象,在运行时,代码可能是这样的: 构建这个对象还是有点麻烦的,它的类关系图如下: ? 所以测试的设置过程也会比较麻烦: 当然也可以不直接new,而是使用mock. 总之都很麻烦. ? 使用工厂所以我们可以使用Factory等模式,把构建UserController的工作放到工厂里: ? 可以这样调用: ? 使用IoC容器如果项目使用了IoC容器的话,还可以使用类似下面的用法: ? 先介绍到这里. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |