Day 10 实用的 let 方法以及客制化错误讯息!

该文章同步发布於:我的部落格

改变数值的时候

昨天提到变动性的问题是什麽呢?

我们到现在的测试都是很单调的,也就是测一个物件本身的属性,那如果我们实作的功能会改变属性呢?我们还是可以通过测试吗?

我们用昨天实作的 Ruby 方法来试试看,遇到变动会发生什麽事吧!

RSpec.describe Burger do
  def burger
    Burger.new('Beef', 'Cheddar')
  end
  
  it "has cheese" do
    expect(burger.cheese).to eq('Cheddar')
    burger.cheese = 'Ricotta'
    expect(burger.cheese).to eq('Ricotta')
  end
  
  it "has meat" do
    expect(burger.meat).to eq('Beef')
  end
end

上述测试代码的第 8.9 行是新增上去的,根据语意可以知道我们想要改变起司的种类,在测试他的种类是不是我们新增上去的那一个!

咦!竟然没有通过,可以看到错误讯息中,我们期待得到 Ricotta 但却拿到 Cheddar

原因就出在於,我们呼叫的是一个方法,每呼叫一次都是一个新的物件诞生,他并不会存活在 block 里面

所以我说用 Ruby 的方法写没有不行,但遇到这种会改变数值的状况就会很麻烦~

那我们既不想要用 before hooks 这麽大包的东西还要转换成实体变数,又希望物件可以存活在 example 里面!

有这麽好康的东西吗?当然!

let helper method

提到 let 方法之前,需要提一下 Memorization 的概念,记忆的过程吗?详细怎麽翻译我不太清楚,但我想讲述一下概念!

今天老师上课给了你一个很难的数学题目,你花了一个小时做完,也告诉老师答案了,过了 10 分钟,老师又给了你一模一样的题目,你还会花一个小时做完吗?

不,你会直接给他答案!这就是 Memorization 的概念,而 let 方法就有一点点这样的概念存在喔!

我们先上修改好的程序码:

RSpec.describe Burger do
  let(:burger) { Burger.new('Beef', 'Cheddar') }
  
  it "has cheese" do
    expect(burger.cheese).to eq('Cheddar')
    burger.cheese = 'Ricotta'
    expect(burger.cheese).to eq('Ricotta')
  end
  
  it "has meat" do
    expect(burger.meat).to eq('Beef')
  end
end

完成了!而且可以正确地通过测试,让我娓娓道来!

let 的後方括号内要放的是你想要命名的变数,这边我们放的是 :burger 记得要使用符号的形式,而後方的 block 中则是你要执行的程序码!

let(:burger) { Burger.new('Beef', 'Cheddar')}

他会把程序码执行完的结果,放到括号内的符号里,而你在使用它的时候就像是区域变数一样,超级无敌方便的啦!

还有更棒的地方就是,他会根据每一个 example 初始化,所以不需要担心物件被污染的问题。

所以我们第一次呼叫 let 是在第 5 行的地方,第二次则是在第 11 行的地方,根据刚刚提到过的 Memorization,在同一个 example 中,他都会是同一个物件,也不会再次的呼叫 let 方法!

可以来测试看看,我们把 burger 给印出来

RSpec.describe Burger do
  let(:burger) { Burger.new('Beef', 'Cheddar') }
  
  it "has cheese" do
    expect(burger.cheese).to eq('Cheddar')
    p burger
    burger.cheese = 'Ricotta'
    expect(burger.cheese).to eq('Ricotta')
  end
  
  it "has meat" do
    expect(burger.meat).to eq('Beef')
    p burger
  end
end

可以看到结果储存的记忆体位置是不相同的!

还记得 before hooks 其实和这个很像,但他是在每一个 example 前都会先执行,也就是说即使你的 example 内没有使用到,他也会执行!

let 有个很厉害的功能 Lazy-loading,也就是说当我们呼叫这个变数的时候,他才去执行这段程序码。

我们也来测试看看 before hookslet 到底差别在哪?

RSpec.describe Burger do
  let(:burger) { p 'let method' }
  before do
    p 'before hooks'
  end
  
  it "has cheese" do
    burger
  end
  
  it "has meat" do
  end
end

我们只有在一个 example 内呼叫了 burger 这个变数,而 burger 这个变数的内容是印出 let method 这段文字!而 before hooks 则是印出 before hooks 这段文字,让我们来看看结果吧。

before hooks 在每一个 example 前都自动执行了,不管你要或不要,但 let method 却只有在我们呼叫的 example 执行,另外一个则不执行!

如果我们确认这个东西是所有的 example 都需要使用的话,也可以用 let! method,但如果只有零星几个,用 let method 可以增加效能,而且更短更好看,也不用使用实体变数~

至於 let! method,加了一个惊叹号就代表说他会在所有的 example 前执行,就像 before hooks 一样,所以我说 let 真的非常强大也非常好用,基本上用这个就可以应付大多数的测试了。

客制化你的错误讯息

我们常见的错误讯息:

但我们可以客制化我们想要的消息在上面,直接上程序码再来讲解!

RSpec.describe Burger do
  let(:burger) { Burger.new('Pork', 'Cheddar')}

  it "has cheese" do
    expect(burger.cheese).to eq('Cheddar')
    burger.cheese = 'Ricotta'
    expect(burger.cheese).to eq('Ricotta')
  end
  
  # 修改肉变成 Pork 来故意出错,显示客制的讯息~
  it "has meat" do
    comparison = "Beef"
    expect(burger.meat).to eq(comparison), "我想要 #{comparison} 而不是 #{burger.meat}"
  end
end

测试出错结果:

还记得我们有提过 to 是一个方法,而他除了一定要接受 expection 之外,也可以增加一些额外的选项,例如:客制化错误。

可以想像原本是这样:

expect(burger.meat).to(eq(comparison), "我想要 #{comparison} 而不是 #{burger.meat}")

只是因为可以省略小括号,让人看起来有点不直觉~但这就是 Ruby 的特色喔!

至於 #{} 这个写法是很常见的在字串中安插变数的方法,也是 Ruby 很基本的使用方法。

小结

呼~今天讲了很重要的 let 方法,个人觉得超级好用!

明天我们要开始介绍 context 这个方法以及 箝套的 describe

箝套就是 Nested,像洋葱一样一层一层包下去的概念


<<:  Day9-TypeScript(TS)的介面型别(Interface)Part 2

>>:  IDEF 构图方法

Day 7 Dart语言-资料型态

资料型态 内建资料型态是构成整个程序的最小型态单位,是程序中不可或缺的元素,而Dart的内建类型主要...

Day28 实作todoList(三)新增事项到State

Header、Button、List元件完成後,我们在根元件App.js依序引入使用, todoLi...

认识Innodb储存引擎

我们知道储存引擎是实际实现读取、储存资料的重要东西 所以今天就来针对Innodb(MySQL的预设引...

Day 09 - 继续加油

React使用jsx的方式撰写,他是一种语法糖(让程序更简单撰写),我们拿前天建立的专案来修改,在d...

不是进行渗透测试的最佳时机

机构更关心的是一个新类型的攻击比发现已知类型的攻击。此外,如果风险保留在风险记录中(风险保留),则表...