You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
9.8 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 29 | 如何在 iOS 中进行面向测试驱动开发和面向行为驱动开发?
你好,我是戴铭。今天,我要和你分享的话题是,如何在 iOS 中进行面向测试驱动开发和面向行为驱动开发。
每当你编写完代码后,都会编译看看运行结果是否符合预期。如果这段代码的影响范围小,你很容易就能看出结果是否符合预期,而如果验证的结果是不符合预期,那么你就会检查刚才编写的代码是否有问题。
但是,如果这段代码的影响范围比较大,这时需要检查的地方就会非常多,相应地,人工检查的时间成本也会非常大。特别是团队成员多、工程代码量大时,判断这段代码的影响面都需要耗费很多时间。那么,每次编写完代码,先判断它的影响面,然后再手动编译进行检查的开发方式,效率就非常低了,会浪费大量时间。
虽说一般公司都会有专门的测试团队对产品进行大量测试,但是如果不能在开发阶段及时发现问题,当各团队代码集成到一起,把所有问题都堆积到测试阶段去发现、解决,就会浪费大量的沟通时间,不光是开发同学和测试同学之间的沟通时间,还有开发团队之间的沟通时间也会呈指数级增加。
那么,有没有什么好的开发方式,能够提高在编写代码后及时检验结果的效率呢?
所谓好的开发方式,就是开发、测试同步进行,尽早发现问题。从测试范围和开发模式的角度,我们还可以把这种开发模式细分出更多类型。
**从测试范围上来划分的话**,软件测试可以分为单元测试、集成测试、系统测试。测试团队负责的是集成测试以及系统测试,而单元测试则是有开发者负责的。对于开发者来说,通过单元测试就可以有效提高编写代码后快速发现问题的效率。
概括来说,单元测试,也叫作模块测试,就是对单一的功能代码进行测试。这个功能代码,可能是一个类的方法,也可能是一个模块的某个函数。
单元测试会使用 Mock 方式模拟外部使用,通过编写的各种测试用例去检验代码的功能是否正常。一个系统都是由各个功能组合而成,功能模块划分得越小,功能职责就越清晰。清晰的功能职责可以确保单个功能的测试不会出现问题,是单元测试的基础。
**从开发模式划分的话,**开发方式可以分为 TDDTest-driven development面向测试驱动开发和 BDDBehavior-driven development ,面向行为驱动开发)。
* TDD 的开发思路是,先编写测试用例,然后在不考虑代码优化的情况下快速编写功能实现代码,等功能开发完成后,在测试用例的保障下,再进行代码重构,以提高代码质量。
* BDD 是 TDD 的进化,基于行为进行功能测试,使用 DSLDomain Specific Language领域特定语言来描述测试用例让测试用例看起来和文档一样更易读、更好维护。
TDD 编写的测试用例主要针对的是开发中最小单元进行测试,适合单元测试。而 BDD 的测试用例是对行为的描述测试范围可以更大一些在集成测试和系统测试时都可以使用。同时不仅开发者可以使用BDD的测试用例高效地发现问题测试团队也能够很容易参与编写。这都得益于 BDD 可以使用易于编写行为功能测试的 DSL 语言。
接下来,我就和你详细聊聊 TDD 和 BDD。
## TDD
我刚刚也已经提到了TDD在确定功能需求后首先就会开始编写测试用例用来检验每次的代码更新能够让我们更快地发现问题并能保正不会漏掉问题。其实这就是通过测试用例来推动开发。
在思想上和拿到功能需求后直接开发功能的区别是TDD会先考虑如何对功能进行测试然后再去考虑如何编写代码这就给优化代码提供了更多的时间和空间即使几个版本过后再来优化只要能够通过先前写好的测试用例就能够保证代码质量。
所以说TDD 非常适合快速迭代的节奏,先尽快实现功能,然后再进行重构和优化。如果我们不使用 TDD 来进行快速迭代开发,虽然在最开始的时候开发效率会比 TDD 高,但是过几个版本再进行功能更新时,就需要在功能验证上花费大量的时间,反而得不偿失。
其实TDD 这种开发模式和画漫画的工作方式非常类似:草稿就类似 TDD 中的测试用例,漫画家先画草稿,细节由漫画家和助手一起完成,无论助手怎么换,有了草稿的保障,内容都不会有偏差。分镜的草稿没有细节,人物眼睛、鼻子都可能没有,场景也只需要几条透视线就可以。虽然没有细节,但是草稿基本就确定了漫画完成后要表达的所有内容。
## BDD
相比 TDDBDD更关注的是行为方式的设计通过对行为的描述来验证功能的可用性。行为描述使用的 DSL规范、标准而且可读性高可以当作文档来使用。
BDD 的 Objective-C 框架有 [Kiwi](https://github.com/kiwi-bdd/Kiwi)、[Specta](https://github.com/specta/specta)、[Expecta](https://github.com/specta/expecta)等Swift 框架有 [Quick](https://github.com/Quick/Quick)。
Kiwi框架不光有 Specta 的 DSL 模式Expecta框架的期望语法还有 Mocks 和 Stubs 这样的模拟存根能力。所以接下来我就跟你说说这个iOS中非常有名并且好用的BDD框架以及怎么用它来进行 BDD 开发。
## Kiwi
将Kiwi集成到你的App里只需要在 Podfile 里添加 pod Kiwi 即可。下面这段代码,是 Kiwi 的使用示例:
```
// describe 表示要测试的对象
describe(@"RSSListViewController", ^{
// context 表示的是不同场景下的行为
context(@"when get RSS data", ^{
// 同一个 context 下每个 it 调用之前会调用一次 beforeEach
beforeEach(^{
id dataStore = [DataStore new];
});
// it 表示测试内容,一个 context 可以有多个 it
it(@"load data", ^{
// Kiwi 使用链式调用should 表示一个期待,用来验证对象行为是否满足期望
[[theValue(dataStore.count) shouldNot] beNil];
});
});
});
```
上面这代码描述的是在 RSS 列表页面,当获取 RSS 数据时去读取数据这个行为的测试用例。这段测试用例代码,包含了 Kiwi 的基本元素也就是describe、context、it。这些元素间的关系可以表述为
* describe 表示要测试的对象context 表示的是不同场景下的行为,一个 describe 里可以包含多个 context。
* it表示的是需要测试的内容同一个场景下的行为会有多个需要测试的内容也就是说一个 context 下可以有多个 it。
测试内容使用的是 Kiwi 的 DSL 语法,采用的是链式调用。上面示例代码中 shouldNot 是期望语法,期望是用来验证对象行为是否满足期望。
期望语法可以是期望数值和数字,也可以是期望字符串的匹配,比如:
```
[[string should] containString:@"rss"];
```
should containString 语法表示的是,期望 string 包含了 rss 字符串。Kiwi 里的期望语法非常丰富还有正则表达式匹配、数量变化、对象测试、集合、交互和消息、通知、异步调用、异常等。完整的期望语法描述你可以查看Wiki的 [Expectations 部分](https://github.com/allending/Kiwi/wiki/Expectations)。
除了期望语法外Kiwi 还支持模拟对象和存根语法。
模拟对象能够降低对象之间的依赖,可以模拟难以出现的情况。模拟对象包含了模拟 Null 对象、模拟类的实例、模拟协议的实例等。存根可以返回指定选择器或消息模式的请求,可以存根对象和模拟对象。
模拟对象和存根的详细语法定义你可以查看Wiki 的 [Mocks and Stubs 部分](https://github.com/allending/Kiwi/wiki/Mocks-and-Stubs)。
## 小结
按照 TDD 和 BDD 方式开发有助于更好地进行模块化设计划清模块边界让代码更容易维护。TDD 在测试用例的保障下更容易进行代码重构优化,减少 debug 时间。而使用 BDD 编写的测试用例,则更是好的文档,可读性非常强。通过这些测试用例,在修改代码时,我们能够更方便地了解开发 App 的工作状态。同时,修改完代码后还能够快速全面地测试验证问题。
无论是 TDD 还是 BDD开发中对于每个实现的方法都要编写测试用例而且要注意先编写测试用例代码再编写方法实现代码。测试用例需要考虑到各种异常条件以及输入输出的边界。编写完测试用例还需要检查如果输入为错时测试用例是否会显示为错。
最后需要强调一点,好的模块化架构和 TDD 、BDD 是相辅相成的。TDD 和 BDD 开发方式会让你的代码更加模块化,而模块化的架构更容易使用 TDD 和 BDD 的方式进行开发。
在团队中推行 TDD 和 BDD 的最大困难,就是业务迭代太快时,没有时间去写测试用例。我的建议是,优先对基础能力的功能开发使用 TDD 和 BDD保证了基础能力的稳定业务怎么变底子还都是稳固的当有了业务迭代、有了间隙时再考虑在核心业务上采用 BDD最大程度的保证 App 核心功能的稳定。
## 课后作业
今天我跟你聊了很多 TDD 和 BDD 的优点,但是很多团队并没有使用这样的开发方式,你觉得这其中的原因是什么呢?
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎把它分享给更多的朋友一起阅读。