Rails模式的实际应用--第一部分:Model(下)

2017-02-23   出处: 8th light  作/译者:Christoph Gockel / 婷婷

 

  接上篇:Rails模式的实际应用--第一部分:Model(上)

  https://www.testwo.com/article/892

选择

  框架特征的易用性和源于紧密耦合的可维护性之间的紧张关系,对数据的持久性和数据验证有很大的影响。想要去找一个解决方案,我们可以先问问自己,当两件事情关联过于紧密的时候我们会怎么做:分开他们。

  如果我们有一件事情涉及保存一个movie,另一件事情涉及验证一个movie,我们通常会创建更好的类的去解绑关联。我们最终会得到两个新的类,他们更符合单一责任原则。感谢随之而来的一个副作用,我们得到的好处是更好的可测试性。任何不是特定的要关联到某个指定movie的测试,我们都可以创建一个新的movie去适应它。

代码示例

  使用之前movie的例子,我们想要做的无非是把所有关于验证一个movie的数据的代码单独封装成一个新的类。

  让我们以我们的新类MovieValidator来开始我们的第一个测试。

RSpec.describe MovieValidator do

  let(:valid_movie) {Movie.new(name: "the name", release_date: DateTime.parse("08 June 1984")}

  it "validates a movie" do

    expect(MovieValidator.valid?(valid_movie)).to be_truthy

  Endend

  MovieValidator有一个叫做valid的方法,根据给定的movie是否是有效的返回true或者false。

  为了让测试通过,我们的办法如下:

 class MovieValidator

  def self.valid?(movie)

    has_title?(movie)

  end

  private

  def self.has_title?(movie)

    movie.title.present?

  endend

  但是如果movie是无效的会发生什么呢?我们需要一个可以向MovieValidator的服务器返回验证消息的办法。随后,这些信息会被展示给终端用户,例如。

  让我们用可以区分这些信息的测试来捕捉这些信息。

RSpec.describe MovieValidator do

  let(:valid_movie)   {Movie.new(name: "the name", release_date: DateTime.parse("08 June 1984")}

  let(:invalid_movie) {Movie.new}

  def validation_of(movie)

    MovieValidator.validate(movie)

  end

  it "validates a movie" do

    expect(validation_of(valid_movie)).to be_okay

  end

  it "does not contain any messages" do

    expect(validation_of(valid_movie).messages).to be_empty

  end

  it "does not validate an invalid movie" do

    expect(validation_of(invalid_movie)).not_to be_okay

  end

  it "has messages for invalid movies" do

    expect(validation_of(invalid_movie).messages).to contain("Name cannot be empty.")

  endend

  为了让测试通过,我们来介绍一下验证结果的概念。

class MovieValidator

  def self.validate(movie)

    new(movie).validate

  end

  def initialize(movie)

    @movie = movie

  end

  def validate

    result = Result.new

    result << validate_title

    result

  end

  private

  attr_reader :movie

  def validate_title

    "Title can't be empty." if title_empty?

  end

  def title_empty?

    movie.title.blank?

  end

  class Result

    attr_reader :messages

    def initialize

      @messages = []

    end

    def okay?

      messages.empty?

    end

    def <<(message)

      @messages << message if message

    end

  endend

  当一个要求movie的有效性的需求提出来的时候,我们可以用Movievalidator来独立的实现而不影响其他的所有用户。测试的最终版本和产出的代码可以再GitHub上找到。

  相对于我们自己进行验证,另一个方案是引入ActiveModel:Validations。取决于手上的用例,这是一个可选择的方法,我们可以充分利用已有的实现几乎不用做任何改动。

class MovieValidator

  include ActiveModel::Validations

  validates_presence_of :title

  validate              :release_year_not_before_1895

  def initialize(movie)

    @title        = movie.title

    @release_date = movie.release_date

  end

  private

  attr_reader :title, :release_date

  def release_year_not_before_1895

    if release_date.nil?

      errors.add(:release_date, "must be provided.")

    elsif release_date.year < 1895

      errors.add(:release_date, "is older than the world's first motion picture.")

    end

  endend

提取验证之后,Movie类变成了什么样子呢?

class Movie < ActiveRecord::Base

end

有一句真理是,最好的代码就是没有代码。我们设法让我们的模型摆脱所有自定义的逻辑。我们的movie只是通向持久性的大门,而不是其他。

我们保留了哪些ActiveRecord的测试呢?

  理想情况下没有,但容我解释一下。

  完全不用测试是不可能的。最后我们还是需要确认一个Movie在一些时间点被保存了。但是我们不需要去合适ActiveRecord如预期一般执行。那是一个我们期望能够正常运转的依赖。

  我们真正需要验证的是,我们代码中与ActiveRecord相关的部分做的事情是正确的。这个当前在测试和MovieController的应用里是指定的。

  在即将到来的这个系列的其他博文里面,我们会继续探索关于当前做的是否正确的更细节,以及是否还有更好的选择。


【英文原文:https://8thlight.com/blog/christoph-gockel/2016/10/19/getting-rails-on-track-part-1-models.html

{测试窝原创译文,译者:婷婷}

译者简介:婷婷,一个萌妹子~



声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
273° /2730 人阅读/0 条评论 发表评论

登录 后发表评论