惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
Recorded Future
Recorded Future
Jina AI
Jina AI
The Register - Security
The Register - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
F
Fortinet All Blogs
人人都是产品经理
人人都是产品经理
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
L
LangChain Blog
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
博客园 - 【当耐特】
雷峰网
雷峰网
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
I
InfoQ
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
有赞技术团队
有赞技术团队
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
Google Online Security Blog
Google Online Security Blog
WordPress大学
WordPress大学
Hacker News - Newest:
Hacker News - Newest: "LLM"
NISL@THU
NISL@THU
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
Cyberwarzone
Cyberwarzone
K
Kaspersky official blog
F
Full Disclosure
Martin Fowler
Martin Fowler
Spread Privacy
Spread Privacy
D
Docker
C
Cisco Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Hacker News: Front Page

任峻宏的小站

Debugging Memory Leaks in a Next.js Application Troubleshooting: Resolving Devise Issue in an API-only Application How to Customize a Right-Click Menu in React Using ActionCable and React to Create a Simple Chat App 纪念左耳朵耗子 - 任峻宏的小站 Using SSE to Implement ChatGPT in Rails Refresh Myself - 任峻宏的小站 战痘 - 任峻宏的小站 逆向的能量——生活需要批评者 - 任峻宏的小站 人活着必须要有目标 - 任峻宏的小站 《第一人称单数》读书笔记 - 任峻宏的小站 卸载英雄联盟 - 任峻宏的小站 我的 2021 年总结 - 任峻宏的小站 记我的爷爷 - 任峻宏的小站 Migrate from Webpacker to Vite 从入门到放弃 《人生算法》读书笔记 - 任峻宏的小站 《不拘一格》读书笔记 - 任峻宏的小站 我为什么很少写博客 - 任峻宏的小站 我的2020年总结 - 任峻宏的小站 如何避免冲动消费? - 任峻宏的小站 《不能不去爱的两件事》读书笔记 - 任峻宏的小站 记一次搬家 - 任峻宏的小站 My blog V3.0 has been published! 解决服务器上运行 bundle install 时 killed 的问题 解决生产环境不能发送邮件的问题 - 任峻宏的小站 解决内存泄漏记录 - 任峻宏的小站 勿忘初心 - 任峻宏的小站 使用 AJAX 重载部分页面 - 任峻宏的小站 Nginx 添加 SSL 支持 - 任峻宏的小站 Nginx 中进行重定向配置 - 任峻宏的小站 记我的第二次部署 - 任峻宏的小站 写在“毕业”之际 - 任峻宏的小站 JS 简单实现弹出层效果 - 任峻宏的小站 速递易工作总结及感想 - 任峻宏的小站 在 Aliyun ECS 上用 Nginx + Passenger 部署 Rails 应用时安装 Passenger 的问题 [译] Common Rails Idioms that Kill Database Performance Git 常用操作总结 - 任峻宏的小站 [译] Terminal 功夫——方便开发者的实用技巧 - 任峻宏的小站 实例方法与类方法 - 任峻宏的小站 各种报错解决集合 - 任峻宏的小站
关于 RSpec 的一点方法总结 - 任峻宏的小站
任峻宏 · 2017-07-23 · via 任峻宏的小站

注:原文翻译时有删改

两个主要的框架垄断了Ruby的测试界:Rspec 和 MiniTest. Rspec 是一个非常有表现力的测试框架,它有很多好的特性和辅助方法来让测试变得可读。当我们用 Rspec 写测试的时候,有几个小的方法,或许可以让测试更好写、更易读、更利于维护。

现在假设有一个系统,有书(Books) 和 作者(Authors),让我们使用一些方法来简化测试。

class Book
  attr_reader :title, :genre

  def initialize(title, genre)
    @title = title
    @genre = genre
  end
end

class Author
  attr_reader :books

  def initialize(name, books)
    @name = name
    @books = Array(books)
  end

  def has_written_a_book?
    !books.empty?
  end
end

let 和 subject

let() 有两个好处: - 不用赋值给实例变量就可以缓存值; - 定义的变量是“惰性计算”的,不调用就不会执行赋值操作;

subject{} 可用来声明测试的对象,后续的测试用例就无需明确指定了。

通过声明 letsubject 变量是一个保持测试可读性和不重复自己(DRY)的好方法。

举个例子,如果我们想确认一个作者有名字(assert an Author has a name),如果不用 letsubject变量,测试大概长这样:

describe Author do
  before do
    @book_genre = 'Historical Fiction'
    @book_title = 'A Tale of Two Cities'
    @book = Book.new(@book_genre, @book_title)
    @author_name = 'Charles Dickens'
    @author = Author.new(@author_name, [@book])
  end

  describe '#name'do
    it 'has a name set' do
      expect(@author.name).to eq(@author_name)
    end
  end
end

如果再加上别的测试,比如确认书的数量,不同的名字,或者其他关于这个作者的东西的话,这测试就会变得很冗长。

所以,我们可以使用 letsubject 变量来实现DRY:

describe Author do
  let(:book_genre) { 'Historical Fiction' }
  let(:book_title) { 'A Tale of Two Cities' }
  let(:book) { Book.new(book_genre, book_title) }
  let(:book_array) { [book] }
  let(:author_name) { 'Charles Dickens' }
  subject { Author.new(author_name, book_array) }

  describe '#name'do
    it 'has a name set' do
      expect(subject.name).to eq(author_name)
    end
  end

  describe '#books' do
    context 'with books' do
      it 'has books set' do
        expect(subject.books).to eq(book_array)
      end
    end

    context 'without books' do
      context 'books variable is nil' do
        let(:book_array) { nil }

        it 'sets books to an empty array' do
          expect(subject.books).to eq([])
        end
      end

      context 'books variable is an empty array' do
        let(:book_array) { [] }

        it 'sets books to an empty array' do
          expect(subject.books).to eq([])
        end
      end
    end
  end
end

不再需要几个 before 块来定义一个个实例变量,这段代码用了 let,简洁易读。更具体地说,这些测试的工作机制是:每当一个 it 块运行的时候,context里面离得最近的 let就被用来初始化这个 subject

通过设置 let 变量,如果想要测试 subject.books是不是一个数组,判断输入是nil或者[],只需要简单地修改一下 let 声明:let(:book_array) { nil }

Loose Expectations

如果一个测试不关心细节,Rspec 允许使用 general expectations 和 占位符(placeholders). 这些占位符可以只专注于真正重要的东西,从而简化测试。

1. anything

正如其名,当一个方法要求一个参数但是这个具体的参数又对测试没有影响的时候,我们可以使用 anything 这个参数匹配符。

如果一个测试,想要确认一个作者已经写过书了,但不关心这本书的标题和类型,就可以用 anything

describe Author do
  describe '#has_written_a_book?' do
    context 'when books are passed in' do
      subject { Author.new(name, books) }
      let(:books) { [Book.new(anything, anything)] }
      it 'is true' do
        expect(subject.has_written_a_book?).to eq(true)
      end
    end
  end
end

2. hash_including

当我们要测试一个期望是 Hash 的方法,这个 Hash 中的某些元素可能比其他的元素更重要。hash_including 匹配符允许开发者 assert 一个或多个 hash 的键值对,而不用指定整个 hash。

假设 Book 类有一个方法,实例化了一个 HTTP client (用来取得一些附加信息):

class Book
  # ...
  def fetch_information
    HTTPClient.new({ title: title, genre: genre, time: Time.now })
              .get('/information')
  end
end

给这个方法写测试应该 assert 这个 client 已经初始化了几个关键点,这个场景就可以用 hash_including

describe Book do
  describe '#fetch_information' do
    let(:book_genre) { 'Historical Fiction' }
    let(:book_title) { 'A Tale of Two Cities' }
    subject { Book.new(title, genre) }

    it 'instantiates the client correctly' do
      expect(HTTPClient).to receive(:new)
                        .with(hash_including(title: book_title,
                                             genre: book_genre))
      subject.fetch_information
    end
  end
end

hash_including可以指定一个期望的 hash 中的键值对或者只是键。这里,这个测试只关心传入的书的标题(title)和类型(genre)。

3. match_array

在Ruby中,当且仅当两个数组包含同样的元素且顺序相同时,这两个数组是相等(equal)的。在一些测试中,这个严格相等的准则可能不是必须的。那些情况下,RSpec 提供了一个叫做 match_array的方法来让测试顺利进行。

如果 Author 类从数据库中取得了它的书的清单,由于默认的 scopes 或者记录的更新操作,书的顺序可能不是连续的。

现定义一个 fetch_books 方法:

class Author
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def fetch_books
    BookDB.find_by(author_name: name)
  end
end

使用 match_array,测试可以确认返回了合适的书,无视顺序:

describe Author do
  describe '#fetch_books' do
    let(:name) { 'Jane Austen' }
    let!(:books) do
      Array.new(2) do
        BookDB.create_book(author_name: name)
      end
    end

    subject { Author.new(name: name) }

    it 'fetches the books correctly' do
      expect(subject.fetch_books).to match_array(books)
    end
  end
end