python単体テスト

さて、結局、単体の品質が高くすることがシステム全体の品質を高くできるというのは間違いのないことなのです。

幸い、私はJavaから入ってjUnitを使ったテストファーストを習慣化できたことで、質の高いプログラムを書けるようになったと思っております。

ということで、最近よく使うPythonの単体テストフレームワークについて学んでいきます。

unittest

unittest – Unit testing framework – Python 3.9.1 documentation

Source code: Lib/unittest/__init__.py (If you are already familiar with the basic concepts of testing, you might want to skip to the list of assert methods .) The unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages.

The unittest unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages.

「unittest」単体テストフレームワークは、jUnitに影響を受けてて、他の言語のメジャーな単体テストフレームワークと似たような感じです、と。

しかもデフォルトで含まれているので、pip installなんかもする必要がありません。

サンプルである

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

もそのまま動いちゃうし、jUnitやってた人なら楽勝でしょう。

import unittest

書いて、

class TestStringMethods(unittest.TestCase):

unittest.TestCaseを継承して

    def test_xxxx(self):
        self.assertEqual("actual", "expected")

関数でテストコードを書くだけ。

あ、

if __name__ == '__main__':
    unittest.main()

最後にこれを書いておく必要はありそう。

実行サンプルは、

$ python Downloads/ab.py
 ..F
 FAIL: test_upper (main.TestStringMethods)
 Traceback (most recent call last):
   File "Downloads/ab.py", line 6, in test_upper
     self.assertEqual('foo'.upper(), 'FOOx')
 AssertionError: 'FOO' != 'FOOx'
 
 Ran 3 tests in 0.000s
 FAILED (failures=1)

という感じ。

pytest

pytest: helps you write better programs – pytest documentation

The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries.

「pytest」フレームワークを利用することで、小さなテストを書いやすくなるだけでなく、アプリケーションやライブラリの複雑な機能テストをサポートするくらいスケールできる。

サンプルは非常にシンプル。
※あえてエラーになるようなサンプル。

# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

複数テストをしたい場合は

# content of test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

TestClassに複数の関数を定義してあげるだけ。

実行サンプルは

$ pytest pytest_ab.py 
 ========================== test session starts ==========================
 platform darwin -- Python 3.8.2, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
 rootdir: /xxxx/xxxxx
 collected 2 items                                                       
 pytest_ab.py .F                                         [100%]
 =============================== FAILURES ================================
 ______________ TestClass.test_two _______________
 self = 
 def test_two(self):     x = "hello"
   assert hasattr(x, "check")
   E       AssertionError: assert False
   E        +  where False = hasattr('hello', 'check') 
 pytest_ab.py:9: AssertionError
 ======================== short test summary info ========================
 FAILED pytest_ab.py::TestClass::test_two - AssertionError: a…
 ====================== 1 failed, 1 passed in 0.04s ======================

という感じで、unittestより情報量が多い。

まとめ

unittestはその他の言語でxUnitをやってきた人にとってはとっつきやすいかも。

pytestは3rdパーティだけあって、pytest-convのようなカバレッジ機能を追加したり、機能テストまでできるところが嬉しいところなのかな。