4. 单元测试(RSpec)- 过一遍,忘了就去问 chatGPT
rspec # BDD for Rails(行为驱动开发)
目的:对 model、controller 进行测试
前提:需要一个测试环境的数据库
现在已经有开发环境的数据库(catdou_dev)了,现在以这个数据库为例,做数据表的迁移。
在迁移之前,需要配置并新建一个测试环境的数据库(catdou_test)。
4.1 测试数据库:环境参数配置与新建
环境参数配置
config/database.yml
# 其他环境配置 test: <<: *default database: catdou_test username: catdou password: 123456 host: db-for-catdou # 其他环境配置
新建数据库
bin/rails db:create RAILS_ENV=test
4.2 数据表迁移
bin/rails db:migrate RAILS_ENV=test
如果缺少 model,执行 rails 提供的 model 生成命令即可。
4.3 rspec 依赖安装
Add rspec-rails
to both the :development
and :test
groups of your app’s Gemfile
:
# Run against this stable release group :development, :test do gem 'rspec-rails', '~> 6.0.0' end
Then, in your project directory:
# Download and install $ bundle install # Generate boilerplate configuration files # (check the comments in each generated file for more information) $ rails generate rspec:install create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb
4.4 rspec 使用之 model 测试
Creating boilerplate specs with rails generate: (如果 user 模型已经存在就不用重复生成了!直接运行第二个指令。)
# RSpec hooks into built-in generators $ rails generate model user invoke active_record create db/migrate/20181017040312_create_users.rb create app/models/user.rb invoke rspec create spec/models/user_spec.rb # RSpec also provides its own spec file generators $ rails generate rspec:model user create spec/models/user_spec.rb # List all RSpec generators $ rails generate --help | grep rspec
Running specs:
# Default: Run all spec files (i.e., those matching spec/**/*_spec.rb) $ bundle exec rspec # Run all spec files in a single directory (recursively) $ bundle exec rspec spec/models # Run a single spec file $ bundle exec rspec spec/controllers/accounts_controller_spec.rb # Run a single example from a spec file (by line number) $ bundle exec rspec spec/controllers/accounts_controller_spec.rb:8 # See all options for running specs $ bundle exec rspec --help
🌰:测试 user 模型:rails generate rspec:model user
之后会创建出一个 spec/models/user_spec.rb 文件(已改源码):
require "rails_helper" RSpec.describe User, type: :model do it '有 email' do user = User.new email: 'eric@x.com' # 新建一个实例,有一个 email 为 'eric@x.com' expect(user.email).to eq('eric@x.com') # 期望A等于B end end
运行测试用例:bundle exec rspec
$ bundle exec rspec . Finished in 0.11852 seconds (files took 4.61 seconds to load) 1 example, 0 failures
一个点代表一个测试用例成功执行。
4.5 rspec 使用之 controller 测试
🌰:测试 validation_codes controller:bin/rails g rspec:request validation_codes
app/controllers/api/v1/validation_codes_controller.rb
class Api::V1::ValidationCodesController < ApplicationController def create code = SecureRandom.random_number.to_s[2..7] # 随机数取6位 validation_code = ValidationCode.new email: params[:email], kind: "sign_in", code: code if validation_code.save head 201 else render json: { errors: validation_code.errors } end end end
spec/requests/validation_codes_spec.rb
require "rails_helper" RSpec.describe "ValidationCodes", type: :request do describe "POST validation_codes#create" do it "can be created" do post "/api/v1/validation_codes", params: { email: "eric@x.com" } expect(response).to have_http_status(201) end end end
附录
附录A MVC 架构
+----------------------+ | User's Request | +----------------------+ | v +----------------------+ | Controller Layer | +----------------------+ | v +----------------------+ | Model Layer | +----------------------+ | v +-----------------------+ | Database or Service | +-----------------------+ | v +-----------------------+ | Response to User | +-----------------------+
用户发送请求后,请求会进入 Controller 层。Controller 层负责接收和解析请求,处理业务逻辑,然后将数据传递给 Model 层进行处理。Model 层负责数据的读取、修改、删除等操作,并将处理后的结果保存到数据库或者其他服务中。最终,Controller 层将经过处理的数据返回给用户。整个过程中,View 层负责将数据渲染成最终的呈现形式(如 HTML 页面、JSON 响应等),但这并不是 MVC 模式的必要组成部分,因为 Ruby on Rails 中有一个概念叫做 Action View,它将 View 层的功能集成在了 Controller 层中,使得开发更加高效简洁。【以上绘图和回答来自 ChatGPT】
A-1 view
view 层,用于数据渲染和视图展示,用户访问路径时会向后端发送请求。
A-2 controller
controller 层,负责响应用户的请求(HTTP:GET、POST、PUT/PATCH、DELETE),这些请求通过路由映射到 controller 中,执行 CRUD 相关操作。
- 从请求对象中获取参数
- 访问 session,查看是否有权限
- 执行查询操作
- 响应类型:HTML 页面、JSON API 响应、文件下载等
- CSRF(跨站点请求伪造)保护功能
A-3 model
model 层,用于数据处理和存储。比如:数据的验证、转换、过滤、存储。
附录B curl 命令
B-1 GET 请求
curl http://127.0.0.1:3000/api/users/1 curl http://127.0.0.1:3000/api/users/1 -v # -v 表示 verbose 详细模式,请求头、响应头之类的会被打印出来
B-2 POST 请求
curl -X POST http://127.0.0.1:3000/api/users
附录C VSCode 插件 - PostgreSQL
postgresql explorer: postgresql
附录D 快速构建 API
前提:构建了 Rails 服务(并连接了数据库):rails new --api --database=postgresql --skip-test catdou-1
D-1 routes
第一步、routes,生成路由表: bin/rails routes
🌰:利用 namespace 生成一个以 /api/v1
开头的 api 路由表:(对应 controller 文件要放在 controllers/api/v1 目录下)
config/routes.rb
Rails.application.routes.draw do namespace :api do namespace :v1 do # /api/v1 resources :validation_codes, only: [:create] resource :session, only: [:create, :destroy] resource :me, only: [:show] resources :items resources :tags end end end
D-2 model
第二步、model,生成(User)模型并在数据库中生成(users)表:bin/rails g model user email:string name:string
Tip:模型是单数,如:User,对应的数据库表名是复数,如:users
- model file(User 模型相关,数据验证可以在这里做):app/models/user.rb
- db migrate file(users 表对应的数据库迁移文件,相关的字段类型定义在这里设定,这个文件决定了如何创建数据表):20230607152514_create_users.rb
为什么model 类是单数,而迁移文件的表会自动对应到复数?ruby-china.org/wiki/the-ra…
和 Matz 一样,我有时候为了实现我的理念也会做出一些蠢事。一个例子便是 Inflector,一个可以对英文做不规则转化的类,譬如 Person 类对应到 People 表、Analysis 对应到 Analyses,Comment 对应到 Comments 等。这个东西现在已经是 Rails 不可分割的元素了,但早期争议的怒火延烧到今日,伤口仍未愈合,由此可见其重要性。
以及指导手册上的 🌰:
- Model Class - Singular with the first letter of each word capitalized (e.g.,
BookClub
). - Database Table - Plural with underscores separating words (e.g.,
book_clubs
).
D-3 migrate
第三步、migrate,迁移文件,创建数据库表(users):bin/rails db:migrate
- 如有失败,则在后面加上环境变量:
bin/rails db:migrate RAILS_ENV=development
- 数据库回滚:
bin/rails db:rollback
- 数据库删除:
bin/rails db:drop
D-4 controller
第四步、controller,生成控制器(要写复数):bin/rails g controller Api::V1::Users show
🌰:因为第一步的路由文件设置了 namespace,所以 controller 文件中第一行的 Class 类要以 Api::V1:: 开头!
class Api::V1::UsersController < ApplicationController end
附录E HTTP 状态码
developer.mozilla.org/zh-CN/docs/…
- 4xx 为客户端错误
- 5xx 为服务器错误
附录F 数据类型
string
:字符串类型,用于表示文本数据。text
:字符串类型,用于表示大文本数据。integer
:整数类型,用于表示整数数据。float
:浮点数类型,用于表示浮点数数据。decimal
:高精度小数类型,用于表示精度比较高的小数数据。datetime
:日期和时间类型,用于表示日期和时间数据。timestamp
:时间戳类型,用于记录数据的创建和更新时间。time
:时间类型,用于表示时间数据。date
:日期类型,用于表示日期数据。binary
:二进制类型,用于存储二进制数据,如图片等。boolean
:布尔类型,用于表示真假数据。
附录G Gem 使用
Ruby 是“红宝石”的意思,而 Gem 是“宝石”的意思。
Gem 相当于 NPM,都是包管理器。
Gemfile, Gemfile.lock 相当于 Node 项目中的 package.json, package-lock.json,描述了项目需要的依赖关系。
但一般使用 bundle 来安装项目所需的所有 gem 包:bundle install
或 bundle
(查看安装详情:bundle --verbose
)
换国内的源,加速下载:打开 Gemfile,找到第一行,将默认的 source "https://rubygems.org"
换成 source "https://gems.ruby-china.com/"
注意:每次安装完成后,因为依赖变了,和前端一样,需要重启一下服务!
gem 常用指令:
gem install <gem_name>
:安装指定的 gem 包。gem uninstall <gem_name>
:删除指定的 gem 包。gem update <gem_name>
:更新指定的 gem 包。gem list
:列出所有已安装的 gem 包。gem search <keyword>
:搜索包含关键词的 gem 包。gem env
:显示当前 gem 环境的配置信息。gem sources
:列出所有可用的 gem 源。gem sources --add <source_uri>
:添加一个新的 gem 源。gem help
:查看 gem 命令的帮助信息。
bundle 常用指令:
bundle install
:根据 Gemfile.lock 文件中的依赖关系,安装所有需要的 gem 包。如果 Gemfile.lock 文件不存在,它会先生成这个文件,然后再进行安装。当然,如果我们在 Gemfile 中添加了新的依赖项,执行bundle install
还会安装这些新的依赖项。bundle update
:检查 Gemfile 中的所有 gem 包的最新版本,并更新 Gemfile.lock 文件,同时安装更新后的 gem 包。bundle exec
:运行特定版本的 gem 包。如果你在多个项目间使用了不同版本的 gem 包,可以使用这个命令来使用指定项目的 gem 包。这个命令用法如下:
bundle exec <command>
- 其中
<command>
指运行的命令,例如rails server
等。 bundle package
:将项目所需的 gem 包打成 gem 包并放到 vendor/cache 目录下,方便离线安装。
附录H zsh 常用命令
zsh 是一种 Unix shell,是 Bourne shell(sh)的扩展,也是 Bash、Ksh 等 shell 的改进版。它提供了更多的功能和自定义选项,如支持自动补全、历史命令搜索、别名等。
- cd:切换当前工作目录
cd ~ # 回到家目录 cd .. # 回到上层 cd - # 回到上一次
- ls:列出当前目录的文件和子目录
- pwd:显示当前所在的工作目录的完整路径
- alias:创建别名
- unalias:删除别名
- source:重新执行当前shell环境
- emacs:进入emacs编辑器模式
- vi:进入vi编辑器模式
- history:查看历史命令
- echo:输出文本信息
- grep:查找文件中的文本内容
- chmod:修改文件或目录的权限
- chown:修改文件或目录的所有者
- rm:删除文件或目录
- cp:复制文件或目录
- mv:移动文件或目录
- mkdir:创建新目录
- rmdir:删除空目录
- cat:查看文件内容
- touch:创建新空文件或更改文件的时间戳。