assert

assertを使ってみます。

a = 0
for i in range(5):
  a += i
  assert a < 30, a
print a

a = 0
for i in range(10):
  a += i
  assert a < 30, a
print a

実行結果は以下のようになります。

10
Traceback (most recent call last):
  File "a.py", line 10, in ?
      assert a < 30, a
      AssertionError: 36

assertは

assert 式1, 式2

のように使います。式1が真の場合は何もしないのですが、式1が偽の場合は例外AssertionErrorを送出し、式2を評価します。実行結果を見ると最初のループは a < 30 が真のまま終了し、print a が実行されていることがわかります。二番目のループでは途中で a < 30 が偽となり、AssertionErrorが送出されています。また、そのときの a が評価され、36 であることがわかります。

unittest.TestCase

unittestをするためにはunittest.TestCaseのサブクラスを作り、testで始まるメソッドを定義します。メソッドの中でassert*を使い、assert*による例外が発生しなかった場合、テストの結果は成功になります。


参考資料

# test.py
import unittest

class FooTest(unittest.TestCase):
  def setUp(self):
    self.seq = [i for i in range(10) if i%2]

  def test_str(self):
    """''.join(map(str, self.seq)) == '13579'?"""
    self.assertEqual(''.join(map(str, self.seq)), '13579')

  def test_num(self):
    '''reduce(lambda x, y: x*y, self.seq) < 500?'''
    a = reduce(lambda x, y: x*y, self.seq)
    self.assert_(a < 500, a)

  def test_seq(self):
    '''1 in [1, 3, 5, 7, 9]?'''
    self.assert_(1 in self.seq)

  def test_almosteq(self):
    '''4.989 is almost_eq 4.99?'''
    self.assertAlmostEqual(4.989, 4.99, 2)

  def test_raises(self):
    '''test_raises'''
    self.assertRaises(TypeError, self.seq, 1)

  def test_IndexError(self):
   '''raise IndexError'''
   print self.seq[100]

unittest.main()

実行結果は以下のようになります。

> python test.py
E.F...
======================================================================
ERROR: raise IndexError
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 31, in test_IndexError
    print self.seq[100]
IndexError: list index out of range

======================================================================
FAIL: reduce(lambda x, y: x*y, self.seq) < 500?
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 15, in test_num
    self.assert_(a < 500, a)
AssertionError: 945

----------------------------------------------------------------------
Ran 6 tests in 0.003s

FAILED (failures=1, errors=1)

最初の E.F... は、テストを六つ行い、四つが成功(.)、一つが失敗(F)、一つがassert以外の例外によるエラー(E)であることを示しています。


テストの結果を細かく見る場合は -v をつけて実行します。

> python test.py -v
raise IndexError ... ERROR
4.989 is almost_eq 4.99? ... ok
reduce(lambda x, y: x*y, self.seq) < 500? ... FAIL
test_raises ... ok
1 in [1, 3, 5, 7, 9]? ... ok
''.join(map(str, self.seq)) == '13579'? ... ok

======================================================================
ERROR: raise IndexError
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 31, in test_IndexError
    print self.seq[100]
IndexError: list index out of range

======================================================================
FAIL: reduce(lambda x, y: x*y, self.seq) < 500?
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 15, in test_num
    self.assert_(a < 500, a)
AssertionError: 945

----------------------------------------------------------------------
Ran 6 tests in 0.011s

FAILED (failures=1, errors=1)

unittes.mainは引数moduleで渡したモジュール中で定義されているTestCase(のサブクラス)を全て実行してくれるみたいです。何も渡さない場合は'__main__'で定義されているテストを全て実行します。

# b.py
import unittest
import random

class BarTest(unittest.TestCase):
  def setUp(self):
    self.seq = range(random.randint(1, 1000))

  def test_len(self):
    '''len(self.seq) < 100?'''
    self.assert_(len(self.seq) < 100, len(self.seq))
# test.py
import unittest
import b

class FooTest(unittest.TestCase):
  def setUp(self):
    self.seq = [i for i in range(10) if i%2]

  def test_num(self):
    '''reduce(lambda x, y: x*y, self.seq) < 500?'''
    a = reduce(lambda x, y: x*y, self.seq)
    self.assert_(a < 500, a)

unittest.main(module=b)

というファイルがあった場合、test.pyを実行するとb.pyで定義されているテストが実行されます。unittest.mainの引数でmoduleが指定されていない場合はtest.pyで定義されているテストが実行されます。

unittest.TestSuite

いくつかのテストをまとめて実行する場合はunittest.TestSuiteを使います。

# b.py
import unittest
import random

class BarTest(unittest.TestCase):
  def setUp(self):
    self.seq = range(random.randint(1, 1000))

  def test_len(self):
    '''len(self.seq) < 100?'''
    self.assert_(len(self.seq) < 100, len(self.seq))
# test.py
import unittest
import b

class FooTest(unittest.TestCase):
  def setUp(self):
    self.seq = [i for i in range(10) if i%2]

  def test_num(self):
    '''reduce(lambda x, y: x*y, self.seq) < 500?'''
    a = reduce(lambda x, y: x*y, self.seq)
    self.assert_(a < 500, a)

  def test_seq(self):
    '''1 in [1, 3, 5, 7, 9]?'''
    self.assert_(1 in self.seq)

suite = unittest.TestSuite()

#suite.addTest(Footest('test_num'))           # <= suiteにFooTestのtest_numだけを追加
suite.addTest(unittest.makeSuite(FooTest))    # <= suiteにFooTestで定義されたテストを全て追加
suite.addTest(unittest.makeSuite(b.BarTest))  # <= suiteにBarTestで定義されたテストを全て追加

unittest.TextTestRunner(verbosity=2).run(suite)  # <= suiteに追加されたTestCaseを全て実行して結果を表示

TestCaseのサブクラス中で定義された全てのテストをsuiteに追加するためにはunittest.makeSuiteでTestSuiteに変換します。

実行結果は以下のようになります。

reduce(lambda x, y: x*y, self.seq) < 500? ... FAIL
1 in [1, 3, 5, 7, 9]? ... ok
len(self.seq) < 100? ... ok

======================================================================
FAIL: reduce(lambda x, y: x*y, self.seq) < 500?
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 11, in test_num
    self.assert_(a < 500, a)
AssertionError: 945

----------------------------------------------------------------------
Ran 3 tests in 0.008s

FAILED (failures=1)

unittest.TextTestRunner(verbosity=2).run(suite)のverbosityを変えることで結果の表示の細かさを変えることができます。

unittest.FunctionTestCase

unittest.FunctionTestCaseを使うと、関数をTestCaseに変換することができます。

# test.py
import unittest

def func_test():
  '''func_test'''
  a = 0
  for i in range(10):
    a += i
    assert a > 30, a

suite = unittest.TestSuite()
suite.addTest(unittest.FunctionTestCase(func_test))  # <= 関数からTestCaseに変換してsuiteに追加
unittest.TextTestRunner(verbosity=2).run(suite)      # <= suiteに追加されたTestCaseを全て実行して結果を表示

実行結果は以下のようになります。

func_test ... FAIL

======================================================================
FAIL: func_test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 9, in func_test
    assert a < 30, a
AssertionError: 36

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (failures=1)

他のTestCaseとまとめて実行することもできます。

# b.py
import unittest
import random

class BarTest(unittest.TestCase):
  def setUp(self):
    self.seq = range(random.randint(1, 1000))

  def test_len(self):
    '''len(self.seq) < 100?'''
    self.assert_(len(self.seq) < 100, len(self.seq))
# test.py
import unittest
import b

class FooTest(unittest.TestCase):
  def setUp(self):
    self.seq = [i for i in range(10) if i%2]

  def test_num(self):
    '''reduce(lambda x, y: x*y, self.seq) < 500?'''
    a = reduce(lambda x, y: x*y, self.seq)
    self.assert_(a < 500, a)

def func_test():
  '''func_test'''
  a = 0
  for i in range(10):
    a += i
    assert a < 30, a

suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(FooTest))
suite.addTest(unittest.makeSuite(b.BarTest))
suite.addTest(unittest.FunctionTestCase(func_test))
unittest.TextTestRunner(verbosity=2).run(suite)

実行結果は以下のようになります。

reduce(lambda x, y: x*y, self.seq) < 500? ... FAIL
len(self.seq) < 100? ... ok
func_test ... FAIL

======================================================================
FAIL: reduce(lambda x, y: x*y, self.seq) < 500?
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 12, in test_num
    self.assert_(a < 500, a)
AssertionError: 945

======================================================================
FAIL: func_test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 19, in func_test
    assert a < 30, a
AssertionError: 36

----------------------------------------------------------------------
Ran 3 tests in 0.008s

FAILED (failures=2)