ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RSpec 入门实战指南:在 cu/curriculum 课程体系中用测试驱动开发编写 Ruby 单元测试

RSpec 入门实战指南:在 cu/curriculum 课程体系中用测试驱动开发编写 Ruby 单元测试 RSpec 入门实战指南在 cu/curriculum 课程体系中用测试驱动开发编写 Ruby 单元测试【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本篇文章是 cu/curriculum 开源课程中 Ruby 测试系列的核心入门课围绕 ruby/testing_ruby_with_rspec/introduction_to_rspec.md 展开。你将系统掌握RSpec 是什么、为什么它是 Ruby 社区最流行的测试框架、如何安装初始化、describe/it/expect三大核心语法以及如何用红-绿-重构red-green-refactor的 TDD 流程为真实类编写测试。学完本文你不仅能独立搭建一个可运行的 RSpec 测试项目还能读懂课程后续 Connect Four、Tic Tac Toe 等 TDD 项目的测试代码。RSpec 是什么以及为什么选择它一种为测试而生的领域特定语言在最基本的层面RSpec 是一门用 Ruby 写成的领域特定语言Domain Specific Language, DSL——通俗地说它是一门专为测试 Ruby 代码这一特定任务而优化的语言。安装rspecgem 后你获得的是一整套开箱即用的工具包它实际上打包了五个 gemrspec元包负责统一调度rspec-core测试的组织与运行核心rspec-expectations期望与匹配器rspec-mocks测试替身 / mock 与 doublerspec-support公共支持库为什么是 RSpecRuby 生态中测试框架并不少课程文档ruby/automated_testing/introduction.md也梳理了它们的来龙去脉Ruby 标准库早期自带Test::Unit后来被Minitest取代后者至今仍活跃在 Rails 中此外还有风格各异的Wrong、Bacon等框架。选择哪个框架本身并不重要重要的是持续锤炼你的测试能力——正如课程原文所强调的实现测试本身才是目的而非选择某个特定框架。RSpec 之所以值得优先熟悉核心原因是它在社区、尤其是 Rails 社区中的广泛采用率。据 introduction_to_rspec.md 记载截至文档编写时其下载量已超过 7 亿次并且被移植为rspec-rails用于 Rails 测试。这意味着无论求职还是阅读开源项目你都会频繁与它打交道。RSpec 的流行还得益于其丰富、富有表达力的语法——它让测试读起来像自然语言描述便于编写、阅读和维护见 ruby/automated_testing/rspec_part_one_basics.md 开篇。在 ruby/testing_ruby_with_rspec/test_driven_development.md 中课程还给出了坚持测试优先的三个理由练习许多雇主看重测试经验、有效性先写测试让后续代码设计更清晰、效率不必反复手动运行整个应用来验证局部逻辑。安装与初始化 RSpec三步安装在终端中依次执行以下命令# 1. 安装 rspec gem gem install rspec # 2. 验证版本会分别输出 rspec、rspec-core、rspec-expectations、 # rspec-mocks、rspec-support 等打包 gem 各自的版本号 rspec --version # 3. 查看全部可用命令行选项 rspec --helprspec --help值得花一分钟浏览它会列出格式化输出、随机顺序、只运行指定文件等常用选项——后续我们会用到其中的--format documentation。用 rspec --init 初始化项目进入你想要配置为使用 RSpec 的项目目录执行初始化命令rspec --init该命令会生成两个文件.rspec和spec/spec_helper.rb。初始化完成后一个典型项目结构如下project |__lib | |__calculator.rb | |__spec | |__spec_helper.rb | |__.rspec关于各目录与文件的职责ruby/automated_testing/rspec_part_one_basics.md 有更完整的说明spec/目录所有测试文件spec 文件存放于此文件名以_spec.rb结尾如calculator_spec.rb。测试代码与源码分离是社区最佳实践避免两者混杂难以辨别。lib/目录存放实际的可执行源码如calculator.rb。.rspec文件RSpec 运行时读取的命令行默认配置。初始化后它默认只含一行--require spec_helper你可以追加配置项让它们对每次rspec运行自动生效。spec/spec_helper.rb存放需要在多个测试文件间共享的配置。如果项目使用 Bundler 管理依赖还可以像 rspec_part_one_basics.md 那样先建Gemfile写入gem rspec, 3.10后执行bundle install再运行rspec --init。Gemfile.lock会自动生成把依赖锁定到具体版本保证任何机器上运行结果一致。第一次运行No examples found从项目根目录运行测试命令rspec由于尚未编写任何测试你会看到No examples found. Finished in 0.00037 seconds (files took 0.21108 seconds to load) 0 examples, 0 failures0 examples, 0 failures 表示测试框架已正确就绪只待你写入测试。基础语法describe 与 it创建第一个测试项目按课程步骤在终端创建新目录并初始化mkdir ruby_testing cd ruby_testing rspec --init输出应与预期一致create .rspec create spec/spec_helper.rb假设我们要实现一个带若干方法的Calculator计算器类。遵循 TDD 精神先写测试、后写代码。在spec/目录下创建calculator_spec.rb并写入# spec/calculator_spec.rb RSpec.describe Calculator do describe #add do it returns the sum of two numbers do calculator Calculator.new expect(calculator.add(5, 2)).to eql(7) end end end逐行解读RSpec.describe定义示例组Example Group它是 RSpec 的关键字用于定义一个测试集合接受一个类或字符串作为参数并接收一个do/end代码块。describe块可以嵌套在RSpec.describe内部——上面第二行就是嵌套的describe #add。it定义单个示例example即一个测试用例它接受一个字符串参数并接收代码块代码块中书写对方法行为的期望。本例中当我们把 5 和 2 传给#add方法时期望它返回 7。这一期望通过 RSpec 的相等性匹配器eql表达expect(calculator.add(5, 2)).to eql(7)编写测试的通用套路课程总结了四条书写规范务必牢记用RSpec.describe描述被测试的类用describe描述方法示例组。命名约定实例方法写为#method如#add类方法写为.method用it编写具体的测试用例用expect编写期望。expect后链式调用.to表示正向期望.to_not或.not_to表示反向期望课程推荐使用.not_to每个测试用例只写一个 expect 子句。关于第 4 点的补充rspec_part_one_basics.md 给出了期望expectation的结构化拆解一条期望由实际值 匹配器两部分构成完整形式为expect(actual_value).to eq(expected_value)反向期望expect(actual_value).not_to ...在期望确实匹配时反而会失败。红绿重构实战让 Calculator 测试通过红色阶段先看到一个错误运行rspec观察输出An error occurred while loading ./spec/calculator_spec.rb. Failure/Error: RSpec.describe Calculator do describe #add do it returns the sum of two numbers do calculator Calculator.new expect(calculator.add(5, 2)).to eql(7) end end end NameError: uninitialized constant Calculator # ./spec/calculator_spec.rb:1:in top (required) No examples found. Finished in 0.0004 seconds (files took 0.16461 seconds to load) 0 examples, 0 failures, 1 error occurred outside of examplesNameError: uninitialized constant Calculator清楚地告诉我们RSpec 在找Calculator类但找不到它。这是 TDD 流程的正常起点。于是我们在项目根目录创建lib/文件夹及calculator.rb并先搭好#add方法的空壳否则 RSpec 找不到方法时会报类似的错误# lib/calculator.rb class Calculator def add(a,b) end end用 require 告诉 RSpec 源码位置还需要让 spec 文件知道Calculator类定义在哪里通过require完成# spec/calculator_spec.rb require ./lib/calculator # add this RSpec.describe Calculator do #... end第一个失败期望 7得到 nil再次运行rspec你会得到第一个失败failureF Failures: 1) Calculator#add returns the sum of two numbers Failure/Error: expect(calculator.add(5, 2)).to eql(7) expected: 7 got: nil (compared using eql?) # ./spec/calculator_spec.rb:7:in block (3 levels) in top (required) Finished in 0.28565 seconds (files taken 0.6273 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/calculator_spec.rb:5 # Calculator#add returns the sum of two numbers输出顶部的F即 Failure 的缩写。恭喜——你已经进入 TDD 红-绿-重构循环的红色阶段这份输出信息量很大值得逐项理解失败列表中明确对比了expected: 7与got: nil一眼可知期望值与实际值不符输出末尾标注了失败测试所在的行rspec ./spec/calculator_spec.rb:5方便在大测试文件中快速定位红色阶段的意义在于测试先行的代码在实现缺失时必然失败从而证明测试本身有效。绿色阶段用最小代码让测试通过为什么返回nil因为我们的#add确实接收了两个参数但什么也没做加入让测试通过所需的最小代码量# lib/calculator.rb class Calculator def add(a,b) a b # add this end end再次运行测试你会看到一个点号.代表测试通过. Finished in 0.0032 seconds (files took 0.14864 seconds to load) 1 example, 0 failures重构阶段何时需要本例中#add本质上是单行方法无需重构。但随着 Ruby 水平提升方法会越来越复杂你可能会发现需要付出额外努力去遵循 SOLID 原则。到那时红-绿-重构循环能让你在重构时充满信心——测试保证你的类及其行为持续满足既定期望。这也是 ruby/automated_testing/test_driven_development.md 强调的关键点从红色走向绿色所写的代码应恰好满足测试如果发现实现做的比测试要求的更多往往是方法职责过重或测试覆盖不全的信号。深入匹配器eq、be 与 include基础篇 rspec_part_one_basics.md 对常用匹配器做了系统讲解与本文的eql形成互补匹配器比较方式典型场景eql使用eql?比较判断值是否相等本课expect(calculator.add(5, 2)).to eql(7)eq使用比较最通用、最常用的匹配器如expect(user.name).to eq(David)be直接判定真/假断言谓词方法expect(todo_list.completed?).to be(true)include判断包含关系断言集合包含元素expect(todo_list.tasks).to include(New task)以include为例它类似 Ruby 集合的include?方法适合只关心实际值的一部分的场景——比如测试向已有一项任务的列表新增任务时我们只关心新任务是否被包含而不关心旧任务。be匹配器则让断言谓词方法的测试读起来更自然expect(...).to be(true)比expect(...).to eq(true)更贴近英文语义。测试的结构Arrange-Act-Assert 与 teardown一个测试的三个阶段rspec_part_one_basics.md 提出每个测试通常由三个明确阶段构成Arrange准备搭建测试所需条件如todo_list TodoList.new(My List, [Incomplete Task])Act执行调用被测行为如todo_list.complete_task(Incomplete Task)Assert断言验证行为是否符合预期如expect(todo_list.tasks).not_to include(Incomplete Task)。teardown清理共享状态当测试改变了系统全局状态如$global_counter这类全局变量、数据库或文件系统数据时还需要第四阶段teardown——在测试执行后把状态恢复到测试前防止状态在测试之间泄漏导致随机失败。例如it increments the counter by one do # arrange counter Counter.new # act counter.increment! # assert expect(counter.count).to eq(1) # teardown counter.reset! end若不加 teardown两个依赖同一全局计数的测试会因随机执行顺序而互相污染有时increment by one失败、有时increment by two失败。共享测试代码hooks、let 与 subject当多个测试出现重复的 arrange/teardown 代码时RSpec 提供了代码共享工具详见 ruby/automated_testing/rspec_part_two_code_sharing.mdHooksbefore钩子在测试执行前运行最适合共享 arrange 代码默认在每个测试前各执行一次保证每个测试拿到全新对象、状态不泄漏传入:all则只在全部测试前执行一次。课程建议避免使用:all坚持默认的每个测试前行为。after钩子在测试执行后运行最适合做 teardown 清理。RSpec.describe Counter do before { counter Counter.new } after { counter.reset! } it increments the counter by one do counter.increment! expect(counter.count).to eq(1) end endlet 与 subject实例变量存在易拼写错误、可能跨测试泄漏等缺陷RSpec 提供了更惯用的let它是惰性求值的——只有在测试中首次调用时才执行。let!则像 before 钩子一样立即执行后续写 Rails 数据库测试时会派上用场。RSpec.describe User do let(:user) { User.new(David, davidexample.com, 30) } endsubject是专门为被测对象的实例设计的更特化的写法命名 subject 是最佳实践RSpec.describe User do subject(:user) { User.new(name, email, age) } let(:name) { David } let(:email) { davidexample.com } let(:age) { 30 } end规则很清晰创建被测对象用subject其他共享数据用let。借助contextdescribe的别名语义上用于按状态/条件分组与在子级 context 中覆盖同名的let变量即可优雅地测试同一方法在不同条件下的输出——例如把let(:age) { 65 }覆盖进context when the user is 65来测试retired?方法的三个分支。RSpec 会使用离测试最近的同名let定义。不过要注意这些工具也可能被过度使用如果读者需要反复在 hooks、lets 与测试体之间跳跃才能理解一个测试反而损害可读性。多数情况下把 arrange/teardown 代码直接写在用它的测试块里更清晰。读懂与定制 RSpec 输出三种测试状态通过前面的实战你已经接触了 RSpec 输出中的三类状态绿色通过输出单个点.统计为0 failures红色失败输出F与失败详情expected vs got、失败行号黄色待定 pending空it块默认被视为通过若想明确标记尚未完成可用xit临时跳过输出显示(PENDING: Temporarily skipped with xit)详见 rspec_part_one_basics.md。用 --format documentation 提升可读性默认进度格式点号不够直观。试试rspec --format documentation它会利用describe与it的参数把测试以缩进树形结构打印出来例如TodoList name returns the name with 1 task remaining returns the name with 0 tasks remaining returns the name with 2 tasks remaining Finished in 0.00247 seconds (files took 0.1574 seconds to load) 3 examples, 0 failures用 .rspec 固化命令行配置如果你喜欢 documentation 格式不必每次手动输入该 flag——把配置写进.rspec文件即可持久生效。初始化后的.rspec默认内容为--require spec_helper打开文件在新行追加--require spec_helper --format documentation --order rand--format documentation固定使用文档化输出格式--order rand随机执行测试顺序。这能倒逼测试彼此独立、确定性执行——若测试依赖特定顺序才能通过说明测试套件过于脆弱容易产生难以排查的失败。课程配套练习亲手拆掉你的 Calculator将新知付诸实践——按课程要求拆掉我们刚写好的 Calculator 测试为#add增加新测试用例下面的测试期望add(2, 5, 7)返回 14。先运行看到失败再写让两个测试同时通过的最小代码必要时重构。# spec/calculator_spec.rb RSpec.describe Calculator do describe #add do it returns the sum of two numbers do # removed for brevity end # add this it returns the sum of more than two numbers do calculator Calculator.new expect(calculator.add(2, 5, 7)).to eql(14) end end end提示Ruby 方法可以接收可变数量参数如def add(*numbers)这正是让add(2, 5, 7)可用的关键也是练习的价值所在。为新方法编写测试用新的describe块为#multiply、#subtract或#divide写测试至少包含一个it块与合适的期望子句让它通过并在必要时重构。对比输出格式在终端用rspec --format documentation运行你的测试观察与默认输出的差异。固化配置若喜欢 documentation 输出就把--format documentation追加到.rspec文件新行之后无需再手动输入该 flag。完成本课练习后课程还为你规划了后续实战路径为 Caesar Cipher 项目 和 Tic Tac Toe 项目 编写测试可引入 RuboCop RSpec 扩展辅助规范最终以 TDD 方式从零构建 Connect Four 项目——先写失败的测试再写恰好让测试通过的代码然后重构以此循环覆盖每个方法。知识检查与总结回顾本课核心知识点你可以用以下问题自测测试文件应放在哪个文件夹——spec/目录文件名以_spec.rb结尾。RSpec.describe定义了什么—— 一个示例组Example Group即一组相关测试的集合可嵌套。it定义了什么—— 单个示例example即一个具体测试用例。一个测试用例可以写几个 expect 子句—— 课程规范建议只写一个。RSpec 有哪些相等性匹配器—— 如eqleql?比较、eq比较以及be、include等。如何告诉 RSpec 被测代码的位置—— 在 spec 文件顶部用require ./lib/xxx引入。从安装gem install rspec、rspec --init初始化到describe/it/expect三大核心语法再到完整的红-绿-重构TDD 实战你现在已经掌握了 RSpec 的全部基础构件。接下来你可以继续学习 ruby/automated_testing/rspec_part_two_code_sharing.md 中的 hooks、let、subject 与 context或在 ruby/testing_ruby_with_rspec/test_driven_development.md 中深化对 TDD 原理的理解把这些技能真正融入日常开发工作流。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表