RailsによるアジャイルWebアプリケーション開発のp.151に載っているテストtest_validateがうちの環境ではFailしてしまいました。Rails(ActiveRecord)のテストがトランザクションモードを使うようになっていたのですが、MySQLがトランザクションに対応していないモードで動作していたのが原因でした。以下はその詳細と解決方法です。
うちの環境はこんな感じです。
- OS: NetBSD 3.0
- DB: MySQL 5.0.20
- Ruby: 1.8.4
- Rails: 1.1.2
- Activerecord: 1.14.2
Failしたテストのコードは以下です。(39行目は本には"Should be positive"となっていましたが、モデル側で出しているメッセージは違うものだったのでそれにあわせて変更しました)
26 def test_update
27 assert_equal 29.95, @product.price
28 @product.price = 99.99
29 assert @product.save, @product.errors.full_messages.join("; ")
30 @product.reload
31 assert_equal 99.99, @product.price
32 end
33
34 def test_validate
35 assert_equal 29.95, @product.price
36 @product.price = 0.00
37 assert !@product.save
38 assert_equal 1, @product.errors.count
39 assert_equal "は0より大きくなくてはなりません", @product.errors.on(:price)
40 end
テストを実行すると、以下のようにFailします。
% ruby test/unit/product_test.rb
Loaded suite test/unit/product_test
Started
..F
Finished in 0.943026 seconds.
1) Failure:
test_validate(ProductTest) [test/unit/product_test.rb:35]:
<29.95> expected but was
<99.99>.
3 tests, 11 assertions, 1 failures, 0 errors
http://www.ruby-forum.com/topic/64764にのっていたのですが、MySQLがトランザクションに対応していないモードで動作しているのが原因のようです。
http://www.linuxworld.jp/special/-/20081.htmlによると、MySQLではデータをハンドリングするストレージエンジンと呼ばれる機構をいくつか持っていて、デフォルトではMyISAMと呼ばれるストレージエンジンを使用しています。ところがMyISAMはトランザクションモードに対応していません。
上記テストがFailしないようにするには、2つの方法があります。
- トランザクションを使わないモードでテストを動作させる
- MySQLのストレージエンジンにトランザクションモード対応のものを使用する
それぞれ試してみました。
1. トランザクションを使わないモードでテストを動作させる
test/test_helper.rb を編集して、トランザクションを使わないようにします。
self.use_transactional_fixtures = false
これで、テストでFailしないようになりました。
% ruby test/unit/product_test.rb
Loaded suite test/unit/product_test
Started
...
Finished in 0.795255 seconds.
3 tests, 14 assertions, 0 failures, 0 errors
2. MySQLのストレージエンジンにトランザクションモード対応のものを使用する
MySQLのストレージエンジンとしてトランザクションに対応したInnoDBを使用します。InnoDBはMySQLコンパイル時に組み込まれている必要がありますが、幸い手元のものはInnoDBが使えるようになっていました。
実際にInnoDBを使用するには、
- mysql初期化ファイル(my.cnfなど)でデフォルトをInnoDBに設定する
- テーブル作成時にInnoDBに設定する
方法がありますが、今回は後者を試します。
具体的には、以下のようにテーブル作成時にSQLのcreateの最後にtype=InnoDBを追加します。
create table products (
id int not null auto_increment,
title varchar(100) not null,
description text not null,
image_url varchar(200) not null,
price decimal(10,2) not null,
date_available datetime not null,
primary key (id)
) type=InnoDB;
すべてのテーブル作成時に同様にtype=InnoDBを追加したところ、こちらもFailは起こらないようになりました。
実はあとでtest/test_helper.rbのコメントを読んでみるとこのことが書かれていて、InnoDBが使えるんだったらそうしたほうがお勧めのようです。
2006/05/17追記:
上記の本のにuse_transactional_fixturesの説明がのってました