P学習帳

書いておぼえるブログ

【Ruby】MiniTestでテストを書いてみる

目的

MiniTestをとりあえず使ってみること。
Effective Ruby 第6章「テスティング」の内容そのままを試してみた結果をまとめる。

テストの対象

たとえばバージョンを管理するクラスversion.rbがある。このクラスのメソッドをテストしたい。

version.rb

class Version
  attr_reader (:major, :minor, :patch)

  def initialize(version)
    @major, @minor, @pathch = version.split('.').map(:map.to_i)
  end
end

使用例:

require File.dirname(__FILE__) + "/Version"

def major_number
  v = Version.new('2.1.3') # major: 2, minor: 1, patch: 3
  v.major # => 2
end

以下ではこのクラスのmajorの値をテストするテストケースを書く。

 手順

1 テストスクリプト命名

version.rbのテストなのでversion_test.rbという命名しておく。 この通りでなくてもよいて、命名が一貫していればOKだ。

2 必要なライブラリを読み込む

MiniTestを実行するために必要となる。

ライブラリ全体を読み込むには以下の1行を書く。

require 'minitest/autorun'

3 テスト用クラスをつくる

テスト本体のクラスをVersionTest、MiniTestのクラスをMiniTest::Unit::TestCaseとしてクラスを作る。

class VersionTest < MiniTest::Unit::TestCase
  # テストケース
end

4 テストを書く

version_test.rbにテストを書いてく。

require('minitest/autorun')        # MiniTestのライブラリを読み込む
require File.dirname(__FILE__) + '/Version' # version.rbを読み込む

class VersionTest < MiniTest::Unit::TestCase
  # テストケース
  def test_major_number
    v = Version.new('2.1.3')
    assert_equal(2, v.major) 
end

5 実行する

以上でテストが完成した。テスト対象のスクリプトとテストスクリプトは同じディレクトリに置かれている。

$ ls
version.rb      version_test.rb

いよいよ実行する。

$ ruby version_test.rb

.
.
.

成功例: 成功した場合とくに何も言われない。0 failuresというところでエラーがなかったことがわかるようになっている。

$ ruby version_test.rb
MiniTest::Unit::TestCase is now Minitest::Test. From version_test.rb:4:in `<main>'
Run options: --seed 48129

# Running:

.

Finished in 0.000882s, 1133.7868 runs/s, 1133.7868 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

失敗例: わざと失敗するようにテストを以下のように書き換えてみる。

assert_equal(1, v.major) 
MiniTest::Unit::TestCase is now Minitest::Test. From version_test.rb:4:in `<main>'
Run options: --seed 52817

# Running:

F

Failure:
VersionTest#test_major_number [version_test.rb:8]:
Expected: 1
  Actual: 2


bin/rails test version_test.rb:6



Finished in 0.001389s, 719.9424 runs/s, 719.9424 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

assert_equalメソッドの第一引数がExpectedv.majorの値がActualで、両者が異なってますよ、ということが表示される。

感想

テストは大切。いちどテストを書いてしまえば、テスト対象が多くてもOK。それに対して、手動でプリントデバッグするのは消耗する。また、変更したあとの再テストがかんたんにできるのも魅力。

ハッシュレファレンスをsprintfでフォーマットしてprintする

課題

こういうハッシュレフを

my $my_info = {
  name => 'Masato',
  tall => 169,
  mass => 55,
  food => 'Sushi',
};

こうやってプリントしたい。

Name         Tall    Mass    Age     Food
Masato       169     55      33      Sushi

やり方

sprintf関数とハッシュスライスを使う。

sprintf関数の使い方

説明は以下のページがわかりやすい。

sprintf関数 - 文字列の書式指定 - Perlゼミ(サンプルコードPerl入門)

まず、ヘッダーをsprintfで出すにはこうすればできる。

  my $format = "%-12s %-7s %-7s %-7s %-7s";
  printf $format, qw(Name Tall Mass Age Food);

$formatはsprintf関数の第一引数に渡す書式だ。%-12sなどというのは左詰のxx固定長と解釈される。よってこの例は左詰で12文字の固定長になる。長さは実際にプリントする文字列に合わせる。

ハッシュスライス  

  printf $format, @$hash_ref{ qw(name tall mass age food) };

javascriptで変数が定義済みかどうかをチェックする

ポイントは、JSでは0が偽になる点です。0は偽ではありますが、未定義ではありません。

if (height) { ... } の条件では0が未定義と判定されてしまいます。0の場合にTrueのブロックに入るようheight == 0を追加しましょう。

var height = 0;

if (heigth || height == 0) {

  console.log('Variable is defined.');

} else {

  console.log('Variable has NOT been defined.');

}

【Rails】空配列がTrue判定になってはまった件

状況

ActiveRecordでクエリを出して戻り値を変数にいれる。その変数が空かどうかで条件分岐させたい。

解法

books = Book.where(title: 'Great novel')
if books.present?
  # booksの中身がある場合に実行する
end

まちがい

books = Book.where(title: 'Great novel') # 該当レコードがない場合 [] (空配列)が返る
if books # 空配列はTrueになる
  # あれ?
end

考察

Rubyではnilfalse以外がすべてTrueになる。つまり、''[]はTrueだ。空配列がfalseになる言語のノリで書いているとハマる。

【SQL】selectで * を使うとおそい

状況

Active Recordのselectで*でテーブルのすべてのカラムを選択するようにしたらどことなく遅くなった。

改善  

*で全部のカラムを返すのではなくて、実際に使う値のカラムを指定するようにした。

前:

Book.select('books.*')

あと:

Book.select('books.title, books.price, books.author, books.isbn')

テーブルのカラムが大量にあればあるほど、明示的に指定して絞れた場合のスピードアップは大きい。

感想  

業務でうえのようにカラムを指定したら2〜3倍クエリが速くなった。かんたんに速度の向上が実現できてよい。

【Active Record】手動でmigrateを部分的にやり直す

状況

development環境でまちがえてテーブルを消してしまった。元に戻したい、など。

解決

消したテーブルをcreate、カラム追加、データ型変更などしたすべてのmigrationファイルのバージョンを、schema_migrationsテーブルから削除する。

そのあとでbundle exec rake db:migrateする。

schema.rb は大丈夫?

上記の方法で手動でmigrationをふたたび実行したあとのschema.rbをみたところ、壊れていなかった。同じテーブルのスキーマが重複して作成されることはないようだ。

【Active Record】findとwhereで返り値が異なる

状況

whereで取得したレコードセットにメソッドをはやしたらエラーになる。

解決

配列の添字を指定してメソッドを実行する。

コード

Book.where("author LIKE '%#{name}%'").update(author: new_name)

考察

findでIDを指定した場合に返ってくるのはただの変数。whereで条件指定すると配列で返ってくる。 だから、

Book.where(author_id: id).update(price: price)

は失敗する。

ではなくて、こうしなければならない。

Book.where(author_id: id)[0].update(price: price)

ただし、

Book.where(author_id: id).each do |book|
  book.update(price: price)
end

はとおる。

whereの返り値が配列だということを忘れてはならない。