とことんDevOps | 日本仮想化技術が提供するDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けするとことんDevOpsでは、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果など、DevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、アジャイル開発、コンテナ開発など

開催予定の勉強会

各種SNSのフォローもよろしくお願いいたします。

Ruby on RailsでRSpecとSimpleCovを使ってテストでカバレッジ取得までやってみた

Ruby on RailsでRSpecとSimpleCovを使ってテストでカバレッジ取得までやってみた

こんにちは。 Ruby on Railsを触り始めたので、せっかくならTDDな開発で学習を進めてみようと思いテストコード+カバレッジ率取得を試してみました。

雑談ですが、ExcelやGoogleスプレドシートでテスト項目書を作成して手動でテストを実施していることもあると思いますが、なかなか労力が必要な作業でどうにかしたいと思いますね。 さらに当時作成したテスト項目書は使い捨てカイロ状態になりがちで、再び利用しようとしたとしもメンテナンスされておらず信用できないものになっていたりします。 テストコード+カバレッジ率取得ができることでKPI設定などをして自然とPDCAを回して維持していけるいいな。KPI設定やPDCAまわりであれこれはまた別の機会に。

完成した状態をGitHubで公開しておりますので、そちらもあわせて参照ください。 github.com

環境構築

  • MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
  • Docker version 20.10.12, build e91ed57
  • Docker Compose version v2.2.3
  • Ruby 3.1.0
  • Rails 7.0.1

その他細かいパッケージはGitHubにて公開しているコードをご確認ください。

MinitestとRSpecの違い

Ruby on Railsにはデフォルトで利用できるMinitestがありますが、Rubyまわりよく聞くのはRSpecではないでしょうか。どのような違いがあるのか簡単にまとめるとできることは基本的に同じです。ただ、使い込んでいくことを考えると、RSpecの方が何かとメリットが多いように思います。 MinitestとRSpecは独立して使用できるため、得意不得意に応じて使い分けることもできなくはなさそうですね。

Minitestは、JUnitなどの古典的なTDDツールに馴染みがある人は親しみやすく、簡素なテストコード向け。

RSpecはドメイン固有言語でRubyの柔軟性を利用して「より読みやすい」テストを実現でき、豊富なアサーションライブラリやモックライブラリが利用できる。また、ドキュメントも充実している。

RSpecとは

Rubyプログラマー向けのビヘイビア駆動開発(BDD)ツール。BDDは、テスト駆動開発、ドメイン駆動設計、受入テスト駆動設計を組み合わせたソフトウェア開発手法です。特にTDDのドキュメントと設計に焦点を当てて、その部分を実行するものです。

RSpecは、以下の4つのライブラリに分割されて提供されています。本記事では、Rails利用を想定しているためrspec-railsのみを利用します。

  • rspec-core
  • rspec-expectations
  • rspec-mocks
  • spec-rails

rspec.info

手順

rails new xxxxでプロジェクトが作成されており、作成されたプロジェクト直下にいる状態からスタートします。

パッケージをインストール

rspecのパッケージをインストールします。

bundle add rspec-rails \
  --group "development,test"

追加でテスト対象とするサンプルコードで利用するパッケージをインストールします。

bundle add bcrypt

RSpecの初期ファイル生成

rspec generateコマンドを実行して初期ファイルを生成します。 gは、generateの略です。

bundle exec rails g rspec:install

実行結果

$ bundle exec rails g rspec:install
      create  .rspec
      exist  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

ファイルが作成されれば成功です。

テスト対象のサンプルコードを準備

テストコードを書く前にテスト対象とする方のコードを実装していきます。

Modelを作成

generateコマンド利用してmodeを作成しましょう。

bundle exec rails g model user

実行結果

$ rails generate model user
      invoke  active_record
      create    db/migrate/20220207045451_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

spec/models/user_spec.rbを見てみると無事にテストファイルも作成されました。

require 'rails_helper'

RSpec.describe User, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end

マイグレートファイルからデータベースを作成

テストコードを実行するために簡単なサンプルコードを実装していきます。 メールとパスワードくらいの簡単なものを作成してみましょう。

generateコマンドを実行して作成されたapp/db/migrate/20220207045451_create_users.rbに記載してください。 20220207045451の部分は、その時々で変わってしまう値なので適宜読み替えてください。

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :mail, null: false # 姓
      t.string :hashed_password # パスワード

      t.timestamps
    end
  end
end

次のコマンドを実行してDBを作成します。今回はSQLiteで簡易的に行います。

bundle exec rails db:create

実行結果

$ bundle exec rails db:create
reated database 'db/development.sqlite3'
Created database 'db/test.sqlite3'

次にテーブルを作成します。

bundle exec rails db:migrate

実行結果

$ bundle exec rails db:migrate
20220207045451 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0023s
== 20220207045451 CreateUsers: migrated (0.0024s) =============================

Userモデルの実装

ユーザ情報が作成する際にパスワードはそのままの値を保持したくないので、ハッシュ化するという設定にします。 条件分岐があった方がカバレッジなどをみる際には差分を確認しやすいため、値がnilのケースの分岐を追加します。

generateコマンドを実行して作成されたapp/models/user.rbに追記してください。

class User < ApplicationRecord
  def password=(password)
    if password.kind_of?(String)
      self.hashed_password = BCrypt::Password.create(password)
    else
      self.hashed_password = nil
    end
  end
end

テスト

テスト対象のサンプルコードが準備できました。次にテストコードを実装していきましょう。

generateコマンドを実行して作成されたspec/models/user_spec.rbに追記してください。

require 'rails_helper'

RSpec.describe User, type: :model do
  describe "Hash password" do
    example "正常系" do
      member = User.new
      member.password = "baukis"
      expect(member.hashed_password).to be_kind_of(String)
      expect(member.hashed_password.size).to eq(60)
    end

    example "純正常系(nil)" do
      member = User.new(hashed_password: "x")
      member.password = nil
      expect(member.hashed_password).to be_nil
    end
  end
end

記述が終わったら以下のコードで実行してみましょう。

$ bundle exec rspec
$ bundle exec rspec
..

Finished in 0.9962 seconds (files took 3.84 seconds to load)
2 examples, 0 failures

何もエラーが出力されずに上記のような結果になれば成功です。

カバレッジ取得

今回は、SimpleCovを使ってカバレッジ率を取得します。

詳しい情報については、以下のリンクを参照してください。

github.com

必要なパッケージをインストール

bundle add simplecov --group ":test"

rspec:installで作成されたspec/rails_helper.rbの先頭に次の2行を追記しましょう。

require 'simplecov'
SimpleCov.start

# This file is copied to spec/ when you run 'rails generate rspec:install'
・・・

追記が終わったら再びRSpecを実行してみましょう。先ほどと違い1行増えていると思います。

bundle exec rspec

実行結果

$ bundle exec rspec
..

Finished in 0.34343 seconds (files took 3.36 seconds to load)
2 examples, 0 failures

Coverage report generated for RSpec to /app/coverage. 39 / 39 LOC (100.0%) covered.

増えた行は、こちらです。

Coverage report generated for RSpec to /app/coverage. 39 / 39 LOC (100.0%) covered.

CLIで簡易的な出力を確認することもできますが、/coverage配下に詳細なHTMLレポートが出力されているので、そちらを見てみるとどの行を通過したのかなどを詳しくみることができます。

f:id:ismt7:20220208111827p:plain
SimpleCovで出力されたレポート(一覧ページ)

f:id:ismt7:20220208111916p:plain
SimpleCovで出力されたレポート(Userモデルクラスの詳細レポートページ)

無事にGUIでも確認ができたので、これで以上になります。