ハマったのでメモ。
test/unit は assert_xxx に失敗すると適当な例外を投げる。
また Ruby では「あるスレッドで例外が発生し、そのスレッド内で rescue で捕捉されなかった場合、通常はそのスレッドだけがなにも警告なしに終了され」る(参照)。
上の二つをまとめると、「メインスレッド以外のスレッドで assert_xxx を呼び出してさらにテストに失敗したとしても、そのスレッドが終了するだけでテストに失敗したように見えない」、ということになる。実際以下のような明らかにテストに失敗するはずのスクリプトを実行すると
require "test/unit" class TestThread < Test::Unit::TestCase def call_block_in_thread(&block) Thread.start{ block.call } end def test_do_block_in_thread call_block_in_thread { assert_equal(0, 1) } end end
結果は以下のようになる。失敗しているようには見えない。
$ ruby test_th.rb Loaded suite test_th Started . Finished in 0.013102 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
このような時はどう対処すべきなんでしょう。どこかですでに議論されているのかな。検索したけど見つかりませんでした。ご存知でしたら教えて下さい。
ちょっと考えてみました。
「スレッド内で assert_xxx に呼ぶ時にユーザがそれなりの注意を払う」。ちょっと大変かも。上のスクリプトの assert_equal(0, 1) がスレッドの中で実行されるかどうかはユーザには分からない。いちいち call_block_in_thread の実装を知らないといけない。
「assert_xxx が実行されているスレッドがメインスレッドでない場合には warning を出す」。取り敢えずユーザがテストが成功していると勘違いすることがなくなる。
「test/unit 側で対処する」。以下のようなことをあらかじめ test/unit がしてくれると嬉しい。けど、まずい場合がでてくるかも知れない。
require "test/unit" require 'thwait' class TestThread < Test::Unit::TestCase def setup @exs = [] end def teardown ths = Thread.list - [Thread.current] ThreadsWait.all_waits(*ths) @exs.each{|e| raise e } end def call_block_in_thread(&block) Thread.start{ block.call } end def assert_equal2(a, b) if Thread.current == Thread.main assert_equal(a, b) else begin assert_equal(a, b) rescue StandardError => ex @exs << ex end end end def test_do_block_in_thread call_block_in_thread { assert_equal2(0, 1) } end end
このスクリプトを実行すると前と違ってちゃんと失敗しているように見える。
$ ruby test_th.rb Loaded suite test_th Started F Finished in 0.048713 seconds. 1) Failure: test_do_block_in_thread(TestThread) [test_th.rb:27:in `assert_equal2' test_th.rb:36:in `test_do_block_in_thread' test_th.rb:35:in `call' test_th.rb:19:in `call_block_in_thread' test_th.rb:19:in `start' test_th.rb:19:in `call_block_in_thread' test_th.rb:35:in `test_do_block_in_thread']: <0> expected but was <1>. 1 tests, 1 assertions, 1 failures, 0 errors
どうなんでしょうか。
『フロイト先生のウソ』をパラパラ眺めてみたけど、どうなんだろう。例えば「本人の性格は遺伝子がある程度決める」ということを双子を使って立証したという研究が肯定的に紹介されている。で、ここで聞きたいのは「〈性格〉の定義はなんですか。どうやって測定するんですか。」ということ。
〈性格〉なんて定義するまでもなく明らかで確固として存在するものだ、というわけではない。
以下は帯広畜産大学心理学研究室の渡邊氏の文章。
仮説1 われわれは自分や他者に一貫性を持った性格の存在を感じている(性格の認知).
仮説2 人の行動には個人に特有の持続的なパターン(個性)がある(行動上の性格)
(中略)
ところが,われわれの素朴な認識や従来の性格心理学では,そこに3つめの仮説が加わります.
仮説3 行動上の性格は,人の内部にある何らかの実体によって規定される(性格の実体論).
(中略)
この仮説3はわれわれの日常的な性格認知を基盤に持っているのです.したがって,
派生仮説A 性格の認知は,性格の実体や行動上の性格をある程度正確に反映している(正確反映仮説)
そして,
派生仮説B 状況とは独立の内的要因に規定される以上,行動上の性格は状況を越えた一貫性を持つ(性格関連行動の通状況的一貫性).
という仮説がそこから派生します.
(中略)
ミシェルの批判は,この仮説3に対して,派生仮説を実証データから検証するという形で挑戦したものです.まず,ミシェルは自分自身が集めたデータから次の事実を発見しました.
事実1 性格検査は人の実際の行動をほとんど予測できない.
そして自分のデータや他の研究の精査から,その理由を以下のような事実に求めました.
事実2 性格の認知(たとえば性格検査の結果)は,実際の行動上の性格と一致していない.
事実3 行動上の性格は環境・状況の影響を大きく受け,通状況的一貫性を示さない.
事実2と3は先の派生仮説ABを直接反証するもので,ミシェルはここから,それまでの性格心理学の基本になっていた仮説3に疑問を突きつけたのです.このことは大変な論争(一貫性論争)を生み出しました.そして,20年以上の論争の中でも事実1から3を無条件で反証できるような新しい事実は発見されなかったのです.
つまり,われわれが自分の性格認知をもとに信じている「性格を決めるのは内的な何かで,だから性格は状況を越えて一貫している」という仮説3はかなり怪しい,ということです.
そして,行動上の性格,つまり科学的に扱うことができる現実としての性格は内的要因と環境・状況との相互作用(注2)で決定され,内的要因だけから性格を理解することはできないと考えられるようになりました.これが相互作用論です.
ビッグファイブの性格測定といくつかの生理指標,遺伝指標などとの間に相関があることから「ビッグファイブによる性格測定が外的に妥当化される」という主張がある。本当にそうだろうか。ふつう,あることについての測定を外的に妥当化するには,測定対象と明確な関係を持つことが確定している外的基準と,測定値とが相関することが必要である。だとすると,性格測定を妥当化するには,性格と明確な関係を持つことが確定しているなにかと,性格測定の結果とに相関があればよいことになる。ビッグファイブと相関する生理指標が「性格と明確な関係を持つ」ことは証明されていない(ビッグファイブと相関するから性格と関係があるというのはいうまでもなく循環論である)ので,ビッグファイブによる測定値と生理指標との相関は,性格測定としてのビッグファイブを妥当化しない。
実際には,これらのデータからわかるのは,ビッグファイブによる性格測定と,いくつかの生理指標・遺伝指標が「部分的に同じものを測っている」ということだけである。それが「性格の遺伝的基盤」である可能性はもちろんあるが,そうでない可能性もある。
解決した。ユーザが下の例の assert_in_thread みたいなのを用意すれば良い。でも警告は欲しいなあ。気付かずにハマると思う。
require "test/unit" require 'thread.rb' class TestThread < Test::Unit::TestCase def assert_in_thread if Thread.main == Thread.current yield else Thread.exclusive { begin yield rescue StandardError => ex Thread.main.raise(ex) end } end end def call_block_in_thread(&block) Thread.start{ block.call } end def test_do_block_in_thread call_block_in_thread { assert_in_thread do assert_equal(0, 1) end } end end
最近のコメント