# RSpec ### Using Test Doubles as Mocks and Stubs * SUT (System Under Test) 待側系統 * DOC (depended-on component) 依賴元件 #### Test Doubles 即為測試替身,一個系統流程包括資料的傳輸(params)以及系統的架設(action),而在整個程式中往往運作流程繁雜(A→B→C→D.....),所以並不會建構出完整流程來進行測試,因此需要一個“替身”來減少複雜度,並且專注於特定部分的測試。 ![](https://i.imgur.com/33QEBGk.gif) ```ruby= RSpec.describe 'double' do let(:store) { double('store', sell: 'earn the money!')} it 'should invoke method first' do expect(store.sell).to eq('earn the money!') end end ``` #### Stubs 可以回傳預先設定值的假物件,但並不會實際呼叫方法(**stub屬於消極方法**) ![](https://i.imgur.com/EW5kOdE.gif) ```ruby= class NumberGenerator def random "A"rand(1..10) end end RSpec.describe "Random" do it "generates a random number" do generator = NumberGeneraor.new allow(generator).to receive(:rand).and_return(5) #stub expect(generator.random).to eq("AAAAA") end end ``` ##### 使用時機: 1. 必須獲得資料才可以繼續進行→中斷測試 2. 獲得資料過程中運行過久(撈取資料龐大)→影響效率 ```ruby= #對find使用stub以避免向資料庫進行操作 it "stubs the class" do allow(project).to receive(:find).and_return(Project.new(name: "Project Greenlight")) project = Project.find(1) expect(project.name).to eq("Project Greenlight") end ``` 3. 觀察異常(要求回應無效結果) #### Mocks 除了回傳預定值以外,也會對待測系統內部流程驗證與實際測試狀況呼叫與否(**mock屬於積極方法**) ![](https://i.imgur.com/TOqoj7g.gif) ```ruby= #必須呼叫以期望的方法 it "expects stuff" do mocky = double("Mock") expect(mocky).to receive(:name).and_return("Paul") expect(mocky).to receive(:weight).and_return(100) expect(mocky.name).to eq("Paul") end ``` ```ruby= #待側系統中必須含有測試方法 class ImageFlipper end RSpec.describe "ImageFlipper" do it "flips an image" do processor = double("processor") expect(processor).to receive(:flip).with("image.jpg") im = ImageFlipper.new(processor) im.flip("image.jpg") end end end ``` ```ruby= #修改後 class ImageFlipper def initilize(processor) @processor = processor end def flip(flie) @processor.flip(file) end end ``` #### Instance Doubles 如果使用double時,待測系統內部程式碼的更動,並不會影響測試結果通過與否,而使用Instance Doubles時,可以對於方法是否存在進行驗證 ```ruby= class Image #沒有寫方法 end RSpec.describe Image do it "#take_picture" Image = double(take_picture: "ok!") expect(Image.take_picture).to eq("ok!") #測試失敗,因為沒有take_picture這個方法 end end ``` ```ruby= class Image def take_picture #寫入方法 "ok!" end end RSpec.describe Image do it "#take_picture" Image = instance_double(Image, take_picture: "ok!") expect(Image.take_picture).to eq("ok!") #通過測試 end end ``` #### Spies 跟 test double概念相像但會驗證方法有沒有被使用到 ![](https://i.imgur.com/5Nm1zB0.gif) ```ruby= RSpec.describe 'spies' do let(:burger) { spy('burger') } it 'confirm a message has been received' do burger.eat expect(burger).to have_received(:eat) end end ``` 比較test double寫法順序 ```ruby= RSpec.describe 'double' do let(:store) { double('store', sell: 'earn the money!')} it 'should invoke method first' do expect(store.sell).to eq('earn the money!') end end ``` --- #### 參考 >https://ithelp.ithome.com.tw/users/20140124/ironman >http://xunitpatterns.com/Test%20Double.html >https://juejin.cn/post/6968624352191315975 >https://partypeopleland.github.io/artblog/2020/09/08/what-is-stub-and-mock/ factory stub mock使用時機 instance double是否需要包含class?