本章,你将扩大你的模型测试,测试整个Rails栈的逻辑(从请求到回复,使用端到端测试)。
使用Capybara来帮助写end-to-end 测试。
好的测试风格,包括端到端测试,大量目标明确的单元测试,和相关的一些覆盖中间代码的测试。
开始写Rails
Requirements-gathering,分析需求,是一整本书的内容。本节假设是写一个自用的小程序,因此无需military-grade precision。
列出非正式的需求单子:
- A user can enter a task, associate it with a project, and also see it on the project page.
- A user can create a project and seed it with initial tasks using the somewhat contrived syntax of task name:size.
- A user can change a task’s state to mark it as done.
- A project can display its progress and status using the date projection you created in the last chapter.
end to end Test
新版Rails默认加了Capybara.
建立目录spec/support/system.rb
同时把rails_helper.rb中注释的语句
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 激活。
⚠️rack_test : which is provided by Capybara to simulate a browser DOM tree without JavaScript. (详细见第8章 Integration Testing with Capybara第一个测试: 一个用户增加一个project.
- Given: 开始于空数据,没有步骤
- When: 填写一个project表格 ,并提交
- Then: 确认新project显示在projects list,同时附加上entered tasks.
使用系统测试,就是功能测试把? spec/system/add_project_spec.rb
Pending Tests
describe , it 如果不带block,就是待定的pending。
也可以加上一个:pending 参数。或放到块内pending "not implemented yet"
这样系统会提示有pending的案例。
如过忽略案例,可以在块内加skip,这样案例不会运行。
Making the Test Pass
此时运行测试会报错❌,需要使用resource generator。
rails generate resource project name:string due_date:date
rails generate resource task project:references title size:integer completed_at:datetime
⚠️,不要override。手动更新model file
⚠️使用g resource,不是g scaffold,所以只有空白的controllers和views
Project.rb中移除initialize方法和attr_accessor方法。ActiveRecord接替了这2个功能。
Task.rb同样移除这两个方法,另外把@completed_at改为self.completed_at
两个模型添加上关联. ⚠️ 让两个类继承ApplicationRecord
然后rake db:migrate
再测试,除了集成测试,之前的都会通过。
The Days Are Action-Packed
建立系统测试spec/system/add_project_spec.rb
测试,并根据❌提示,建立controller-new, views/projects/new.html.erb
⚠️<%= text_area_tag("project[tasks]")%>的写法
然后编辑create action。见下节讨论:
Going with the workflow
现在需要做一些决定,你有一些逻辑要处理,当表单提交时,要创建Task实例。这个代码需要前往某个地方,并且单元测试需要指定这个地方是哪。这就是TDD process
有三个位置被经常用于那些响应用户输入超过了普通的传递hash参数到ActiveRecord#create的业务逻辑,见下:
- 把额外的逻辑放入controller,这是常用的方法。但遇到复杂逻辑则不好使,它会对测试是个麻烦,同时让重构费劲,难以分享,如果在控制器中有多个复杂的动作会让人迷糊。
- 把额外的逻辑放到关联模型中,常常是至少部分放入class method。容易测试,但重构仍awkward。这种方法也会让模型变得更复杂并且不容易关联跨域多个模型得action
- 创建一个单独的类来封装这个逻辑和workflow.这是最容易的测试和最好的关联复杂变化的方法。 主要负面是你wind up with a lot of little classes. 作者不介意这个缺点。
Prescription:
Placing business logic outside Rails classes makes that logic easier to test and manage.
创建一个单独的测试spec/workflows/creates_project_spec.rb
创建单独类CreatesProject封装创建project对象和关联task对象的行为:
测试通过。
然后继续修改测试,
增加测试describe "task string paring", 内含"handle empty string", "single string", "single string with size", "multiple tasks"等。
通过修改类CreatesProject通过测试。
CreatesProject 类是单独的类,封装了创建project对象和增加task的功能。
blank?和empty?的区别:
blank? 方法,Nilclass的对象nil也可以用,nil.blank? => true。
而empty?方法没有nilclass。
相同之处:
都可以用于string,和关联ActiveRecord:relation.
be_blank, be_empty是RSpec中的be方法和rails方法的结合。也可以自定义方法和be结合。
be_a 和new_record?结合, be_a_new_record, 查看是否是未save的记录
be_a_new(class/String/类名) 查看subject是否是这个类的实例对象,是的话返回true.
has_attributes(hash) 查看一个对象是否有指定属性
Refactor, The single-assertion style
it别名是specify,specify不用带块,一行,不带说明。
aggregate_failures do..end 方法.用块把一个案例的期望放到一起,同时执行。即使其中一个出错也会执行其他的。
Who controls the Controller?
上节建立了 CreatesProject 类,封装了创建project对象和增加task的功能。
并通过了单元测试。
下面进行集成测试。当点击按钮“Create Project”时,如何关联到CreateProject的实例方法。
ProjectsController 会发送数据到the workflow object和到视图层。
def index
然后是视图,新增views/projects/index.html.erb
⚠️ 集成测试中,测试视图的期望比较弱,可能会导致一些错误, 数字8可能出现多处。
最好使用id,class等标志
通过测试后,再次重构,把视图测试改严谨一些。
Capybara的have_selector matcher。
#have_selector(*args, &optional_filter_block) ⇒ Object
视图:
测试:
@project = Project.find_by(name: "Project Runway")
expect(page).to have_selector("#project_#{@project.id} .name", text: "Project Runway")
expect(page).to have_selector(".project-row .total-size", text:"Project Runway")
知识点:
dom_id方法,dom_class方法都是RecordIdentifier 的实例方法。
⬆️题,可以使用id="project_<%= project.id%>"代替dom_id方法。
dom_id(Post.find(45)) # => "post_45"
dom_id(Post.new) # => "new_post"
dom_class(post, :edit) # => "edit_post"
Testing for Failure
增加一个系统测试案例。
增加一个单元测试creates_project_spec.rb
project.valid?
当然都失败了。
知识点:
在spec/spec_helper.rb中取消注释:
config.example_status_persistence_file_ "spec/examples.txt"
然后运行rspec命令,会产生一个spec/examples.txt文件。
里面列出了所有案例的运行结果和运行时间。
再运行 rspec --only-failures 命令,会只运行所有的失败的测试。
如运行rspec --next-failures命令,会运行第一个失败的案例测试,然后停止。
单元测试:在model:project加上名字验证,valicates :name, precent: true
测试通过。
系统测试:在new.html.erb上假设class="new_project", 然后在projects_controller.rb上
create方法:
做了什么
写了一个整个的rails 功能。一个集成测试add_project_spec.rb,一个workflow测试creates_project_spec.rb。 2个模型测试。
下面几章章节,讲解模型测试,控制器测试,视图测试,以及用数据测试,安全测试, Javascript测试。
更广泛的,逻辑代码测试,让测试符合逻辑,测试额外的services (API)等。
第四章讲解高效自动化测试