分享第二章,关于测试驱动。这里的测试主要针对Web后端的测试 —— 你为何要写测试用例(即测试用例的完善是不是浪费时间),如何完善你的测试用例,代码设计如何简化测试用例的书写,以及一些后期的构想。javascript
这个习惯一般会被认为是一种耽误开发进度的行为,你须要花费几乎和开发代码相同的时间来逐步完善你的测试用例。可是在开发过程当中,在开发完成一段代码后若是负责任而不是说彻底把问题交给测试人员去发现的话,这个时候一般都会去作一些手动的测试。例如:前端
现代化的测试工具都在尽量的将这些人工的手动测试行为抽象成代码块,当你有意识去进行手动测试的时候,其实已经开始在尝试测试用例的行为了。既然能够经过手动的方式进行测试,那为何还须要用代码来实现测试?java
回归测试
,即在你修复 Bug 将你的系统从新测试一遍。可是若是你已经有了完善的测试用例了呢,直接执行命令搞定。在进入完善阶段前,先说说你将如何实现测试用例。laravel
rubydescribe Meme do before do @meme = Meme.new end describe "when asked about cheeseburgers" do it "must respond positively" do @meme.i_can_has_cheezburger?.must_equal "OHAI!" end end describe "when asked about blending possibilities" do it "won't say no" do @meme.will_it_blend?.wont_match /^no/i end end end
上面的代码来自于 Ruby 的 minitest。before
包含的代码块是在执行下面的测试用例前要作的事情,一般还会支持一个相对应的方法,在测试用例执行完执行。每一个用例里面都进行一些很小的判断。git
第一段中提到了一些手动测试里面常常会涉及到的测试内容,这里拿其中的 2
和 3
进行说明。在进行数据库相关的测试时,须要在 before
中插入一条测试数据,而且在 after
中删除测试数据。中间的测试用例中,经过执行相应的方法,执行完毕后:检查数据变化状况/检查是否有预期的异常/是否返回预期结果 来确认代码的正确性。若是是接口的话,就是经过代码发起对应的请求,而后检查返回的内容是否返回预期,有须要的话再去查看数据库里面的数据是否符合预期变化。github
如今已经有了测试用例,可是任然须要考虑一种特殊状况。我如今为一个函数写了相对完善的测试用例了,跑完都 PASS
了,结果发现线上的日志里面仍是有那个函数的报错。检查下发现函数的某个分支以前在测试的时候没有测试到,恰好线上的某种状况运行到了这个分支,结果有一个很不明显的语法错误报错了,有没有办法能确保全部的代码都测试过了?这里须要引入的是一个叫作 测试用例覆盖率 的概念,基本上每一个语言都会有响应的实现。经过测试用例覆盖率,量化的告诉你你的测试用例有没有跑完某某文件里的全部代码,而你须要作的,就是尽量保证你的覆盖率保持在 100%。web
某种意义上来讲,测试用例和测试覆盖率是用来提升开发者对本身代码自信心的工具。可是,他们也不是万能的。测试用例里面总可能会漏掉一些参数的可能性,固然你的代码里面也没有为这种可能性进行代码的编写,最终测试用例覆盖率只能告诉你你写的代码咱们都帮你检测过了测试过了,对于你没有考虑到的可能性,表示无能为力。因此尽量编写严格的代码,例如 javascript 里面尽量都用 ===
而不是 ==
,使用强类型的编程规范等等,这些来下降这种由于接受的参数范围过大带来的潜在风险。数据库
整个 Web (也不局限于 web)一般包括三个层面的代码 —— 单纯数据处理与运算、涉及到数据库、涉及到具体的网络协议。其中单纯的数据运算于处理主要为普通的运算的函数或者是其余代码,涉及到数据库就是传统意义上 MVC
里面的 M
,涉及到具体的网络协议就是对应的 C
。这三块的测试分别对应着第一节中常规的测试内容的前三条。编程
由于 C
层面一般还可能涉及到页面的渲染以及相应协议的模拟,因此一般把测试的重心放在函数以及数据库相关的代码里面能够减小测试用例代码的复杂度,这个就要求 Controller
的代码要尽量少。对于复杂度较高的应用的一些目前的一些建议:后端
M
层,若是使用 Ruby 开发的话,ActiveRecord
以及Mongoid
都提供了很方便使用的 validation
功能。Pub/Sub
模式配合一些 ORM
中提供的钩子(hook) 来实现 Model
之间的通讯。 例如在 A 建立的时候发布某个消息,B监听到消息以后修改他本身的某个属性值。Command
模式将一些业务无关的功能从系统中抽离出来,例如邮件发送。以上的内容都避开了先后端须要联调的测试用例,下面的内容主要是针对这块。Ruby 在这个方向已经有一些比较优雅的实现,感兴趣的能够直接先去欣赏一下 Capybara。
随着包括 Selenium Phantomjs 以及基于前者的 Watir 等一系列浏览器驱动的普及,使用代码控制浏览器已经再也不是一件很复杂的事情。在这个能力的基础上,能够尝试把基于前端的测试分为四步:
基于这个流程,能够解决绝大多数的前端测试。可是单纯依靠这个流程任然不够,由于页面中可能出现例如验证码这样的阻碍元素,在不修改代码的前提下,能够尝试经过数据库/缓存来取到这些内容。一样,和测试接口相同,这里也涉及到在测试前数据库中插入测试数据,测试用例执行后严重数据库里面数据变化,以及所有测试完毕后删除测试数据的内容。最终致使这块测试用例代码的实现须要同时对前端后端有必定的了解。目前还在考虑在借鉴 Capybara 的基础上,设计出更加通用的方案。
最后贴一段 Capybara 的代码结束这段内容:
rubyfeature "Signing in" do background do User.make(:email => 'user@example.com', :password => 'caplin') end scenario "Signing in with correct credentials" do visit '/sessions/new' within("#session") do fill_in 'Email', :with => 'user@example.com' fill_in 'Password', :with => 'caplin' end click_button 'Sign in' expect(page).to have_content 'Success' end given(:other_user) { User.make(:email => 'other@example.com', :password => 'rous') } scenario "Signing in as another user" do visit '/sessions/new' within("#session") do fill_in 'Email', :with => other_user.email fill_in 'Password', :with => other_user.password end click_button 'Sign in' expect(page).to have_content 'Invalid email or password' end end