使用 Rails 4.2+ 测试异步邮件系统 - V2EX
OneAPM

使用 Rails 4.2+ 测试异步邮件系统

  •  6
     
  •   OneAPM Jun 1, 2015 3792 views
    This topic created in 4003 days ago, the information mentioned may be changed or developed.

    Alt text
    众所周知,假设想写一个需要发送邮件的应用,我们绝不能阻拦控制器,因此异步传送才是解决之道。为了达到这个目的,我们需要通过能在后台处理任务的异步处理进程库,将邮件发送代码从最初的request/response循环中移出。

    然而,做出这样的改变之后,我们如何确保代码能够一如往常的运行呢?在这篇博文中,我们会探索一种新方法来进行测试,就是我们将要使用的MiniTest测试框架(因为这也是Rails的选择),but the concepts preseted here can be easily translated to RSpec。

    现在有一个好消息,那就是从Rails 4.2开始,异步传送邮件已经比之前简单多了。我们在例子中使用Sidekiq作为队列系统。但由于ActionMailer#deliver_later建立在ActiveJob之上,the interface is clean and agnostic of the asynchronous processing library used。这表示,要不是我刚才提了一下,身为开发者或用户的你也不会知情。其实,建立队列系统是一个独立的话题,你可以在开始使用Active Job中读到更多的信息。

    别太依赖小组件

    在例子中,我们假定Sidekiq及其依赖组件配置正确,因此本场景特有的一段代码是声明Active Job该使用哪一个队列调节器。

    # config/application.rb module OurApp class Application < Rails::Application … config.active_job.queue_adapter = :sidekiq end end 

    Active Job在隐藏实质性的队列配置细节方面功能非常强大,以至于若是使用ResqueDelayed Job或其他组件,代码也不需要太大的改动。因此,如果我们转而使用Sucker Punch,唯一的改变就是在引用相应的依赖包后,将队列调节器从from :sidekiq改为 :sucker_punch

    站在Active Job的肩膀上

    如果你是Rails 4.2新手,或者对Active Job不太了解,Ben Lewis的Active Job介绍是就是很好的入门读物。然而,这篇文章留给我的一个小期许是,找到一种简洁、地道的测试方法,从而让所有组件都能正常的运行。

    根据本文的目标,我们假定你已经部署了:

    • Rails 4.2或者一个更高的版本
    • Active Job set up to use a queueing backend (e.g. Sidekiq, Resque, etc.)
    • 一个邮件程序

    Any Mailer should work with the concepts described here,but we'll use this welcome email to make keep our examples pragmatic:

    #app/mailers/user_mailer.rb class UserMailer < ActionMailer::Base default from: '[email protected]' def welcome_email(user:) mail( to: user.email, subject: "Hi #{user.first_name}, and welcome!" ) end end 

    为了保持程序简单并有针对性,我们会为每个注册的用户发送一封欢迎邮件。

    这与Rails指南中的邮件系统案例如出一辙:

    # 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 

    虽然我们的测试是实时发生的,但在开篇就已经确定绝不阻拦控制器,邮件发送以后台任务进行,我们现在需要部署所有组件以确保系统是确定的。因此,在异步的世界里,我们必须先执行所有队列中的任务才能评定他们的结果。为了执行等待中的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测试指南建议在此阶段使用固定部署,但是我觉得他们太过脆弱。尤其是一开始,当我们还在试验设计或邮件内容,一个变化很快就会让他们变得过时,让我们的测试无法通过。

    我个人转而偏向使用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中请求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官方技术博客

    3 replies    2015-06-01 14:36:08 +08:00
    zhyu
        1
    zhyu  
       Jun 1, 2015/span>
    全文转载不好吧
    另外个人意见,译文还是在标题写清楚比较好,而且还是中英混杂,没译完么。。
    aksoft
        2
    aksoft  
       Jun 1, 2015
    看着有点费劲...
    OneAPM
        3
    OneAPM  
    OP
       Jun 1, 2015
    @zhyu 感谢您的建议,我们下次会在标题里加上译文的注明。
    @aksoft 欢迎提出改进的意见 :)
    About     Help     Advertise     Blog     API     FAQ     Solana     1154 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 48ms UTC 23:42 PVG 07:42 LAX 16:42 JFK 19:42
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86