• 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)等。

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

  • 相关阅读:
    Redis配置文件的使用
    WEB请求处理一:浏览器请求发起处理
    Nginx配置文件(nginx.conf)配置详解
    【node】------mongoose的基本使用
    Promise.resolve()与new Promise(r => r(v))
    promise是什么?
    apiDoc
    apiDoc 使用指南
    微信小程序-性能与体验优化
    微信小程序-调取上一页的方法
  • 原文地址:https://www.cnblogs.com/chentianwei/p/9085093.html
Copyright © 2020-2023  润新知