在編寫需要發(fā)送郵件的應(yīng)用時,控制器是絕不能被阻塞的,因此異步發(fā)送必不可少。為了實現(xiàn)這個途徑,郵件發(fā)送代碼必須從request/response周期轉(zhuǎn)移到可以在后臺異步處理的進程中去。
那么,如此處理之后,代碼的正常運行又改如何保障?本篇博文中,我們將重點關(guān)注測試的途徑,同時將會使用MiniTest(Rails已經(jīng)內(nèi)置了這個框架),但是使用的理念卻可以很簡單地轉(zhuǎn)換為Rspec。
現(xiàn)在有一個好消息,那就是從Rails 4.2開始,異步郵件發(fā)布已經(jīng)比之前簡單多了。我們在例子中使用Sidekiq作為隊列系統(tǒng)。由于ActionMailer#deliver_later建立在ActiveJob之上,接口非常的簡潔明了。這表示,要不是我剛才提了一下,身為開發(fā)者或用戶的你也不會知情。建立隊列系統(tǒng)是另外一個話題,你可以在getting started with Active Job here中了解更多詳細信息。
在例子中,假設(shè)你已經(jīng)正確配置了Sidekiq及其依賴組件,因此本場景中特有的代碼就是申明Active Job該使用哪一個隊列調(diào)節(jié)器。
Active Job可以幫助用戶大幅度避免隊列配置細節(jié),在Resque、Delayed Job或其他工作上也可以使用。因此,如果我們轉(zhuǎn)而使用Sucker Punch,的改變就是在引用相應(yīng)的依賴包后,將queue_adapter從:sidekiq改為:sucker_punch就可以了。
如果你對Rails 4.2或者Active Job不太了解,https://blog.engineyard.com/2014/getting-started-with-active-job可以幫助你開始。然而,這篇文章留給我的一個小期許是,找到一種簡潔、地道的測試方法,從而讓所有組件都能正常的運行。
根據(jù)本文的目標,我們假定已經(jīng)部署了:
Rails 4.2或者一個更高的版本
已經(jīng)設(shè)置好queue_adapter的Active Job (Sidekiq、Resque等)
一封郵件
任何郵件都應(yīng)該能夠按照這里描述的方式正常工作,這里我們就用一封歡迎郵件來使這個例子更實用:
為了保持程序簡單并有針對性,這里會在每個用戶注冊后發(fā)送給他們一封歡迎郵件。
這和the Rails guides mailer example是一樣的:
接下來,我們想確保控制器內(nèi)的任務(wù)能如所期待的那樣執(zhí)行。
在測試指南中,custom assertions for testing jobs inside other components的章節(jié)介紹了大約六種這樣的自定義斷言方法。
或許直覺告訴你應(yīng)該單刀直入,然后使用assert_enqueued_jobsassert-enqueued-jobs來測試每次添加新用戶時,我們有否將郵件傳送任務(wù)放入隊列。
你可能會這么做:
然而如果這么做,你會驚奇地發(fā)現(xiàn)測試失敗了,系統(tǒng)會告訴你assert_enqueued_jobs未經(jīng)定義,且無法使用。
這是因為,我們的測試類繼承自ActionController::TestCase,而后者在編寫時沒有包含ActiveJob::TestHelper。
不過我們很快就可以修正這一點:
假定我們的代碼如期執(zhí)行,那么測試應(yīng)該就能順利通過了。
這是好消息。現(xiàn)在,我們可以重構(gòu)我們的代碼,增加新的功能,也可以增加新的測試。我們可以選擇后者,看看我們的郵件有否投遞成功,如果是的話,那就檢查投遞的內(nèi)容是否正確。
ActionMailer能為我們提供一個包含所有發(fā)出郵件的隊列,前提是將delivery_method選項設(shè)置為:test,我們能通過ActionMailer::Base.deliveries讀取這個隊列。
在同步投遞郵件時,檢測郵件是否發(fā)送成功是很容易的。我們只需檢查在動作完成后,投遞計數(shù)器加1。用MiniTest來寫的話,就像下面這樣:
我們的測試是實時發(fā)生的,但在開篇就已經(jīng)知道不能阻攔控制器,需要在后臺進程中發(fā)送郵件,現(xiàn)在我們把所有的組件都組裝起來,確定系統(tǒng)是沒有問題的。因此,在異步的世界里,我們必須先執(zhí)行所有隊列中的任務(wù)再去判定執(zhí)行結(jié)果。為了執(zhí)行pending中的Active Job任務(wù),我們使用perform_enqueued_jobs:
目前為止,我們都在進行功能性測試以確保我們的控制器如期執(zhí)行。但是,代碼的變化足以破壞我們發(fā)送的郵件,為什么不對我們的郵件程序進行單元測試,從而縮短反饋流程,然后更快地洞察變化呢?
Rails測試指南建議在這里使用fixtures,但是我覺得他們太生硬了。尤其是一開始,當我們還在嘗試設(shè)計郵件時,快速變化就會讓他們變得不可用,讓我們的測試無法通過。我偏向使用assert_match以關(guān)注那些構(gòu)成郵件主體的關(guān)鍵元素。
為此,也因為其他原因(比如抽離處理多部分郵件的邏輯結(jié)構(gòu)),我們可以建立自定義斷言。這可以擴展MiniTest標準斷言或Rails專屬斷言。這也是創(chuàng)建自己的領(lǐng)域?qū)僬Z言(Domain Specific Language)并用于測試的好例子。
讓我們在測試一文件夾內(nèi)創(chuàng)建一個共享文件夾,用以存放SharedMailerTests模塊。我們自定義的斷言可以這么來寫:
接下來,我們需要讓郵件測試系統(tǒng)注意到這個自定義斷言,為此,我們可以將其放入ActionMailer::TestCase類中。然后可以借鑒之前把ActiveJob::TestHelper類包含于ActionController::TestCase類的方法:
注意,我們首先需要在test_helper中require shared_mailer_tests。
這些辦好之后,我們現(xiàn)在可以確信我們的郵件中包含我們期望的關(guān)鍵元素。假設(shè)我們想確保發(fā)送給用戶的URL包含一些用于追蹤的特定UTM參數(shù)。我們現(xiàn)在可以將自定義斷言與老朋友perform_enqueued_jobs聯(lián)合起來使用,就像這樣:
在Active Job的基礎(chǔ)上,使用ActionMailer讓從同步發(fā)送郵件到通過隊列發(fā)送郵件的轉(zhuǎn)化變得如此簡單,就如同從deliver_now轉(zhuǎn)化到deliver_later。
同時,由于使用Active Job大大簡化了設(shè)定工作基礎(chǔ)環(huán)境的流程,你可以對自己所用的隊列系統(tǒng)知之甚少。希望這篇教程能讓你對此過程有更多了解。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個個人學習交流的平臺,網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。