zoukankan      html  css  js  c++  java
  • Rails 5 Test Prescriptions 第3章Test-Driven Rails

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

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

  • 相关阅读:
    Single Number II
    Pascal's Triangle
    Remove Duplicates from Sorted Array
    Populating Next Right Pointers in Each Node
    Minimum Depth of Binary Tree
    Unique Paths
    Sort Colors
    Swap Nodes in Pairs
    Merge Two Sorted Lists
    Climbing Stairs
  • 原文地址:https://www.cnblogs.com/chentianwei/p/9085093.html
Copyright © 2011-2022 走看看