Diary

Diary

日々学んだことをアウトプットする場として初めてみました

python で初めての Unit Test

python で初めての Unit Test

突然ですが皆さん、テスト書いてますか?

個人開発だとあんまり積極的に書かないかもしれませんが、ある程度からは必須のスキルだと思っています。

かく言う自分もまともに書いたことありませんが、今日から意識して書いていこうと思います!

UnitTest

単体テストと呼ばれることもあり、小さな機能を1つの単位(ユニット)としてその動きが正しいかをテストするものである。

通常は関数やメソッドが対象となり、また関数やメソッドもテストがしやすいように1つの機能に抑えておくべきである。

円の面積を求めるプログラム

単純に実装してみる

circles.py

from math import pi

def circle_area(r):

    return pi*(r**2)

この関数に対するテストを実装する

テスト関数を書くファイルの名前は、<filename>_test.pyのようにする

circles_test.py

import unittest
from circles import circle_area
from math import pi

class TestCircleArea(unittest.TestCase):
    def test_area(self):
        # Test areas when radius >=0
        self.assertAlmostEqual(circle_area(1), pi)
        self.assertAlmostEqual(circle_area(0), 0)
        self.assertAlmostEqual(circle_area(2.1), pi * 2.1**2)

上のようにunittest.TestCaseを継承したクラスを定義する。クラス名はTestから始める必要がある。

テストを実行させる

$ python -m unittest circles_test.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

これで十分か?

テストを書いていると、「あれ、負の数はどうだろう、とか実数以外の時はどうだろう」という視点が持ててくる....

関数を呼ぶときに引数を毎回0以上の実数かチェックしても良いが、他の人に使ってもらうには引数に何が来ても関数内で対応できるようにしておきたい。

そこで、何が必要かをテスト関数から変更してみる。

テスト関数の変更

以下では負の数に対しては ValueError, 実数以外に関しては TypeError をあげるようにしている。

circles_test.py

import unittest
from circles import circle_area
from math import pi

class TestCircleArea(unittest.TestCase):
    def test_area(self):
        # Test areas when radius >=0
        self.assertAlmostEqual(circle_area(1), pi)
        self.assertAlmostEqual(circle_area(0), 0)
        self.assertAlmostEqual(circle_area(2.1), pi * 2.1**2)
    
    def test_values(self):
        # Make sure value errors are raised when necessary
        self.assertRaises(ValueError, circle_area, -2)

    def test_types(self):
        self.assertRaises(TypeError, circle_area, 3+5j)
        self.assertRaises(TypeError, circle_area, True)
        self.assertRaises(TypeError, circle_area, "radius")

当然メイン関数を変更してないので、このテストは失敗する(テストは失敗させてなんぼ!)

$ python -m unittest circles_test.py
.FF
======================================================================
FAIL: test_types (circles_test.TestCircleArea)
----------------------------------------------------------------------
Traceback (most recent call last):
...
AssertionError: TypeError not raised by circle_area

======================================================================
FAIL: test_values (circles_test.TestCircleArea)
----------------------------------------------------------------------
Traceback (most recent call last):
...
----------------------------------------------------------------------
Ran 3 tests in 0.016s

FAILED (failures=2)

メイン関数の実装の変更

必要な例外を全てあげたら、あとは丁寧に実装する。

circles.py

from math import pi

def circle_area(r):
    if type(r) not in [int, float]:
        raise TypeError("The radius must be a non-negative real number")
    
    if r < 0:
        raise ValueError("The radius cannot be negative.")
    return pi*(r**2)

これで関数を実行させると、無事テストが全て通る。

python -m unittest circles_test.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

おわりに

この記事では、pythonにおける Unit Test の実行方法を実例を使って紹介しました。

これからは自分も意識してテストを書いていきたいと思います!