Day 12 Hooks 们以及作用域的差别!

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

After Hooks

之前有介绍过 before hooks 的使用方法,今天也可以介绍一下 after hooks 的使用方法,我们先用范例来看看!

RSpec.describe "before and after hooks" do
  before(:example) do
    p "before hooks"
  end
  
  after(:example) do
    p "after hooks" 
  end
  
  it "test for before & after" do
    p "inner example"
    expect(1 + 1).to eq(2) 
  end
end

下面这是 Output 的结果,可以看到执行顺序的不同!

那一定会很好奇,什麽时候要使用 after hooks,有时候我们为了效能考虑,某些工程师会不想要每次都产生一个新的物件。

所以会在 after hooks 中把物件 Reset 资料或是清空资料库等等的动作,都会在这时候使用!

可以用一个简单的范例来看看~

RSpec.describe "before and after hooks" do
  before(:example) do
  end
  
  after(:example) do
    p @array
    p @array.clear
  end
  
  it "test for before & after" do
    @array = []
    p "Inner #{@array}"
    @array << '1'
    expect(1 + 1).to eq(2) 
  end
end

我们在测试中产生一个 @array 然後在 after hooks 中清空它,下面是 Out put 的样子!

可以看到 @array 在结束後被清除了,这对於某些情况是有帮助的,但使用的程度并没有到极高的情形,还是可以把它记起来,说不定哪天会派上用场喔!

但我自己还是比较喜欢在每个例子前产生物件,最後还是取决於你们团队的决定和方向~

Before & After Hooks 的选项

刚刚介绍完 after hooks 的运作模式,我要介绍一下传入的参数所造成的差别!

我们一般都是预设在 (:each) 的情形,也就是你每一个 example 都跑一次的意思!

这边介绍一个 (:context) 的选项,可以让你在 describe & context 的前後执行 hooks,我们来看看例子吧!

RSpec.describe "before and after hooks" do
  before(:context) do
    p "before context"
  end

  before(:example) do
    p "before example"
  end
  
  after(:example) do
    p "after example"
  end
  
  after(:context) do
    p "after context" 
  end

  it "test for before & after" do
    expect(1 + 1).to eq(2) 
  end
end

接着看 OutPut 会非常明显和清楚!

(:context) 会在整个测试最开始的时候执行一次,然後会在整个测试结束的时候执行!

这个的用处在於,某些设定你只需要一次,就可以在每个测试里面拿到,而不需要每一个 example 都产生一次,这样就会有效能上很大的差别。

所以写测试的时候,稍微考虑一下是不是都会被使用到而且又不会被改变,如果答案是确定的!那就可以使用 (:context) 的选项来帮助你喔!

箝套的逻辑和顺序

熟悉的两个 hooks 的运作模式之後,我们要来把 hooks 加入箝套里面测试一下自己是不是真的理解了?

所以我们先上程序码( 可以自己先想一下答案! )

RSpec.describe "before and after hooks" do
  before(:context) do
    p "OUTER Before context"
  end

  before(:example) do
    p "OUTER Before example"
  end

  it "will pass" do
    expect(1 + 1).to eq(2)
  end

  context "when it can pass" do
    before(:context) do
      p "INNER Before context"
    end

    before(:example) do
      p "INNER Before example"
    end

    it "will pass too" do
      expect(2 + 2).to eq(4)
    end
  end
end

有办法一步步的推敲出正确答案的话,代表已经完全理解了 hooks 的运作顺序了。

我们要去思考测试码是由上至下的读取,所以会先经过什麽?遇到什麽又会触发什麽?

只要能一步一步地厘清其实就不会感觉非常复杂~

公布答案:

我猜最容易没想到的地方就是在 context 里面的 example 也吃到了外面的 before hooks 吧!

最外层的物件,在里面也是拿得到的,这对於一开始写 RSpec 的人还蛮有帮助的。

因为在一边整理程序码的时候,会出现这个物件会不会拿不到,要不要再写一个 let 等等的疑问~

那至於为什麽要在不同的地方写 hooks 呢?

是因为某些 Context 可能有属於他的物件需要放在里面,若是所有的东西都放在最外层,很有可能不小心就命到相同的名字,或是看起来很杂乱!

虽说写在最外面肯定是不会有什麽问题,但能够精准的物件放在他的作用域,也是一个工程师厉不厉害的指标喔!

利用作用域来覆写物件

刚刚提到了内层可以取得外层的物件,但如果我想要在 context 里面测试同一个物件,不同的行为怎麽做呢?

我们先用个范例来看看:

class Burger
  attr_accessor :type
  def initialize(type = "small")
    @type = type
  end
end

RSpec.describe Burger do
  let(:burger) { Burger.new("Big") }
  
  it "type will be Big" do
    expect(burger.type).to eq("Big")
  end
  
  context "when we didn't give argument" do
    let(:burger) { Burger.new } # 利用覆写来通过测试
    it "typ will be small" do
      expect(burger.type).to eq("small")
    end
  end
end

当我们想要给一个 context 的情境来测试没有传入参数的预设值时,要如何可以通过测试?

我们可以利用覆写 burger 这个物件的内容,来通过测试。

当测试码跑到第 18 行时,他会从自己的作用域开始找寻一个叫做 burger 的物件,若是没有就会往上去找!

所以不覆写的话,会找到第 9 行的 burger 物件!就会没办法通过测试。

所以当我们理解了作用域,就可以随心所欲的撰写测试,根据不同的情境加入不同的条件!

而且使用 let 也不担心效能的问题,还记得我们在介绍 let 的时候提过的 lazy-loading 吗? 超赞的!

小结

今天介绍了 RSpec 的作用域,以及覆写带来的帮助,希望可以帮到像我一样的新手。

毕竟测试也不是超基本的知识,还是需要自己去读文件和练习 KATA 才会进步!

所以希望自己的想法可以有所帮助~

明天会介绍 subject 这个物件,也会介绍缩短程序码的小方法!( Ruby 生态圈就是越简洁优雅越好 )


<<:  Day19-"字串练习-2"

>>:  DAY 11 Operators

端点安全防护 - 端点防护软件 EDR

又饶舌了 适用人员: 技术人员。 适用法规: 资通安全责任等级分级办法 技术面分类提要 网路架构 端...

ASO 的重要项目

大部分的人下载 App 都是直接在 App Store 或 Google Play 搜寻 ASO 就...

第28天:『SEO优化第十步』-内部连结和外部反向连结

SEO优化-内部连结和外部反向连结 所谓的连结通常是使用文字、图片或是网址作为超连结,SEO优化中常...

Day 30 Compose UI never End

总算写到 30 天了,谢谢各位看倌的耐心, 最後一篇是今年铁人赛的总回顾,我想对自己一开始的规划是否...

【D32】结尾

这个系列是制作讯号灯,制作出一些简单的讯号灯,当作我们判断的依据。这些灯号之後还可以做出更为精细的比...