3
1

ShopifyのRubyスタイルガイド 日本語訳

Last updated at Posted at 2023-12-02

この記事はDeepLの結果を編集したものです。修正点などはお気軽に編集リクエストをしてください。

RubyはShopifyのメイン言語です。私たちは主にRubyを使用しており、おそらく最大規模のショップのひとつです。Rubyは新しいWebプロジェクトやスクリプティングのための言語です。

Shopifyではすべての開発者が少なくともRubyを一通り理解していることを期待しています。素晴らしい言語です。日々どのような仕事をしていても、より良い開発者になれるでしょう。以下に示すのはRubyでの開発中に従うべき緩やかなコーディングスタイルです。

このスタイルガイドはShopifyでの10年以上に渡るRuby開発の成果です。内容の多くはBozhidar BatsovのRubyスタイルガイドに基づいており、多くのコントリビューターによってShopifyに適応されています。

RuboCopで使う

本スタイルガイドをRubyプロジェクトで採用する際には、RuboCopを利用することを推奨します。RuboCopのインストール方法や使い方については、RuboCopの公式ドキュメントを参照してください。

デフォルトのRuboCopの設定を継承することで、このスタイルガイドと同期することができます。これを利用するにはGemfileに以下を追加します:

Gemfile
gem "rubocop-shopify", require: false

そしてプロジェクトのRuboCop設定ファイルの先頭に以下を追加します:

inherit_gem:
  rubocop-shopify: rubocop.yml

指定されたIncludeExcludeの設定はRuboCopのデフォルトにマージされます。

gemからの設定の継承の詳細については、RuboCopのドキュメントを参照してください。

一般

  • メソッドのすべての行が同じ抽象化レベルで動作するようにする。(単一抽象レベルの原則)。
  • 機能的にコーディングする。できる限り変異(副作用)を避ける。
  • 防御的なプログラミングを避ける

過度に防御的なプログラミングは、決して遭遇することのないエラーに対して安全策を講じる可能性があり、その結果としてランタイムとメンテナンスのコストが発生する。

  • 引数の変異を避ける。
  • モンキーパッチは避ける。
  • 長いメソッドは避ける。
  • 長いパラメータリストは避ける。
  • 不必要なメタプログラミングを避ける。
  • privateprotectedの可視性を阻害しないように、sendよりもpublic_sendを優先する。
  • ruby -wで安全なコードを書く。
  • 3レベル以上のブロックネストを避ける。

レイアウト

  • ソースファイルのエンコーディングにはUTF-8を使用する。
  • 2スペースのインデント、タブなし。
  • Unixスタイルの改行コードを使用する。
  • ステートメントや式の区切りに;を使わない。1行に1つの式を使用する。
  • 演算子の周り、カンマ、コロン、セミコロンの後ろ、{の周り、}の前にはスペースを使う。
  • (, [の後、], )の前にはスペースを入れない。
  • !演算子の後にはスペースを入れない。
  • 範囲リテラル内にはスペースを入れない。
  • メソッド呼び出し演算子の周囲はスペースを入れない。
# bad
foo . bar

# good
foo.bar
  • ラムダリテラル内のスペースを避ける。
# bad
a = -> (x, y) { x + y }

# good
a = ->(x, y) { x + y }
  • caseの行と同じ深さまでwhenをインデントする。
  • 条件式の結果を変数に代入する場合は、その分岐を戻り値を受け取る変数に合わせる。
# bad
result =
  if some_cond
    # ...
    # ...
    calc_something
  else
    calc_something_else
  end

# good
result = if some_cond
  # ...
  # ...
  calc_something
else
  calc_something_else
end
  • beginブロックの結果を代入する場合は、rescue/ensure/endを行頭に揃える。
# bad
host = begin
         URI.parse(value).host
       rescue URI::Error
         nil
       end

# good
host = begin
  URI.parse(value).host
rescue URI::Error
  nil
end
  • メソッド定義とメソッドの間には空行を使い、内部的にはメソッドを論理的な段落に分割する。
  • メソッドのパラメータにデフォルト値を代入する場合は、=演算子の周囲に空白文字を使用する。
  • 必要でない場合は\で行を継続することを避ける。
  • メソッド呼び出しのパラメータが複数行にまたがる場合は、メソッド呼び出しのある行の開始位置に対して1レベル字下げして揃える。
# starting point (line is too long)
def send_mail(source)
  Mailer.deliver(to: "bob@example.com", from: "us@example.com", subject: "Important message", body: source.text)
end

# bad (double indent)
def send_mail(source)
  Mailer.deliver(
      to: "bob@example.com",
      from: "us@example.com",
      subject: "Important message",
      body: source.text)
end

# good
def send_mail(source)
  Mailer.deliver(
    to: "bob@example.com",
    from: "us@example.com",
    subject: "Important message",
    body: source.text,
  )
end

複数の行でメソッドを連結する場合は、連続する呼び出しを1レベルずつインデントする。

# bad (indented to the previous call)
User.pluck(:name)
    .sort(&:casecmp)
    .chunk { |n| n[0] }

# good
User
  .pluck(:name)
  .sort(&:casecmp)
  .chunk { |n| n[0] }
  • 複数行にまたがる配列リテラルの要素を揃える。
  • 行は120文字以内にする。
  • 末尾の空白は避ける。
  • アラインメント目的以外の余分な空白は避ける。
  • 各ファイルの最後は改行する。
  • ブロックコメントは避ける。
# bad
=begin
comment line
another comment line
=end

# good
# comment line
# another comment line
  • 括弧が最初の引数とは別の行にある場合は、メソッド呼び出しの括弧を最後の引数の後の行に置く。
# bad
method(
  arg_1,
  arg_2)

# good
method(
  arg_1,
  arg_2,
)
  • メソッド呼び出し、ハッシュ、配列を複数行でラップする場合は、各要素/引数を改行する。
# bad

method(arg_1, arg_2,
  arg_3
)

[
  value_1, value_2,
  value_3,
]

{
  key1: value_1,
  key2: value_2, key3: value_3,
}

# good

method(
  arg_1,
  arg_2,
  arg_3,
)

[
  value_1,
  value_2,
  value_3,
]

{
  key1: value_1,
  key2: value_2,
  key3: value_3,
}

# good (special cases)

# Single argument method call
method({
  foo: bar,
})

# Last argument, itself is multiline
class User
  after_save :method, if: -> {
    do_some_checks
  }
end

# Single value array
errors = [{
  error_code: 1234,
  error_message: "This is an error",
}]
  • マジックコメントとコードやドキュメントは空行で区切る。
# good
# frozen_string_literal: true

# Some documentation for Person
class Person
  # Some code
end

# bad
# frozen_string_literal: true
# Some documentation for Person
class Person
  # Some code
end
  • 属性アクセサーの周囲に空行を使う。
# bad
class Foo
  attr_reader :foo
  def foo
    # do something...
  end
end

# good
class Foo
  attr_reader :foo

  def foo
    # do something...
  end
end
  • メソッド、クラス、モジュール、ブロック本体の周囲で空行は避ける。
# bad
class Foo

  def foo

    begin

      do_something do

        something

      end

    rescue

      something

    end

    true

  end

end

# good
class Foo
  def foo
    begin
      do_something do
        something
      end
    rescue
      something
    end
  end
end

シンタックス

  • 定数(クラスやモジュールも含む)やコンストラクタ(Array()Nokogiri::HTML()など)を参照する場合にのみ::を使用する。通常のメソッド呼び出しには::を避ける。
  • 定数参照は親クラス/モジュール内を検索しないため、クラスやモジュールの定義、継承には::を使うことを避ける。
# bad
module A
  FOO = "test"
end

class A::B
  puts FOO  # this will raise a NameError exception
end

# good
module A
  FOO = "test"

  class B
    puts FOO
  end
end
  • パラメータがある場合はdefに括弧をつける。メソッドがパラメータを受け付けない場合は括弧を省略する。
  • forは避ける。
  • thenは避ける。
  • if/then/else/end構文よりも三項演算子(?:)を使う。
# bad
result = if some_condition then something else something_else end

# good
result = some_condition ? something : something_else
  • 三項演算子では1つの分岐につき1つの式を使用する。これは三項演算子を入れ子にしてはいけないということでもある。このような場合はif/else構文を優先する。
  • 複数行の?:(三項演算子)は避け、代わりにif/unlessを使用する。
  • 1行の場合はwhen x then ...を使う。
  • notの代わりに!を使う。
  • and/orより&&/||を優先する。
  • 否定条件の場合はifよりunlessを優先する。
  • elseを使ったunlessは避ける。ポジティブな条件から書き直す。
  • メソッド呼び出しの引数を括弧で囲む。引数を指定しない場合は括弧を省略する。また、呼び出しが単一行でメソッドが以下の場合は括弧を省略する:
    • 暗黙のレシーバを持つクラスメソッド呼び出し。
    • 糖衣構文による呼び出し(例:1 + 1+メソッドを呼び出し、foo[bar][]メソッドを呼び出すなど)。
# bad
class User
  include(Bar)
  has_many(:posts)
end

# good
class User
  include Bar
  has_many :posts
  SomeClass.some_method(:foo)
end
    • 以下のいずれかのメソッド:
      • require
      • require_relative
      • require_dependency
      • yield
      • raise
      • puts
  • 暗黙のオプションハッシュを囲む中括弧は省略する。

  • 呼び出されたメソッドがブロックの唯一の操作である場合、proc呼び出しの省略記法を使う。

# bad
names.map { |name| name.upcase }

# good
names.map(&:upcase)
  • 1行ブロックの場合はdo...endよりも{...}を優先する。
  • 複数行のブロックでは{...}よりもdo...endを優先する。
  • returnは可能であれば省略する。
  • selfは可能であれば省略する。
# bad
self.my_method

# good
my_method

# also good
attr_writer :name

def my_method
  self.name = "Rafael" # `self` is needed to reference the attribute writer.
end
  • 条件文の中で返り値を使用する場合は代入を括弧で囲む。
if (value = /foo/.match(string))
  • 変数の初期化には、まだ初期化されていない場合にのみ||=を使う。
  • 真偽値の初期化に||=を使うのは避ける。
# bad - would set enabled to true even if it was false
@enabled ||= true

# good
@enabled = true if @enabled.nil?

# also valid - defined? workaround
@enabled = true unless defined?(@enabled)
  • メソッド名と開始括弧の間にスペースを入れることを避ける。
  • lambdaよりもlambdaリテラル構文を優先する。
# bad
l = lambda { |a, b| a + b }
l.call(1, 2)

l = lambda do |a, b|
  tmp = a * 7
  tmp * b / 50
end

# good
l = ->(a, b) { a + b }
l.call(1, 2)

l = ->(a, b) do
  tmp = a * 7
  tmp * b / 50
end
  • Proc.newよりもprocを優先する。
  • 使用しないブロックパラメーターの先頭には_を付ける。_だけでも構わない。
  • 無効なデータを保証できる場合はガード節を優先する。ガード節とは関数の先頭にある条件文のことで、できる限り早く抜け出す。
# bad
def compute_thing(thing)
  if thing[:foo]
    update_with_bar(thing)
    if thing[:foo][:bar]
      partial_compute(thing)
    else
      re_compute(thing)
    end
  end
end

# good
def compute_thing(thing)
  return unless thing[:foo]
  update_with_bar(thing[:foo])
  return re_compute(thing) unless thing[:foo][:bar]
  partial_compute(thing)
end
  • オプションハッシュよりもキーワード引数を優先する。
  • collectよりmapdetectよりfindfind_allよりselectlengthよりsizeを優先する。
  • DateTimeよりもTimeを優先する。
  • "2018-03-20T11:16:39-04:00"のようなISO8601形式の時間文字列を期待する場合、Time.parse(foo)ではなくTime.iso8601(foo)を優先する。
  • 代入コンテキストでbeginブロックから戻るのは避ける。beginブロック内のメソッドからリターンすると、代入が行われなくなり、混乱を招くメモ化バグが発生する可能性がある。
# bad
def foo
  @foo ||= begin
    return 1 if flag?
    2
  end
end

# good
def foo
  @foo ||= begin
    if flag?
      1
    else
      2
    end
  end
end

名前付け

  • シンボル、メソッド、変数にはsnake_caseを使う。
  • クラスとモジュールにはCamelCaseを使うが、HTTP、RFC、XMLのような頭字語は大文字にする。
  • hello_world.rbのようにファイルやディレクトリの名前にはsnake_caseを使う。
  • 1つのソースファイルに1つのクラスまたはモジュールを定義する。ファイル名はクラス名やモジュール名と同じにするが、CamelCasesnake_caseに置き換える。
  • その他の定数にはSCREAMING_SNAKE_CASEを使う。
  • 短いブロックでinjectを使用する場合、注入されるものに応じて引数に名前を付ける。例:|hash, e| (mnemonic: hash, element)
  • 二項演算子を定義する場合、引数の名前をotherにする(<<[]はセマンティクスが異なるので、このルールの例外となる)。
  • 述語メソッドには?を付ける。述語メソッドは真偽値を返すメソッドである。
  • ブール値を返さない場合はメソッド名の最後に?を付けないようにする。
  • メソッド名の先頭にis_を付けない。
# bad
def is_empty?
end

# good
def empty?
end
  • メソッド名をget_で始めるのは避ける。
  • 破壊的変更を伴わない同等のメソッドがない場合、メソッド名の最後をで終わらせないようにする。例えばActiveRecordではsaveはブール値を返すが、save!は失敗時に例外をスローする。
  • マジックナンバーは避ける。定数を使い、意味のある名前をつける。
  • 差別的な由来を持つ(と解釈される)命名法は避ける。

コメント

  • 読者が見逃しているかもしれないので、関連する文脈をコメントに含める。
  • コメントはコードと同期させる。
  • 適切な大文字と句読点を使ってコメントを書く。
  • 余計なコメントは避ける。コードがどのように動作するかではなく、コードがなぜそのようになっているかに焦点を当てる。

クラスとモジュール

  • クラスメソッドだけを持つクラスよりもモジュールを優先する。クラスはそこからインスタンスを生成することに意味がある場合にのみ使うべきである。
  • module_functionよりもextend selfを優先する。
# bad
module SomeModule
  module_function

  def some_method
  end

  def some_other_method
  end
end

# good
module SomeModule
  extend self

  def some_method
  end

  def some_other_method
  end
end
  • クラスメソッドを定義するときはdef selfよりもclass << selfのブロックを使い、1つのブロックにまとめる。
# bad
class SomeClass
  def self.method1
  end

  def method2
  end

  private

  def method3
  end

  def self.method4 # this is actually not private
  end
end

# good
class SomeClass
  class << self
    def method1
    end

    private

    def method4
    end
  end

  def method2
  end

  private

  def method3
  end
end
  • クラス階層を設計するときはリスコフの置換原則を守る。
  • attr_accessorattr_readerattr_writerを使用して、些細なアクセサとミューテーターを定義する。
# bad
class Person
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def first_name
    @first_name
  end

  def last_name
    @last_name
  end
end

# good
class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end
  • attrよりもattr_readerattr_accessorを優先する。
  • クラス変数(@@)は避ける。
  • publicprotectedprivateメソッドを、それらが適用されるメソッド定義と同じようにインデントする。visibility修飾子の上に1行、下に1行空行を置く。
class SomeClass
  def public_method
    # ...
  end

  private

  def private_method
    # ...
  end

  def another_private_method
    # ...
  end
end
  • aliasよりもalias_methodを優先する。

例外

  • raiseメソッドを使って例外を通知する。
  • 引数2つのバージョンのraiseではRuntimeErrorを省略する。
# bad
raise RuntimeError, "message"

# good - signals a RuntimeError by default
raise "message"
  • 例外インスタンスではなく例外クラスとメッセージを2つの別々の引数として与えてraiseすることを優先する。
# bad
raise SomeException.new("message")
# Note that there is no way to do `raise SomeException.new("message"), backtrace`.

# good
raise SomeException, "message"
# Consistent with `raise SomeException, "message", backtrace`.
  • ensureブロックからのリターンは避ける。ensureブロック内のメソッドから明示的にリターンすると、発生した例外よりもリターンが優先され、メソッドは例外がまったく発生しなかったかのように戻る。事実上、例外は静かに捨てられる。
# bad
def foo
  raise
ensure
  return "very bad idea"
end
  • 可能な限り暗黙のbeginブロックを使用する。
# bad
def foo
  begin
    # main logic goes here
  rescue
    # failure handling goes here
  end
end

# good
def foo
  # main logic goes here
rescue
  # failure handling goes here
end
  • 空のrescue構文は避ける。
# bad
begin
  # an exception occurs here
rescue SomeError
  # the rescue clause does absolutely nothing
end

# bad - `rescue nil` swallows all errors, including syntax errors, and
# makes them hard to track down.
do_something rescue nil
  • rescueの修飾形は避ける。
# bad - this catches exceptions of StandardError class and its descendant
# classes.
read_file rescue handle_error($!)

# good - this catches only the exceptions of Errno::ENOENT class and its
# descendant classes.
def foo
  read_file
rescue Errno::ENOENT => error
  handle_error(error)
end
  • Exceptionクラスのレスキューは避ける。
# bad
begin
  # calls to exit and kill signals will be caught (except kill -9)
  exit
rescue Exception
  puts "you didn't really want to exit, right?"
  # exception handling
end

# good
begin
  # a blind rescue rescues from StandardError, not Exception.
rescue => error
  # exception handling
end
  • 新しい例外クラスを導入するよりも標準ライブラリの例外を優先する。
  • 例外変数には意味のある名前を使う。
# bad
begin
  # an exception occurs here
rescue => e
  # exception handling
end

# good
begin
  # an exception occurs here
rescue => error
  # exception handling
end

コレクション

  • コンストラクタにパラメータを渡す必要がない限り、リテラル配列とハッシュ生成記法を使用する。
# bad
arr = Array.new
hash = Hash.new

# good
arr = []
hash = {}
  • %w%iよりもリテラル配列構文を優先する。
# bad
STATES = %w(draft open closed)

# good
STATES = ["draft", "open", "closed"]
  • 複数行のコレクションリテラルの末尾にカンマを追加する。
# bad
{
  foo: :bar,
  baz: :toto
}

# good
{
  foo: :bar,
  baz: :toto,
}
  • 配列の最初か最後の要素にアクセスするときは、[0][-1]よりもfirstlastを優先する。
  • 変更可能なオブジェクトをハッシュのキーにしない。
  • すべてのキーがシンボルである場合は、省略記法のハッシュリテラル構文を使用する。
# bad
{ :a => 1, :b => 2 }

# good
{ a: 1, b: 2 }
  • すべてのキーが記号でない場合は、省略記法よりもハッシュロケット構文を優先する。
# bad
{ a: 1, "b" => 2 }

# good
{ :a => 1, "b" => 2 }
  • Hash#has_key?よりもHash#key?を優先する。
  • Hash#has_value?よりもHash#value?を優先する。
  • 存在すべきハッシュキーを扱う場合はHash#fetchを使う。
heroes = { batman: "Bruce Wayne", superman: "Clark Kent" }
# bad - if we make a mistake we might not spot it right away
heroes[:batman] # => "Bruce Wayne"
heroes[:supermann] # => nil

# good - fetch raises a KeyError making the problem obvious
heroes.fetch(:supermann)
  • カスタムロジックを使用するのではなく、Hash#fetchを使用してハッシュキーのデフォルト値を導入する。
batman = { name: "Bruce Wayne", is_evil: false }

# bad - if we just use || operator with falsy value we won't get the expected result
batman[:is_evil] || true # => true

# good - fetch work correctly with falsy values
batman.fetch(:is_evil, true) # => false
  • 中括弧が最初の要素とは別の行にある場合は、最後の要素の後の行に ] と } を置く。
# bad
[
  1,
  2]

{
  a: 1,
  b: 2}

# good
[
  1,
  2,
]

{
  a: 1,
  b: 2,
}

文字列

  • 文字列連結の代わりに文字列補間と文字列書式を優先する:
# bad
email_with_name = user.name + " <" + user.email + ">"

# good
email_with_name = "#{user.name} <#{user.email}>"

# good
email_with_name = format("%s <%s>", user.name, user.email)
  • 補間式では括弧内のパディングスペースは避ける。
# bad
"From: #{ user.first_name }, #{ user.last_name }"

# good
"From: #{user.first_name}, #{user.last_name}"
  • 文字列は二重引用符で囲む。
# bad
'Just some text'
'No special chars or interpolation'

# good
"Just some text"
"No special chars or interpolation"
"Every string in #{project} uses double_quotes"
  • 文字リテラル構文?xは避ける。
  • 文字列に補間されるインスタンス変数やグローバル変数の周囲には{}を使用する。
class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  # bad - valid, but awkward
  def to_s
    "#@first_name #@last_name"
  end

  # good
  def to_s
    "#{@first_name} #{@last_name}"
  end
end

$global = 0
# bad
puts "$global = #$global"

# fine, but don't use globals
puts "$global = #{$global}"
  • 補間されたオブジェクトのObject#to_sを避ける。
# bad
message = "This is the #{result.to_s}."

# good - `result.to_s` is called implicitly.
message = "This is the #{result}."
  • より高速で専門的な代替手段を使うことができる場面ではString#gsubを避ける。
url = "http://example.com"
str = "lisp-case-rules"

# bad
url.gsub("http://", "https://")
str.gsub("-", "_")
str.gsub(/[aeiou]/, "")

# good
url.sub("http://", "https://")
str.tr("-", "_")
str.delete("aeiou")
  • 複数行の文字列にheredocsを使用する場合、先頭の空白を保持するという事実に注意する。余分な空白をカットするために、ある程度のマージンを取ることは良い習慣となる。
code = <<-END.gsub(/^\s+\|/, "")
  |def test
  |  some_method
  |  other_method
  |end
END
# => "def test\n  some_method\n  other_method\nend\n"

# In Rails you can use `#strip_heredoc` to achieve the same result
code = <<-END.strip_heredoc
  def test
    some_method
    other_method
  end
END
# => "def test\n  some_method\n  other_method\nend\n"
  • Ruby 2.3ではRailsのstrip_heredocと同じセマンティクスを持つ"squiggly heredoc"構文を優先する:
code = <<~END
  def test
    some_method
    other_method
  end
END
# => "def test\n  some_method\n  other_method\nend\n"
  • heredocのコンテンツとクロージングを、その開始に従ってインデントする。
# bad
class Foo
  def bar
    <<~SQL
      'Hi'
  SQL
  end
end

# good
class Foo
  def bar
    <<~SQL
      'Hi'
    SQL
  end
end

# bad

# heredoc contents is before closing heredoc.
foo arg,
    <<~EOS
  Hi
    EOS

# good
foo arg,
    <<~EOS
  Hi
EOS

# good
foo arg,
  <<~EOS
    Hi
  EOS

正規表現

  • 文字列の正規表現よりもプレーンテキスト検索を優先する。
string["text"]
  • キャプチャした結果を使用しない場合は、キャプチャしないグループを使用する。
# bad
/(first|second)/

# good
/(?:first|second)/
  • Perl-legacy変数よりもRegexp#matchを優先してグループマッチを捕捉する。
# bad
/(regexp)/ =~ string
process $1

# good
/(regexp)/.match(string)[1]
  • 番号付きグループよりも名前付きグループを優先する。
# bad
/(regexp)/ =~ string
...
process Regexp.last_match(1)

# good
/(?<meaningful_var>regexp)/ =~ string
...
process meaningful_var
  • 文字列の先頭から末尾までをマッチングする場合、^$よりも\A\zを優先する。
string = "some injection\nusername"
string[/^username$/] # `^` and `$` matches start and end of lines.
string[/\Ausername\z/] # `\A` and `\z` matches start and end of strings.

パーセントリテラル

  • 補間と埋め込みダブルクォートの両方が必要な1行文字列には%()を使用する。複数行の文字列にはheredocsを使う。
  • '"の両方を含む文字列でない限り%qは避ける。正規文字列リテラルの方が読みやすいので、多くの文字をエスケープする必要がない限り、そちらを使うべきである。
  • %rは少なくとも1つの/文字にマッチする正規表現にのみ使う。
# bad
%r{\s+}

# good
%r{^/(.*)$}
%r{^/blog/2011/(.*)$}
  • %sの使用は避ける。空白を含む記号を作成するには:"some string"を使用する。
  • 正規表現でよくあるように、括弧がリテラル内に現れる場合を除き、すべての%リテラルの区切り文字として()を使用する。(){}[]<>のうち、リテラル内に現れない最初のものを使用する。

テスト

  • テストコードも他のコードと同じように扱うこと。つまり、可読性、保守性、複雑さなどを念頭に置く。
  • テストフレームワークとしてMinitestを使用する。
  • 各テストケースはコードのひとつの側面をカバーするものに限定する。
  • テストケースのセットアップ、アクション、アサーションの各セクションを空行で区切った段落にまとめる。
test "sending a password reset email clears the password hash and set a reset token" do
  user = User.create!(email: "bob@example.com")
  user.mark_as_verified

  user.send_password_reset_email

  assert_nil user.password_hash
  refute_nil user.reset_token
end
  • 複雑なテストケースを機能を分離してテストする複数の単純なテストに分割する。
  • テストケースを定義するのにdef test_fooよりもtest "foo"スタイルの構文を優先する。
  • より説明的なエラーメッセージを出力するアサーションメソッドを優先する。
# bad
assert user.valid?
assert user.name == "tobi"


# good
assert_predicate user, :valid?
assert_equal "tobi", user.name
  • assert_nothing_raisedの使用は避ける。代わりに肯定的なアサーションを使う。
  • 期待よりもアサーションを優先する。期待は特にシングルトンオブジェクトとの組み合わせにおいて、より脆いテストにつながる。
# bad
StatsD.expects(:increment).with("metric")
do_something

# good
assert_statsd_increment("metric") do
  do_something
end
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1