如何让测试变得有趣和容易

发表于:2017-9-15 08:28

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Maciek Głowacki    来源:51Testing软件测试网采编

分享:
 让我们掷重炮
  在深入到stubbing API调用之前,还有一件事应该看看。假设除了在Twitter和Facebook上发布消息之外,应用程序还发送了一封电子邮件(不知道是发给谁,可能是CIA)。这听起来像是在验收测试中应该处理的事情。
  假设要检查在创建新post之后是否发送通知电子邮件,我建议这样做:
 require 'acceptance_helper'
  resource 'Posts' do
    explanation 'Posts are entities holding some text information.
                 They can be created and seen by anyone'
    post '/posts' do
      # ... params and stuff ...
  let(:title) { 'Foo' }
  let(:body) { 'Lorem ipsum dolor sit amet' }
  subject do
    ApiRequest.test.success(201)
              .response(:id, title: title, body: body)
              .email.to('notify@cia.gov').with(subject: 'New Post published', body: body)
              .and do
      # ... Same as before ...
    end
  end
  include_examples 'api_requests',
                   'Creating a post',
                   'You can create a post by sending its body text and an optional title'
  end 
  end
  如果.to 和 .with不属于ApiRequest本身,.email应该创建其他类的对象,这与正在处理的请求绑定在一起。可以把它看作是一种ApiRequest的方法,来描述Mail。听起来合理吗?来看看代码:
  class ApiRequest
    attr_reader :status,
                :response_keys,
                :response_spec,
                :specs,
                :messages
    def initialize
      @response_keys = []
      @response_spec = {}
      @specs = proc {}
      @messages = []
    end
    def email
      @messages
  收尾工作
  用于生成文档的gem允许在单个示例中生成一些请求,但是我们的实现仅限于其中一个。为了解决这个问题,可以接受一个api请求数组,而不是一个实例。为了保持与现有代码的兼容性,将把主题包装在数组中(如果已经是数组,它将不会做任何事情):
  shared_examples 'api_requests' do |name, explanation|
    header 'Accept', 'application/json'
    header 'Content-Type', 'application/json'
    example name do
      explanation explanation
  Array.wrap(subject).each do |request|
    ActionMailer::Base.deliveries = []
    # ... previous test stuff goes here ...
    # ... just remember to use request instead of subject ...
    request.stubs.each do |stub|
      expect(stub.data).to have_been_requested.at_least_once
      WebMock::StubRegistry.instance.remove_request_stub(stub.data)
    end
  end
  end 
  end
  可以在一个例子中执行很多请求,但是它还不能很好地使用。所以必须添加一种方法来轻松地覆盖一些参数。让我们为ApiRequest类添加最后一个方法:
  class ApiRequest
    attr_reader :status,
                :response_keys,
                :response_spec,
                :specs,
                :messages,
                :stubs,
                :params
    def initialize
      @response_keys = []
      @response_spec = {}
      @specs = proc {}
      @messages = []
      @stubs = []
      @params = {}
    end
    def with(params)
      @params = params
      self
    end
    # ... The rest stays the same ...
  end
  shared_examples 'api_requests' do |name, explanation|
    header 'Accept', 'application/json'
    header 'Content-Type', 'application/json'
    example name do
      explanation explanation
  Array.wrap(subject).each do |request|
    # ... Other stuff happening here ...
    do_request(request.params)
    # ... And here ...
  end
  end 
  end
  有了这些,现在可以在每个示例中执行多个请求:
 require 'acceptance_helper'
  resource 'Posts' do
    explanation 'Posts are entities holding some text information.
                 They can be created and seen by anyone'
    post '/posts' do
      # ... same as before ...
  let(:title) { 'Foo' }
  let(:body) { 'Lorem ipsum dolor sit amet' }
  subject do
    requests = []
    requests << # ... previous "success" subject here
    requests << ApiRequest.test.failure
                          .with(post: { body: 'Too short' })
                          .response(body: ['002'])
                          .and do
      expect(Post.count).to eq(1)
    end
    requests
  end
  include_examples 'api_requests',
                   'Creating a post',
                   'You can create a post by sending its body text and an optional title'
  end
  end
  最好的方法是,在文档中立即有它们(注意前面的长图)
  终于完成了。刚刚创建了一个很容易使用的DSL,它让我们能够改变这个长期且容易出错的测试:
  require 'acceptance_helper'
  resource 'Posts' do
    explanation 'Posts are entities holding some text information.
                 They can be created and seen by anyone'
    post '/posts' do
      with_options scope: :post do
        parameter :title, 'Title of a post. Can be empty'
        parameter :body, 'Main text of a post. Must be longer than 10 letters', required: true
      end
  response_field :id, 'Id of the created post'
  response_field :title, 'Title of the created post'
  response_field :body, 'Main text of the created post'
  header 'Accept', 'application/json'
  header 'Content-Type', 'application/json'
  let(:title) { 'Foo' }
  let(:body) { 'Lorem ipsum dolor sit amet' }
  before do
    @twitter_request = stub_request(:post, 'https://api.twitter.com/1.1/statuses/update.json')
                         .with(body: { status: body })
                         .to_return(status: 200, body: { id: 1 }.to_json)
    @facebook_request = stub_request(:post, 'https://graph.facebook.com/me/feed')
                          .with(body: hash_including(:access_token,
                                                     :appsecret_proof,
                                                     message: body))
                          .to_return(status: 200, body: { id: 1 }.to_json)
  end
  example 'Creating a post' do
    explanation 'You can create a post by sending its body text and an optional title'
    do_request
    expect(status).to eq 201
    response = JSON.parse(response_body)
    expect(response.keys).to include 'id'
    expect(response['title']).to eq title
    expect(response['body']).to eq body
    expect(Post.count).to eq(1)
    post = Post.last
    expect(post.title).to eq(title)
    expect(post.body).to eq(body)
    expect(ActionMailer::Base.deliveries.count).to eq(1)
    email = ActionMailer::Base.deliveries.last
    expect(email.to).to include 'notify@cia.gov'
    expect(email.subject).to include 'New Post published'
    expect(email.body).to include body
    expect(@twitter_request).to have_been_requested
    expect(@facebook_request).to have_been_requested
  end
  end 
  end
  更易读和更容易使用的形式:
  require 'acceptance_helper'
  resource 'Posts' do
    explanation 'Posts are entities holding some text information.
                 They can be created and seen by anyone'
    post '/posts' do
      with_options scope: :post do
        parameter :title, 'Title of a post. Can be empty'
        parameter :body, 'Main text of a post. Must be longer than 10 letters', required: true
      end
  response_field :id, 'Id of the created post'
  response_field :title, 'Title of the created post'
  response_field :body, 'Main text of the created post'
  let(:title) { 'Foo' }
  let(:body) { 'Lorem ipsum dolor sit amet' }
  subject do
    ApiRequest.test.success(201)
              .response(:id, title: title, body: body)
              .email.to('notify@cia.gov').with(
                subject: 'New Post published',
                body: body)
              .request.twitter.with(status: body).status_update.success
              .request.facebook.with(message: body).put_wall_post.success
              .and do
      expect(Post.count).to eq(1)
      post = Post.last
      expect(post.title).to eq(title)
      expect(post.body).to eq(body)
    end
  end
  include_examples 'api_requests',
                   'Creating a post',
                   'You can create a post by sending its body text and an optional title'
  end 
  end
  现在使用ApiRequest比手工编写测试更快,这样就可以很容易地说服团队的其他人使用它来进行验收测试。因此,现在可以得到值得信任的测试,以及API文档。目标实现了!
  最后的话
  在ApiRequest类的帮助下,可以在几分钟内编写新的端点测试,可以很容易地指定业务需求,因此进一步的开发也变得更容易。但请记住,这些只是验收测试。你仍然应该对代码进行单元测试,以捕获任何实现的错误。
  为了向你展示如何在实际应用程序中使用这个方法,我已经准备了一个GitHub存储库,它具有一个完整的非常基本的用例。自己试一下:https://github.com/Bombasarkadian/testifier
  就这篇文章而言:让测试易于编写,同时保持高水平的可用性,当然这里还有很多事情可以做。例如,可以部分地生成基于stub的端点描述。或者,可以想出一种方法,提取一些共同的逻辑,然后进行分享。
22/2<12
精选软件测试好文,快来阅读吧~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号