本章,你将扩大你的模型测试,测试整个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. POI官网中的例子

    官方指南中的例子: http://poi.apache.org/spreadsheet/quick-guide.html#New+Sheet 这一节 Workbook wb = new HSSFWor ...

  2. Oracle 的闪回技术 --flashback

    SQL Fundamentals: 表的创建和管理 如何开启数据库闪回? SQL> shutdown immediate; ORA-01109: database not open Databa ...

  3. 少走冤枉路!带你走过SNMP的那些坑

    SNMP(Simple Network Management Protocol)即简单网络管理协议,是在网络与系统监控领域中,最常使用的一种数据采集技术.尽管这个协议非常简单,但在大规模IT环境监测中 ...

  4. thinkcmf安装教程与目录结构详解 快速上手版

    最近接了一个建站项目,要求用thinkcmf来搭建,ytkah在想php都大致一样吧,快速地下载安装包,可是!怎么安装呢?没看到安装指引文件或目录,查看了安装说明public目录做为网站根目录,入口文 ...

  5. centos shell编程5 LANMP一键安装脚本 lamp sed lnmp 变量和字符串比较不能用-eq cat > /usr/local/apache2/htdocs/index.php <<EOF重定向 shell的变量和函数命名不能有横杠 平台可以用arch命令,获取是i686还是x86_64 curl 下载 第三十九节课

    centos shell编程5  LANMP一键安装脚本 lamp  sed  lnmp  变量和字符串比较不能用-eq  cat > /usr/local/apache2/htdocs/ind ...

  6. 前端错误提示whitelabel error page

    1:错误提示whitelabel error page:需要定义一个error.html  ,否则提示如图

  7. 使用distcp并行拷贝大数据文件

    以前我们介绍的访问HDFS的方法都是单线程的,Hadoop中有一个工具可以让我们并行的拷贝大量数据文件,这个工具就是distcp. distcp的典型应用就是在两个HDFS集群中拷贝文件,如果两个集群 ...

  8. angry_birds_again_and_again(2014年山东省第五届ACM大学生程序设计竞赛A题)

    http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2877 题目描述 The problems ca ...

  9. (13)如何使用Cocos2d-x 3.0制作基于tilemap的游戏:第一部分

    引言 程序截图: 本教程将会教大家如何使用Cocos2d-x来做一个基于tile地图的游戏,当然还有Tiled地图编辑器.(我们小时候玩的小霸王小学机里面的游戏,大部分都是基于tile地图的游戏,如坦 ...

  10. Redis快速入门之简介

    一.Redis是什么? Redis 是一个开源(BSD许可)基于内存数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如字符串(strings), 散列(hashes) ...