BDD旨在解决具体问题,帮助开发人员确定应该测试些什么。它提供了一个DSL
( Domain-specific language,域特定语言)鼓励开发者弄清楚他们的需求,并且它引入了一个通用语言帮助你轻易理解测试的目的。
Q:我应该测试什么?
A:测试你对象的行为方式
。
BDD框架
Swift专用的BDD框架:
对比:
Cedar捆绑了匹配
和置换
,还包含了额外的配置功能:集中测试
,提供了反向配置能力
,但它用了一点黑客技术才能与XCTest集成,所以如果XCTest改变了的话,Cedar容易失效。
Kiwi同样捆绑了匹配模块
以及stubs
和mocks
,但缺乏像 Cedar一样的可配置性功能。
Specta缺少匹配,也没有mocks或者stubs,但它紧密地与XCTest结合在一起并且提供了近似Cedar的可配置性的能力。
依赖注入(Dependency Injection)
原文:依赖注入
DI本身是在彰显一个更高层面的概念:代码组成了模块,模块拼接构建成了应用本身。
-
构造器注入
:构造器注入,即将某个依赖对象传入到构造器中 (在 Objective- C中指 designated 初始化方法) 并存储起来,以便在后续过程中使用;
注意:尽管 Objective-C 本身没有所谓的构造器而是使用初始化方法,但因为构造器注入是 DI 的标准概念,放到各种语言中也是普遍适用的,所以还是准备用构造器注入这个词来代指初始化注入。
-
属性注入
:采用属性赋值方式,以便在后续过程中使用; -
方法注入
:如果依赖对象只在某一个方法中被使用,则可以利用方法参数做注入; -
环境上下文
:当通过一个类方法 (例如单例) 来访问依赖对象时,在单元测试中可以通过两种方式来控制依赖对象。- 如果可以控制单例本身,则可以通过公开其属性来控制其状态;
- 如果上述方式无效或者所操作的单例不归自己管理,此时就该运用swizzling了:直接替换类方法,让其返回你所期望的返回值。
写测试
测试用例使我们的代码质量变得可靠,同时让我们能够放心地重构或者修改代码,并保证我们的修改没有破坏其他部分。
原文:糟糕的测试
可以根据Given-When-Then模式来组织我们的测试用例。
写测试的时候问自己两个问题:
- “如果我修改了我的生产代码,测试是会失败 (还是通过) 呢?”
- “那是一个让测试失败 (或者通过) 的好的理由么?”
测试的基本准则(F.I.R.S.T.)
- 很快速(Fast) —— 测试应该能够被经常执行。
- 能隔离(Isolated) —— 测试本身不能依赖于外部因素或者其他测试的结果。
- 可重复(Repeatable) —— 每次运行测试都应该产生相同的结果。
- 带自检(Self-verifying) —— 测试应该包括断言,不需要人为干预。
- 够及时(Timely) —— 测试应该和生产代码一同书写。
注意:测试中一个最重要的关键点是降低未来的变化所带来的成本,不要将测试和实现细节耦合在一起。
-
不要测试私有方法
:可以进行置换测试 -
不要 Stub 私有方法
:stub 私有方法将会使程序难以调试 -
不要 Stub 外部库
:第三方代码不应该在你的测试中直接出现。 正确地 Stub 依赖
-
不要测试构造函数
:构造函数定义的是实现细节,你不应该测试构造函数。
置换测试: Mock, Stub etc.
用一些模拟代码替换你的实际代码来编写一些测试用例
原文:置换测试: Mock, Stub 和其他
mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
double可以理解为置换,它是所有模拟测试对象的统称,我们也可以称它为替身。一般来说,当你创建任意一种测试置换对象时,它将被用来替代某个指定类的对象。
stub可以理解为测试桩,它能实现当特定的方法被调用时,返回一个指定的模拟值。如果你的测试用例需要一个伴生对象来提供一些数据,可以使用 stub 来取代数据源,在测试设置时可以指定返回每次一致的模拟数据。
spy可以理解为侦查,它负责汇报情况,持续追踪什么方法被调用了,以及调用过程中传递了哪些参数。你能用它来实现测试断言,比如一个特定的方法是否被调用或者是否使用正确的参数调用。当你需要测试两个对象间的某些协议或者关系时会非常有用。
mock与spy类似,但在使用上有些许不同。spy 追踪所有的方法调用,并在事后让你写断言,而 mock 通常需要你事先设定期望。你告诉它你期望发生什么,然后执行测试代码并验证最后的结果与事先定义的期望是否一致。
fake是一个具备完整功能实现和行为的对象,行为上来说它和这个类型的真实对象上一样,但不同于它所模拟的类,它使测试变得更加容易。一个典型的例子是使用内存中的数据库来生成一个数据持久化对象,而不是去访问一个真正的生产环境的数据库。
对不同类型的模拟测试对象更多的细节讨论参考:"Mocks Aren't Stubs"
Mock框架
模拟测试时的注意事项:
-
依赖注入是你的好伙伴
:使用依赖注入的话,你会发现使用 stub 和 mock 方式写测试要容易的多; -
不要模拟你没有的
:应该只为你代码库本身拥有的对象创建 mock 或 stub,而不是为第三方依赖或一些库去创建。