51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2301|回复: 0
打印 上一主题 下一主题

[转贴] 使用Rails 4.2+ 测试异步邮件系统

[复制链接]
  • TA的每日心情
    擦汗
    3 天前
  • 签到天数: 1047 天

    连续签到: 5 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2016-1-14 14:20:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

    摘要:在编写需要发送邮件的应用时,控制器是绝不能被阻塞的,因此异步发送必不可少。为了实现这个途径,邮件发送代码必须从request/response周期转移到可以在后台异步处理的进程中去。

    【编者按】异步测试总是一个很大的问题,邮件发送测试更是让很多开发同学不知道从哪里入手。在新版的Rails里,这类测试在很大程度上被简化了。

    关于译者:李哲,OneAPM工程师,拥有7年一线开发经验,曾在大型民航、电力等企业就职,厌倦了国有企业的无聊氛围之后,义无反顾的投进互联网企业的大潮之中。平时喜欢研究各种编程语言,目前在OneAPM负责Ruby探针的研发,研究Ruby语言实现,以及RubyVM底层的技术。

    以下为译文

    在编写需要发送邮件的应用时,控制器是绝不能被阻塞的,因此异步发送必不可少。为了实现这个途径,邮件发送代码必须从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_jobsassert-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大大简化了设定工作基础环境的流程,你可以对自己所用的队列系统知之甚少。希望这篇教程能让你对此过程有更多了解。

    英文原文:Testing async emails, the Rails 4.2+ way


    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-18 06:01 , Processed in 0.068956 second(s), 27 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表