岩本隆史の日記帳(アーカイブ)

はてなダイアリーのサービス終了をうけて移行したものです。更新はしません。

HUnitで日本語を出力してみる→成功

こんなテストケース(test.hs)があるとします。

module Main where

import Test.HUnit

test1 = TestCase (assertEqual "アサーションのラベル" "期待値" "実際の値")
tests = TestList [TestLabel "テストのラベル" test1]

これを走らせると:

Prelude> :load test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> runTestTT tests
Loading package HUnit-1.2.2.1 ... linking ... done.
### Failure in: 0:"\12486\12473\12488\12398\12521\12505\12523"
アサーションのラベル
expected: "\26399\24453\20516"
 but got: "\23455\38555\12398\20516"
Cases: 1  Tried: 1  Errors: 0  Failures: 1
Counts {cases = 1, tried = 1, errors = 0, failures = 1}

のように表示されてしまいます。「テストのラベル」「期待値」「実際の値」がエスケープされていて、意図通りなのは「アサーションのラベル」だけです。

意図通り表示したいのですが、どうしたものでしょうか。

エスケープの原因

まず、エスケープの原因を探る必要があります。

調べると、下記のリソースが見つかりました。

putStrLnやhPutStrLnではなくprintを日本語文字列の出力に使った場合,文字列が数値を使った表現にエスケープされてしまう点に注意してください。(中略)

エスケープの原因は,printの内部で使われているshow関数にあります。Char型のShowクラスに対するインスタンスでは,文字列をどんな環境でも表示可能にするため,UnicodeでDELよりも後にくる文字をエスケープするよう定義されています(中略)

エスケープされた文字列は,Readクラスのreadメソッドを使うことで元に戻せます。

第39回 一般向けの「Haskell Platform」とインストール・ツールの「cabalコマンド」(5ページ目) | 日経 xTECH(クロステック)

なので、テスト結果出力部分でprint関数なりshow関数なりが呼ばれている可能性が高そうです。

show関数が使われていた

実際にHUnitのコードを追ってみると、予想通りでした。

Test/Text.hs
showPath nodes = foldl1 f (map showNode nodes)
 where f b a = a ++ ":" ++ b
       showNode (ListItem n) = show n
       showNode (Label label) = safe label (show label)
       safe s ss = if ':' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s
Test/Base.hs
assertEqual preface expected actual =
  unless (actual == expected) (assertFailure msg)
 where msg = (if null preface then "" else preface ++ "\n") ++
             "expected: " ++ show expected ++ "\n but got: " ++ show actual

各所でshow関数が使われています。これがエスケープの原因です。

read関数をかます

当該部分にread関数をかまし、ビルド〜インストールし直せば、意図通りに出力できそうです。

実際にやってみます。まず、HUnitのtarボールを展開します。

$ cd ~/.cabal/packages/hackage.haskell.org/HUnit/1.2.2.1
$ tar xzf HUnit-1.2.2.1.tar.gz
$ cd HUnit-1.2.2.1

そして、Test/Text.hsとTest/Base.hsを編集します。diffは下記の通りで、read関数をかましているだけです。

Test/Text.hs
$ diff -u Test/HUnit/Text.hs.org Test/HUnit/Text.hs
--- Test/HUnit/Text.hs.org      2010-07-22 14:09:01.000000000 +0000
+++ Test/HUnit/Text.hs  2010-07-22 14:10:08.000000000 +0000
@@ -113,7 +113,7 @@
 showPath nodes = foldl1 f (map showNode nodes)
  where f b a = a ++ ":" ++ b
        showNode (ListItem n) = show n
-       showNode (Label label) = safe label (show label)
+       showNode (Label label) = safe label (read (show label))
        safe s ss = if ':' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s

Test/Base.hs
$ diff -u Test/HUnit/Base.hs.org Test/HUnit/Base.hs
--- Test/HUnit/Base.hs.org      2010-07-22 14:01:40.000000000 +0000
+++ Test/HUnit/Base.hs  2010-07-22 14:00:36.000000000 +0000
@@ -73,7 +73,7 @@
 assertEqual preface expected actual =
   unless (actual == expected) (assertFailure msg)
  where msg = (if null preface then "" else preface ++ "\n") ++
-             "expected: " ++ show expected ++ "\n but got: " ++ show actual
+             "expected: " ++ read (show expected) ++ "\n but got: " ++ read (show actual)


 -- Overloaded `assert` Function

read版をインストール

ビルドの準備ができたので、「cabal install」します。

$ cabal install

問題なくインストールできました。

テスト実行

さて、テストを実行します。意図通り表示されるでしょうか。

Prelude> :load test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> runTestTT tests
Loading package HUnit-1.2.2.1 ... linking ... done.
### Failure in: 0:テストのラベル
アサーションのラベル
expected: 期待値
 but got: 実際の値
Cases: 1  Tried: 1  Errors: 0  Failures: 1
Counts {cases = 1, tried = 1, errors = 0, failures = 1}

されました!

雑感

僕の環境(CentOS 5+GHC 6.12.2)では問題なさそうなので、このまま使おうと思っています。

次期リリースのHUnitがどうなるのかとか、何らかの理由でエスケープありのままにしておくほうが無難なのかとか、よく分かっていません。GHC自体どんどん進化しているようなので、この日記の内容もすぐ古くなる可能性があります。