💡
この記事は Middleman 時代に書いた古いものです。記録のため、astro-notion-blog に移行していますが、あまり参考にしないでください。
コンストラクタとプロパティのテスト
MonoImage は NumPy の ndarray を内包するクラスとする. また,ndarray の type とは別に画像のビット数をインスタンスとして持つ. まずは,コンストラクタのテストと,いくつかの読み込みプロパティのテストだけを実施してみる.
-
tests/test_mono_image.py を追記する.height だけ後から定義を修正した.
from nose2.tools import such import numpy as np from hkob_pyimage import MonoImage with such.A('MonoImage class') as it: @it.has_test_setup def setup(): np.set_printoptions(threshold=10) it.mu8 = MonoImage(np.array([[10, 20, 30], [40, 50, 60]], dtype=np.uint8)) it.mi16 = MonoImage(np.array([[1, -2], [-3, 4], [5, -6]], dtype=np.int16), 16, True) it.md = MonoImage(np.array([3.1, 1.4, 1.5, 9.2, 6.5, 3.5], dtype=np.double), 0) it.marray = (it.mu8, it.mi16, it.md) @it.has_teardown def teardown(): pass @it.should('have correct getter properties.') def test(): it.assertEqual([x.bits for x in it.marray], [8, 16, 0]) it.assertEqual([x.signed for x in it.marray], [False, True, False]) it.assertEqual([x.dtype for x in it.marray], [np.uint8, np.int16, np.double]) it.assertEqual([x.width for x in it.marray], [3, 2, 6]) it.assertEqual([x.height for x in it.marray], [2, 3, 1]) it.assertEqual([x.max_value for x in it.marray], [255, 65535, 0]) it.createTests(globals())
-
自分の覚書に気になるところをメモしておく.
-
nose2 を使うならnose2.tools から such module をインポートする
from nose2.tools import such
-
numpy を import するが,長いので通常 np という名前で利用する.
import numpy as np
-
RSpec の describe に相当するものが,with such.A である. ここで,受けている it は nose2.tools.such.Scenario のオブジェクトらしい.
with such.A('MonoImage class') as it:
-
@it.has_test_setup はセットアップメソッドのためのデコレータである (has_setup は最初の一回だけ,has_test_setup はテストごとに呼ばれる). デコレータなので,メソッド名はなんでもよいが,setup(): にするのが一般的だろう. RSpec の before に相当する.
@it.has_test_setup def setup():
-
テストが失敗した時にその原因を表示するために,Numpy array を表示することがある. この時にあまりに数字が多く表示されるとエラーの原因がわかりにくくなるため,threshold を 10 にして設定しておく.
np.set_printoptions(threshold=10)
-
setup() の中では,it.mu8 のようにインスタンスに代入しておくことで,後々のテストで利用するオブジェクトを設定しておく. setup() は各テストの実行前に必ず呼ばれるためである. MonoImage クラスのコンストラクタは,データ本体である ndarray とデータのビット数,符号ありかどうかのフラグを受ける. ただし,ビット数と符号フラグは省略可能とする.ビット数が省略の場合は 8 ビットとし,符号フラグがない場合には,符号なしとする. 以下の例では,2行3列の uint8 の ndarray を持つ,8ビット符号なしモノクロ画像を意味する.
it.mu8 = MonoImage(np.array([[10, 20, 30], [40, 50, 60]], dtype=np.uint8))
-
この場合は,3行2列の int16 のndarray を持つ,16ビット符号ありモノクロ画像である.
it.mi16 = MonoImage(np.array([[1, -2], [-3, 4], [5, -6]], dtype=np.int16), 16, True)
-
同様にこの場合は,長さ 6 の double の ndarray を持つ,double の符号なしモノクロ画像とする.MonoImage ではビット数 0 で double を示すことにする.
it.md = MonoImage(np.array([3.1, 1.4, 1.5, 9.2, 6.5, 3.5], dtype=np.double), 0)
-
(it.mu8, it.mi16, it.md) の表記はタプルを示す.タプルは Swift と同様で imutabble なリストに近い.
it.marray = (it.mu8, it.mi16, it.md)
-
@it.has_teardown は Rails の after に相当するデコレータで,テスト終了時に行う後始末のメソッドである. 今のところ後始末すべきことはないので,メソッドだけ用意しておく. pass は MonoImage の空クラスを作った時にも使用した. インデントベースの Python では中身が空のブロックを表現できず,何もしない pass 文を記述しなければならない.
@it.has_teardown def teardown(): pass
-
@it.should デコレータは RSpec の it に相当するもので,ここがテストになる. RSpec の it に指定する文字列を should の中に記述する. デコレータの文字列が異なれば異なるメソッドになるため,def test(): は常に同じ表記で構わない.
@it.should('have correct getter properties.') def test():
-
it.assertEqual は引数の二つが等しいかどうか判定するメソッドである. 何行も書きたくないので,Python の内包表現でリストを作成した. この表記では it.marray のタプルのそれぞれの中身に対して,x.bits というメソッドを呼び出し,結果をリストで返す. Ruby だと map に相当する処理になる.この行は,RSpec だと以下のような表記になるだろう.
it.assertEqual([x.bits for x in it.marray], [8, 16, 0])
expect(subject.map(&:bits)).to eq [8, 16, 0]
-
nose2 では最後に必ず以下の行を記述する.これを忘れるとテストをしてくれないので注意する.すっかり忘れてテストが動かず焦った.
it.createTests(globals())
-
nose2 を使うならnose2.tools から such module をインポートする
-
chokidar の結果は以下のようになる.
change:mono_image.py ERROR ====================================================================== ERROR: LayerSuite ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 8, in setup it.mu8 = MonoImage(np.array([[10, 20, 30], [40, 50, 60]], dtype=np.uint8)) TypeError: MonoImage() takes no arguments ---------------------------------------------------------------------- Ran 0 tests in 0.000s FAILED (errors=1)
コンストラクタの作成
-
テストが通過するように mono_image.py にコンストラクタを追加する.
import numpy as np class MonoImage: def __init__(self, array, bits=8, signed=False): self._array = array self._bits = bits self._signed = signed
-
chokidar の結果は以下のようになる.
change:mono_image.py test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ... ERROR ====================================================================== ERROR: test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 19, in test it.assertEqual([x.bits for x in it.marray], [8, 16, 0]) File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 19, in <listcomp> it.assertEqual([x.bits for x in it.marray], [8, 16, 0]) AttributeError: 'MonoImage' object has no attribute 'bits' ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (errors=1)
読み込みプロパティの作成
-
bits という属性がないと言われたのでプロパティを追加する.
class MonoImage: (中略) @property def bits(self): return self._bits
-
chokidar の結果は以下のようになる.
change:mono_image.py test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ... ERROR ====================================================================== ERROR: test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 20, in test it.assertEqual([x.signed for x in it.marray], [False, True, False]) File "/Users/hkob/python3/hkob_pyimage/tests/test_mono_image.py", line 20, in <listcomp> it.assertEqual([x.signed for x in it.marray], [False, True, False]) AttributeError: 'MonoImage' object has no attribute 'signed' ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (errors=1)
-
残りの読み込みプロパティも同様に実装してみた.height だけ後から修正している.
class MonoImage: (中略) @property def signed(self): return self._signed @property def dtype(self): return self._array.dtype @property def width(self): return self._array.shape[1] if self._array.ndim > 1 else self._array.shape[0] @property def height(self): return self._array.shape[0] if self.ndim > 1 else 1 @property def max_value(self): return 0 if self._bits == 0 else (1 << self._bits)-1
-
最終的に chokidar の結果は以下のようになる.
change:mono_image.py test 0000: should have correct getter properties. (test_mono_image.A MonoImage class) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
長くなったので今日はここまで.