在TDD里有两条规则:
- 只在有未通过的自动化测试的情况下,你才会去写新的代码
- 消灭重复
缝(seam)
缝(seam):可以在程序里替换行为的地方, 而不需要在这个地方进行修改. 或者说就是可以让你的代码移除依赖项并创建出可用于隔离测试对象的地方。
如何产生缝隙
** 解藕依赖项**: 在C#里, 我们通过对接口编程而不是对实现来编程来实现这个任务.
** 依赖注入**: 主要是采用构造函数注入.
构建对象
在构造函数或在构建的过程里, 做这些额外的工作会让测试变得异常困难. 这是因为像初始化依赖项, 调用服务, 设置状态的逻辑等这些工作会把用于测试的”缝”弄丢. 导致无法进行mock。
总之在构造的过程中做太多的工作会妨碍测试。
危险信号
1,在构造函数/字段声明里出现new关键字
如果构造函数里需要创建依赖, 那么这就会为该类与依赖项之间创造了紧耦合。但是简单的值类型, 例如字符串, List, Dictionary等可以使用new关键字。
2, 在构造函数/字段声明里调用静态方法
静态方法不可以被mock, 也不能被注入。
3, 构造函数出现流程控制逻辑代码
4, 构造函数里出现非赋值代码
5, 存在另外一个初始化函数
也就是说构造函数走完了, 但是对象并没有被完全初始化
解决问题
原则:避免对象的构建和对象的行为混合到一起
- 不要在构造函数里创建依赖项, 应该注入它们.然后在构造函数里把它们赋值给类的私有变量。
- 当需要构建对象图(一组有引用关系的对象), 也包括对象需要一些构建的参数等情况, 应该使用工厂, 建造者模式, 或者IoC容器的依赖注入等, 目的是把这些对象的构建工作分离出去。
- 避免在构造函数里写逻辑代码, 例如条件, 循环, 计算等等. 也不能把逻辑代码放在别的方法, 然后调用该方法。
对象的构造分两类, 一种是可注入的, 一种是可new的.
针对这两类构造, 有下列规则:
可注入的对象可以在构造函数请求(注入)其它的可以注入对象, 但是不能在构造函数请求可new的对象.
反过来, 可new的对象可以在构造函数请求其它的可new对象, 但是不能在构造函数请求可注入的对象.
依赖项
迪米特法则 (Law of Demeter)
迪米特法则大概的意思是: “只访问你自己创建的对象, 或者作为参数传给你的对象. 不要通过其它对象间接的访问对象”
即:只与直系朋友交谈, 不要和陌生人交谈。
单一职责
一个类只能因为一个原因去改变。
参考:
.NET Core TDD 前传: 编写易于测试的代码 – 缝
.NET Core TDD 前传: 编写易于测试的代码 – 构建对象
.NET Core TDD 前传: 编写易于测试的代码 – 依赖项
.NET Core TDD 前传: 编写易于测试的代码 – 全局状态