【Python】単体試験 / unittest ~ あれこれ編 ~

■ はじめに

https://dk521123.hatenablog.com/entry/2019/10/02/223658
https://dk521123.hatenablog.com/entry/2021/03/31/000000
https://dk521123.hatenablog.com/entry/2021/04/05/165656

の続き。

今回は、Python標準 unittest の Tips を纏めておく

目次

【1】メソッドに関する試験
 1)サンプル
【2】クラスに関する試験
 1)簡単なクラスの単体試験
 2)継承したクラスの単体試験
【3】例外に関する試験
 1)構文
 2)イメージ
 3)サンプル

【1】メソッドに関する試験

1)サンプル

test_file_path_utils.py

import unittest
# 対象のPythonファイル
import file_path_utils


class FilePathUtilsTest(unittest.TestCase):
  def test_convert_file_extention(self):
    file_name = 'hello.world.txt'
    extention = 'csv'
    expected = 'hello.world.csv'
    actual = file_path_utils.convert_file_extention(file_name, extention)
    self.assertEqual(expected, actual)


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

file_path_utils.py

import os

def convert_file_extention(file_name, extention):
  file_name_without_extention = os.path.splitext(file_name)[0]
  return "{}.{}".format(file_name_without_extention, extention)

【2】クラスに関する試験

1)簡単なクラスの単体試験

calculator.py (単体試験の対象クラス)

class Calculator(object):
    def plus(self, x, y):
        return x + y

calculator_test.py

import unittest
import calculator

class CalculatorTest(unittest.TestCase):
    def test_plus(self):
        claz = calculator.Calculator()
        self.assertEqual(claz.plus(2, 1), 3)
        
if __name__ == '__main__':
    unittest.main()

出力結果(成功時)

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

出力結果(「self.assertEqual(claz.plus(2, 1), 4)」と修正し意図的に失敗させた場合)

======================================================================
FAIL: test_plus (__main__.CalculatorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:\Users\Daisuke\Documents\Python Scripts\calculator_test.py", line 7, in test_plus
    self.assertEqual(claz.plus(2, 1), 4)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)

2)継承したクラスの単体試験

[親クラス] base_demo_class.py

class BaseDemoClass(object):
  def __init__(self):
    self.base_value = 'World'

  def say_hello(self):
    return f'Hello {self.base_value}!'

  def say_hi(self, name):
    return f'Hi, {name}'

[テスト対象クラス] demo_class.py

from base_demo_class import BaseDemoClass

class DemoClass(BaseDemoClass):
  def __init__(self):
    super().__init__()
    self.value = 'Sam'

  def say_thank_you(self):
    return f'Thank you, {self.value}!'

# 簡易動作確認用
if __name__ == '__main__':
  print('Start')
  target_class = DemoClass()
  print(target_class.say_thank_you())
  print(target_class.say_hello())
  print(target_class.say_hi('Mike'))
  print('Done')

test_demo_class.py

import unittest

from demo_class import DemoClass


class TestDemoClass(unittest.TestCase):

  # テストクラスが初期化される際に一度だけ呼ばれる
  @classmethod
  def setUpClass(cls):
    print('setUpClass')

  # テストクラスが解放される際に一度だけ呼ばれる
  @classmethod
  def tearDownClass(cls):
    print('tearDownClass')

  # テストメソッドを実行するたびに呼ばれる
  def setUp(self):
    print('setUp')
    self.target_class = DemoClass()

  # テストメソッドの実行が終わるたびに呼ばれる
  def tearDown(self):
    print('tearDown')

  # 1) say_hello
  def test_say_hello(self):
    self.assertEqual(
      self.target_class.say_hello(),
      'Hello World!')

  # 2) say_hi
  def test_say_hi(self):
    self.assertEqual(
      self.target_class.say_hi('Kevin'),
      'Hi, Kevin')

  # 3) say_thank_you
  def test_say_thank_you(self):
    self.assertEqual(
      self.target_class.say_thank_you(),
      'Thank you, Sam!')

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

【3】例外に関する試験

参考文献
https://hiroronn.hatenablog.jp/entry/20181004/1538611164

1)構文

with self.assertRaises(【例外】):
  # 例外を出すメソッドをコール

2)イメージ

  def test_is_prime_raise_error(self):
    with self.assertRaises(TypeError):
      is_prime('dummy')

3)サンプル

calculator.py

def add(num1, num2):
  if (not num1 or not num2):
    raise ValueError('input is None')
  return num1 + num2

calculator.py

import unittest
import calculator

class CalculatorTest(unittest.TestCase):
  def test_add(self):
    self.assertEqual(calculator.add(2, 1), 3)
    
    with self.assertRaises(ValueError):
      calculator.add(None, 1)
    
    with self.assertRaises(ValueError):
      calculator.add(1, None)

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

関連記事

単体試験 / unittest ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2019/10/02/223658
単体試験 / unittest ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2021/03/31/000000
単体試験 / unittest ~ mock編 ~
https://dk521123.hatenablog.com/entry/2021/04/05/165656
単体試験 / pytest ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/12/13/224810