本章,你将扩大你的模型测试,测试整个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

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end
end

同时把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

require "rails_helper"
RSpec.describe "adding a project", type: :system do
  it "allows a user to create a project with tasks" do
    visit new_project_path
    fill_in "Name", with:"Project Runway"
    fill_in "Tasks", with:"Choose Fabric:3\nMake it Work:5"
    click_on("Create Project")
    visit projects_path
    expect(page).to have_content("Project Runway")
    expect(page).to have_content 8
  end
end

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

require "rails_helper"
RSpec.describe "adding a project", type: :system do
  it "allows a user to create a project with tasks" do
    visit new_project_path
    fill_in "Name", with:"Project Runway"
    fill_in "Tasks", with:"Choose Fabric:3\nMake it Work:5"
    click_on("Create Project")
    visit projects_path
    expect(page).to have_content("Project Runway")
    expect(page).to have_content 8
    #为何等于8没懂。
  end
end

测试,并根据❌提示,建立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

require "rails_helper"
RSpec.describe CreatesProject do
  it "creates a project given a name" do
    creator = CreatesProject.new(name: "Project Runway")
    creator.build
    expect(createor.project.name).to eq "Project Runway"
  end
end
然后编写代码,app/workflows/creates_project.rb

创建单独类CreatesProject封装创建project对象和关联task对象的行为:

class CreatesProject
  attr_accessor :name, :project
  def initialize(name:"")
    @name = name
  end
  def build
    #不保存
    self.project = Project.new(name:name)
  end
end

测试通过。

然后继续修改测试,


增加测试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 create
    @workflow = CreatesProject.new(
      name:params[:project][:name],
      task_string: params[:project][:tasks]
    )
    @workflow.create
    redirect_to projects_path
  end

def index

    @projects = Project.all
  end

然后是视图,新增views/projects/index.html.erb

⚠️ 集成测试中,测试视图的期望比较弱,可能会导致一些错误, 数字8可能出现多处。

最好使用id,class等标志

    expect(page).to have_content("Project Runway")

expect(page).to have_content 8

通过测试后,再次重构,把视图测试改严谨一些。

Capybara的have_selector matcher。

#have_selector(*args, &optional_filter_block) ⇒ Object

视图:

    <%  @projects.each do |project| %>
      <tr class="project-row" id="<%= dom_id(project)%>">
        <td class="name"> <%=  project.name  %> </td>
        <td class="total-size"> <%=  project.total_size %> </td>
      </tr>
    <% end %>

测试:

@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

增加一个系统测试案例。

  it  "does not allow a user to create a project without a name" do
    visit new_project_path
    fill_in  "Name" ,  with:   ""
    fill_in  "Tasks" ,  with:   "Choose Fabric:3\nMake it Work:5"
    click_on("Create Project")
    expect(page).to have_selector( ".new_project" )
  end

增加一个单元测试creates_project_spec.rb

    it "fails when trying to save a project with no name" do
      creator = CreatesProject.new(name:'', task_string:"")
      project = creator.build

project.valid?

      expect(project.errors[:name]).to include("can't be blank")
    end

当然都失败了。


知识点:

在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方法:

    if @workflow.create
      redirect_to projects_path
    else
      @project = @workflow.project
      render :new
    end

做了什么

写了一个整个的rails 功能。一个集成测试add_project_spec.rb,一个workflow测试creates_project_spec.rb。 2个模型测试。

下面几章章节,讲解模型测试,控制器测试,视图测试,以及用数据测试,安全测试, Javascript测试。

更广泛的,逻辑代码测试,让测试符合逻辑,测试额外的services (API)等。

第四章讲解高效自动化测试

Rails 5 Test Prescriptions 第3章Test-Driven Rails的更多相关文章

  1. Rails 5 Test Prescriptions 第11章其他部分的测试。

    Routes✅ Helper Methods✅ Controllers and Requests✅ Simulating Requests⚠️,看之前的博客 What to Expect in a R ...

  2. Rails 5 Test Prescriptions 第9章 Testing-JavaScript: Integration Testing,❌挂一个问题webpacker::helper

    使用Capybara进行JS的集成测试 谈论驱动 让测试通过 Webpack in Development Mode Js设计 是用户在网页上有好的体验的重要因素. 尽管如此,许多网页不测试JS. 部 ...

  3. Rails 5 Test Prescriptions 第8章 Integration Testing with Capybara and Cucumber

    Capybara:  A complete reference is available atrubydoc.info. 集成测试就是把局部的程序组合起来测试. 端到端测试是一个特殊的集成测试,覆盖了 ...

  4. Rails 5 Test Prescriptions 第5章 Testing Models

    Rails,model层包含业务逻辑和储存逻辑.其中储存逻辑被ActiveRecord处理. 在model中,不是每件事都必须是ActiveRecord对象.model layer可以包含各种服务,对 ...

  5. Rails 5 Test Prescriptions 第4章 什么制造了伟大的测试

    伴随着程序成长,测试变长,复杂性增加,如何更高效的写测试,对以后开发不会造成麻烦. 测试本身没发被测试,所以一定要清楚,可控.不要加循环,不要过于复杂的自动编程. Cost and Value 成本和 ...

  6. Rails 5 Test Prescriptions 第10章 Testing for Security

    Web 安全是一个可怕的主题.所有的你的程序都依靠密码学,代码超出了你的控制. 尽管如此,你还是可以控制部分网页安全 --所有的logins和access checks和injection error ...

  7. Rails 5 Test Prescriptions 第10章 Unit_Testing JavaScript(新工具,learn曲线太陡峭,pass)

    对Js的单元测试是一个大的题目.作者认为Ruby的相关测试工具比Js的测试工具更灵活 大多数Js代码最终是关于响应用户的行为和改变DOM中的元素 没有什么javascript的知识点.前两节用了几个新 ...

  8. Rails 5 Test Prescriptions 第7章 double stub mock

    https://relishapp.com/rspec/rspec-mocks/v/3-7/docs/basics/test-doubles 你有一个问题,如果想为程序添加一个信用卡程序用于自己挣钱. ...

  9. Rails 5 Test Prescriptions 第6章Adding Data to Tests

    bcreate the data quickly and easily.考虑测试运行的速度. fixtures and factories.以及下章讨论的test doubles,还有原生的creat ...

随机推荐

  1. Tomcat 400错误 问题集锦

    1.前后台参数类型不一致 上图错误提示就是客户端发送的请求不能找到你的具体的页面或者地址,这是Spring MVC抛出的错误,这样我们就要进行参数的检查,一定是JSP提交的参数和Controller里 ...

  2. poj2185 Milking Grid【KMP】

    Milking Grid Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 10084   Accepted: 4371 Des ...

  3. CH1807 Necklace【Hash】【字符串】【最小表示法】

    1807 Necklace 0x18「基本数据结构」练习 背景 有一天,袁☆同学绵了一条价值连城宝石项链,但是,一个严重的问题是,他竟然忘记了项链的主人是谁!在得知此事后,很多人向☆同学发来了很多邮件 ...

  4. 2.5 – Garbage Collection 自动垃圾回收 Stop-the-world vs. incremental vs. concurrent 垃圾回收策略

    2.5 – Garbage Collection  自动垃圾回收 Lua 5.3 Reference Manual http://www.lua.org/manual/5.3/manual.html# ...

  5. mysql_commit() COMMIT ROLLBACK 提交 回滚 连接释放

    MySQL :: MySQL 8.0 Reference Manual :: 28.7.7.6 mysql_commit() https://dev.mysql.com/doc/refman/8.0/ ...

  6. Django - rest - framework - 上

    一.快速实例 http://www.django-rest-framework.org/tutorial/quickstart/#quickstart http://www.cnblogs.com/y ...

  7. 【apt install】Unable to locate package python3-pip

    解决办法: 先 sudo apt update 然后再 sudo apt install python3-pip,完成. 如果还不行的话参考这个:

  8. Block Towers---cf626c(二分)

    题目链接:http://www.codeforces.com/contest/626/problem/C 题意是有一群小朋友在堆房子,现在有n个小孩每次可以放两个积木,m个小孩,每次可以放3个积木,最 ...

  9. Python开发【前端】:Ajax(二)

    原生Ajax.JQuery.伪Ajax三种方式使用优先级 如果发送的是[普通数据] jQuery XMLHttpRequest iframe 如果发送的是[文件] iframe jQuery(Form ...

  10. nginx中获取真实ip(转)

    原文:http://blog.csdn.net/a936676463/article/details/8961504 server { listen       80; server_name  lo ...