Rails 5 Test Prescriptions 第8章 Integration Testing with Capybara and Cucumber
集成测试就是把局部的程序组合起来测试。
端到端测试是一个特殊的集成测试,覆盖了系统的全部行为, end-to-end.
接受测试acceptance test是用来指定正确行为,从客户或商业想法出发。Acceptance tests 通常在代码开始之前就计划或者写了。
集成测试也使用Javascript driver来评测基于Javascript的模仿用户行动。
本章是无Js版本,下章是有Js版本。
- Setting up Capybara✅
- Using Feature Tests to bulid a Feature✅
- What to Test in an RSpec System test✅
- Outside-in Testing
- Making the Capybara Test Pass
- Retrospective回顾
- Setting up Cucumber ⚠️新的知识点,概览!!
- Writing Cucumber Features
- Writing Cucumber Steps
- Advanced Cucumber
- is Cucumber Worth it?
A Field Guide to Integration and System Tests
Rails core testing library defines two similar types of end-to-end test:
integration test:传统的Rails测试,使用自己的API,功能没Capybara强大。
system test:5.1新增的。需要使用Capybara 。也叫feature tests. 也可以叫integration test。
另外Request 测试,是指URL for routing的test input。也是integration的一种。(11章会学习 request specs.)
RSpec-rails团队,推荐使用Capybara在系统测试中,不论是否使用JavaScript。
Setting Up Capybara (API doucument)
使用自己的API来和元素交互,使用驱动来管理实际的交互。默认不提供Js 交互,但可以配置使用一个无头的浏览器,如headless Chrome让JS交互被模仿。
gem "capybara-screenshot"用来闪存测试失败 (800星)放在support/system.rb
Using Feature Tests to Build a Feature
Writing a test
Given: 一个project ,2个task.
When/then: 用户填写form,你确认新的task建立
When/then:用户移动一个task up向上(增加向上和向下的按钮,task的排列?),你确认顺序改变。
system/add_tasks_spec.rb
⚠️ RSpec有rspec:feature生成器。
# given 3 data
# when/then1:
# when/then2:
The Capybara API: Navigating
vist 对应 HTTP GET方法,
click_on(link/button)对应 HTTP POST方法
⚠️最好👆这么写。current_path方法返回当前页面路径,不建议直接用。
草!用expect(current_path).to eq projects_path测试的时候,测试通过,但手动根本没有通过。have_current_path,和 current_path别用了,有bug!
The Capybara API: Interacting (看之前博客)
fill_in(locator, with: "TEXT") #locator可以使用DOM ID, name, label text.
例子:
<form>
<label for= "user_email" >Email</label>
<input name= "user[email]" id= "user_email" />
</form>
fill_in( "user_email" , with: "noel@noelrappin.com" ) #🆔
fill_in("user[email]", with: "noel@noelrappin.com")
fill_in("Email", with: "noel@noelrappin.com")
select(value, from:locator)
attach_file(locator, path) 。#5.2Rails有上传Active storage模块了
The Capybara API: Querying
可以使用# 或者 .也可以用[]来暗示HTML的属性。如input[nam="email"]
have_selector, take options that limit whether a selector with the given HTML tag, DOM class, and/or ID matches.
在mini test, assert_no_selector.
常用参数:
count.如count:3,其他如between(1..3), minimun, maximum
text: "string" #或者正则
visible。默认是匹配可见的元素。visble::hidden , visible::all
还有have_text, have_link?等灵活的方法。
save_and_open_page
#超有用的debug方法。闪存出错的模拟网页。使用gem launchy来自动弹出网页。
gem 'capybara-screenshot '可以自动储存错误网页到/tmp目录
What to Test in an RSpec System Test
在让测试通过前,回顾一下你正在试图做什么。TD process,你将写一个系统测试 来开始,然后开始写单元测试来驱动底层的逻辑。
比喻:测试pyramid.
系统测试的素材:
- 在控制器和模块之间或者其他对象的互动所提供的数据。
- 在由多个控制器行为/方法组成的工作流的互动。
- 某个安全问题涉及到一个用户状态和一个特别的控制器行为
- 特别的商业逻辑,如如果数据nil或有一个错误的值会发生什么
- 错误事件。
- 商业逻辑的内部实现细节。
⚠️ 集成测试的缺点:
- 集成测试更慢
- 集成测试不能精确判断内部的逻辑执行的错误。
Outside-in Testing (feature tests)
从外面写测试并用那个测试来驱动你的单元测试。
相同的思路,TDD使用失败的单元测试来驱动写代码,feature tests使用失败的接受测试(在an acceptance test中的a failing line)来驱动单元测试的创建。
https://ww1.sinaimg.cn/large/006tKfTcly1frsbapf6kfj31h60skadg.jpg
主线是写集成测试,运行, 看是否通过。
在运行阶段:出现错误,小错直接修改代码,大的逻辑错误则进行单元测试。
当单元测试完成后,再进行写集成测试,最终会通过。
然后可以重构这个测试。
如果集成测试增加了新的功能,继续进行这个♻️写测试再运行 。
Making the Capybara Test Pass
在system/add_tasks_spec.rb写完了集成测试,见上!⬆️
运行,第一个失败是没有show。所以要写ProjectsController中的show方法和视图。
再测试发现失败,是因为没有创建TasksController中的create方法。
⚠️字段的验证,可以写一行。
再测试失败,此时是到了
within("#task_3") do
根据之前的give/when/then的工作流程。这时,应该除了加DOM ID,还应该增加一个新的功能,给tasks列表调整显示的顺序。
经过思考,需要再Task中增加order属性。
rails g migration add_order_to_tasks project_order:integer
rake db:migrate
在project.rb中,客制化查询条件scopes for has_many
在集成测试文件内,let(:task_1)也需要更新属性。
给一个新的task在order的调整上,增加一点逻辑,有三个方法:
- 使用回调,after_save callback on Task
- Project中增加一个方法
- 创建一个Task workflow 对象,类似CreatesProject对象(早期建立的)
现在到了unit-test model,测试的是对tasks.project_order的排序功能。 每添加一个新的task,这个task获得project_order的值是最后一次增加的task的project_order的值+1。
在project.rb中加一个next_task_order方法。
spec/model/project_spec.rb
下面合并它。
第一,task控制器的新方法, up, down.并修改create方法,增加project_order。
create:
project_order:@project.next_task_order))
然后,在view界面增加ID.
<tr id="task_<%= task.project_order %>">
这是测试中对应的 #task_3 selector.
测试,在click_on "up" 处报错,思考现在需要什么逻辑,怎么为它写一个测试?
given: 一个project, 3个task.
when/then:
- 能判断这个task是不是第一个或最后一个。
- 每个task能知道它相邻的2个task
- 每个task都可以上/下移
- 第一个task上移,order不变。
- 最后一个下移,project_order不发生变化。
first_in_project?, last_in_project?,
previous_task, next_task,
move_up, move_down
⚠️ first_in_project?, last_in_project?直接写在mode层,这是boolean值,带❓的方法。
move_up, move_down 需要决定是否使用RESTFul,这里用up/down方法包含前两个方法。
up/down写独立的路径:
然后在view中加up/down的链接。
然后在task.rb中写previous_task, next_task, move_up, move_down
自己写的代码不能通过测试!!!???
third.previous_task.project_order = 100
一旦不把代码抽出来重构,就出现nilclass的错误。
在 rails c里面建立数据后,用自己定义的move_down方法,也会出现❌。
2个小时多,问题卡住,没有解决: 休息,🚿。
然后想到从起点开始写代码(从头开始设计✈️)5分钟搞定了哎:
测试都通过,✌️!
再分析:
# 之前写的代码的❌之处,是self.next_task.project_order
不能用这种链式方法,应该分开:
down_task = self.next_task
down_task.project_order = self.project_order
链式的写法可以用于读取value,但不能修改。
Review
通过集成测试开发TDD的优势::
- 帮助开发者建设用户角色(故事):通过模拟用户对产品的使用,形成基础的网页结构和工作流程,并一步一步的落实。
- 集成测试提供了这个:相关的各类的单元测试能够在一起工作,不会只见🌲不见森林。end-to-end测试强迫开发者描述森林。
- 尽管集成测试比单元测试慢,但比手动测试浏览器页面快,可反复用,还能作为证据。(最后也得过一遍手动测试)
Cucumber(细分项目的工具)忽略未看。
一个写接受测试的工具。上一章提到开始的测试有点长和笨拙。需要重构一下。或者使用Cucumber。是一个使用自然语言(英文)的开发框架,可以让管理者和非开发客户预先设计,猜测:这导致中国人用的少。