【譯】使用Rails 4.2+ 測試非同步郵件系統
李哲 — MAY 26, 2015 原文連結:Testing async emails, the Rails 4.2+ way
假設想寫一個需要傳送郵件的應用,我們都知道在這種情況是絕對不能block控制器的,因此非同步傳送才是解決之道。為了達到這個目的,我們需要 將郵件傳送程式碼從最初的request/response
迴圈中移到後臺的非同步處理程式中去。
然而,做出這樣的改變之後,我們如何確保程式碼能夠一如往常的執行呢?在這篇博文中,我們會探索一種新方法來進行測試,我們將要使用的MiniTest(Rails
已經內建了這個框架), 這裡的概念同樣使用Rspec
。
現在有一個好訊息,那就是從Rails 4.2
開始,非同步傳送郵件已經比之前簡單多了。我們在例子中使用Sidekiq
作為佇列系統。由於ActionMailer#deliver_later
建立在ActiveJob
之上, 介面非常的簡潔明瞭。這表示,要不是我剛才提了一下,身為開發者或使用者的你也不會知情。建立佇列系統是另外一個話題, 你可以在getting started with Active Job here中瞭解更多相關的資訊。
別太依賴小元件
在例子中,我們假定Sidekiq
及其依賴元件配置正確,因此本場景特有的一段程式碼是宣告Active Job
該使用哪一個佇列調節器。
# config/application.rb
module OurApp
class Application < Rails::Application
# ...
config.active_job.queue_adapter = :sidekiq
end
end
Active Job
在隱藏實質性的佇列配置細節方面功能非常強大,以至於若是使用Resque
,Delayed Job
或其他元件,程式碼也不需要太大的改動。 因此,如果我們轉而使用Sucker Punch
,唯一的改變就是在引用相應的依賴包後,將queue_adapter
從:sidekiq
改為:sucker_punch
就可以了。
站在Active Job的肩膀上
如果你是Rails 4.2
或者Active Job
不太瞭解,https://blog.engineyard.com/2014/getting-started-with-active-job 是就是很好的入門讀物。然而,這篇文章留給我的一個小期許是,找到一種簡潔、地道的測試方法,從而讓所有元件都能正常的執行。
根據本文的目標,我們假定你已經部署了:
- Rails 4.2或者一個更高的版本
- 已經設定好
queue_adapter
的Active Job (Sidekiq, Resque, 等)- 一封郵件
任何郵件都應該能夠按照這裡描述的方式正常工作,這裡我們就用一封歡迎郵件來使這個例子更實用:
#app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default from: 'email@example.com'
def welcome_email(user:)
mail(
to: user.email,
subject: "Hi #{user.first_name}, and welcome!"
)
end
end
為了保持程式簡單並有針對性,我們會在每個使用者註冊後傳送給他們一封歡迎郵件。
這和the Rails guides mailer example是一樣的:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
# Yes, Ruby 2.0+ keyword arguments are preferred
UserMailer.welcome_email(user: @user).deliver_later
end
end
The Mailer Should Do Its Job, Eventually
接下來,我們想確保控制器內的任務能如所期待的那樣執行。
在測試指南中,custom assertions for testing jobs inside other components 的章節介紹了大約六種這樣的自定義斷言方法。
或許直覺告訴你應該單刀直入,然後使用assert_enqueued_jobs assert-enqueued-jobs
來測試每次新增新使用者時,我們有否將郵件傳送任務放入佇列。
你可能會這麼做:
# test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
test 'email is enqueued to be delivered later' do
assert_enqueued_jobs 1 do
post :create, {}
end
end
end
然而如果這麼做,你會驚奇地發現測試失敗了,系統會告訴你assert_enqueued_jobs
未經定義,且無法使用。
這是因為,我們的測試類繼承自ActionController::TestCase
,而後者在編寫時沒有包含ActiveJob::TestHelper
。
不過我們很快就可以修正這一點:
# test/test_helper.rb
class ActionController::TestCase
include ActiveJob::TestHelper
end
假定我們的程式碼如期執行,那麼測試應該就能順利通過了。
這是好訊息。現在,我們可以重構我們的程式碼,增加新的功能,也可以增加新的測試。我們可以選擇後者,看看我們的郵件有否投遞成功,如果是的話,那就檢查投遞的內容是否正確。
ActionMailer
能為我們提供一個包含所有發出郵件的佇列,前提是將delivery_method
選項設定為:test
,我們能通過ActionMailer::Base.deliveries
讀取這個佇列。
當同步的地投遞郵件時,檢測郵件是否傳送成功是很容易的。我們只需檢查在動作完成後,投遞計數器加1。用MiniTest
來寫的話,就像下面這樣:
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
post :create, {}
end
我們的測試是實時發生的,但在開篇就已經知道不能阻攔控制器,需要在後臺程式中傳送郵件,現在我們把所有的元件都組裝起來,確定系統是沒有問題的。 因此,在非同步的世界裡,我們必須先執行所有佇列中的任務再去判定執行結果。為了執行pending
中的Active Job
任務,我們使用perform_enqueued_jobs
:
test 'email is delivered with expected content' do
perform_enqueued_jobs do
post :create, {}
delivered_email = ActionMailer::Base.deliveries.last
# assert our email has the expected content, e.g.
assert_includes delivered_email.to, @user.email
end
end
縮短反饋流程
目前為止,我們都在進行功能性測試以確保我們的控制器如期執行。但是,程式碼的變化足以破壞我們傳送的郵件,為什麼不對我們的郵件程式進行單元測試,從而縮短反饋流程,然後更快地洞察變化呢?
Rails測試指南建議在這裡使用fixtures
,但是我覺得他們太生硬了。尤其是一開始,當我們還在嘗試設計郵件時,一個變化很快就會讓他們變得不可用,讓我們的測試無法通過。 我偏向使用assert_match
以關注那些構成郵件主體的關鍵元素。
為此,也因為其他原因(比如抽離處理多部分郵件的邏輯結構),我們可以建立自定義斷言。這可以擴充套件MiniTest
標準斷言或Rails專屬斷言。 這也是建立自己的領域專屬語言(Domain Specific Language)並用於測試的好例子。
讓我們在測試一資料夾內建立一個共享資料夾,用以存放SharedMailerTests
模組。我們自定義的斷言可以這麼來寫:
# /test/shared/shared_mailer_tests.rb
module SharedMailerTests
def assert_email_body_matches(matcher:, email:)
if email.multipart?
%w(text html).each do |part|
assert_match matcher,email.send("#{part}_part").body.to_s
end
else
assert_match matcher, email.body.to_s
end
end
end
接下來,我們需要讓郵件測試系統注意到這個自定義斷言,為此,我們可以將其放入ActionMailer::TestCase
類中。 然後可以借鑑之前把ActiveJob::TestHelper
類包含於ActionController::TestCase
類的方法:
# test/test_helper.rb
require 'shared/shared_mailer_tests'
class ActionMailer::TestCase
include SharedMailerTests
end
注意,我們首先需要在test_helper
中require shared_mailer_tests
。
這些辦好之後,我們現在可以確信我們的郵件中包含我們期望的關鍵元素。假設我們想確保傳送給使用者的URL包含一些用於追蹤的特定UTM引數。 我們現在可以將自定義斷言與老朋友perform_enqueued_jobs
聯合起來使用,就像這樣:
# test/mailers/user_mailer_test.rb
class ToolMailerTest < ActionMailer::TestCase
test 'emailed URL contains expected UTM params' do
UserMailer.welcome_email(user: @user).deliver_later
perform_enqueued_jobs do
refute ActionMailer::Base.deliveries.empty?
delivered_email = ActionMailer::Base.deliveries.last
%W(
utm_campaign=#{@campaign}
utm_content=#{@content}
utm_medium=email
utm_source=mandrill
).each do |utm_param|
assert_email_body_matches utm_param, delivered_email
end
end
end
結論
在Active Job
的基礎上,使用ActionMailer
讓從同步傳送郵件到通過佇列傳送郵件的轉化變得如此簡單,就如同從deliver_now
轉化到deliver_later
。
同時,由於使用Active Job
大大簡化了設定工作基礎環境的流程,你可以對自己所用的佇列系統知之甚少。希望這篇教程能讓你對此過程有更多瞭解。
本文系OneAPM工程師編譯整理。OneAPM是中國基礎軟體領域的新興領軍企業。專注於提供下一代應用效能管理軟體和服務,幫助企業使用者和開發者輕鬆實現:緩慢的程式程式碼和SQL語句的實時抓取。想閱讀更多技術文章,請訪問OneAPM官方技術部落格。
相關文章
- 2020 時代的 Rails 系統測試 (翻譯)AI
- 使用 OfflineIMAP 同步郵件
- rails4.2.6配置傳送郵件AI
- swoole 郵件系統
- 企業郵件系統
- 使用postfix與dovecot服務部署郵件系統
- shell -5 告警系統郵件
- 郵件系統VPN搭建方案
- 簡單郵件系統程式
- 郵件系統之 SPF reject
- 測試平臺系列(88) 完成郵件通知功能(附贈精美郵件模板)
- postfix郵件系統之郵件客戶端無法收郵件問題解析客戶端
- win10系統郵件不能同步進行怎麼解決 win10電腦郵件設定進行同步的方法Win10
- 非同步系統的兩種測試方法非同步
- windows10系統怎麼使用Outlook郵件模板Windows
- [譯] 如何使用 Rails HelperAI
- 測試CMS同步測試CMS同步測試CMS同步
- Laravel 使用 rabbitmq 非同步傳送郵件小案例.LaravelMQ非同步
- 告警系統郵件引擎 執行告警系統
- 為什麼郵件系統不能用來大量傳送郵件
- 測試流程規範--提測規範(釘釘、郵件)
- Laravel 郵件推送系統異常Laravel
- 高校郵件系統配置相關
- Postfix電子郵件系統精要
- 歡迎加入【億能測試快訊】郵件列表!
- SMT首件測試系統可以與MES系統對接嗎?
- 使用 Postfix 從 Fedora 系統中獲取電子郵件
- iOS12系統應用傳送普通郵件構建郵件iOS
- win10系統中自帶郵件如何設定接收網易郵箱郵件Win10
- 郵件系統下一代
- 微軟高管郵件被盜,俄羅斯駭客入侵微軟企業郵件系統微軟
- Laravel 中使用 Beanstalkd 訊息佇列系統傳送郵件LaravelBean佇列
- 對OrcaMDF的系統測試裡避免regressions(譯)
- win10郵件同步163郵箱的方法_win10郵件怎麼繫結163郵箱實現同步Win10
- 使用python傳送郵件和接收郵件Python
- Win10系統禁止Cortana檢測電子郵件資訊的方法Win10
- mailtrap.io - 一種更方便的郵件測試方式AI
- 測試平臺系列(87) 搞個精美的郵件模板