Python 3 エンジニア認定実践試験対策 (8) - unittest

Pythonを数年使ってきて、改めて知識の整理と最新仕様のキャッチアップのために Python 3 エンジニア認定実践試験を受験しようと思い、私的に作成した資料から簡易版を作成しました。
最後に練習問題を設けましたので、解説を読んだら記憶定着のために練習問題へチャレンジしてください。最後に答えを掲載しています。

---

# unittest

### unittestとは
  - Pythonの標準ユニットテストフレームワーク
  - 複雑なテストケースを管理し実行できる。

### テストランナー
  - 「テストランナー」がテスト実行と結果出力を管理する。
  - テストランナーは、ルールに従って作成されたテストクラス/テストメソッドを検索し実行する。

### テストケースの作成
  - テストケースの作成ルール
    - `unittest` をインポート
    - テストクラスは `unittest.TestCase` を継承
    - テストメソッド名は `test_` で始める

  ```python
  import unittest
  from example import sum_function

  class SumTest(unittest.TestCase):
      def test_sum_of_two_numbers(self):
          """ sum_function のテストコード """
          result = sum_function(1, 2)
          expected = 3
          self.assertEqual(result, expected)

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

- **テストケースの実行**
  - コマンド: `$ python test_sum.py`

### アサーションメソッド
  - テスト結果を確認するためのメソッド
  - 代表的なメソッド:
    - `assertEqual(a, b)`: aとbが同一
    - `assertNotEqual(a, b)`: aとbが異なる
    - `assertTrue(x)`: xがTrue
    - `assertFalse(x)`: xがFalse
    - `assertIsNone(x)`: xがNone
    - `assertRaises(exception)`: 指定した例外が発生

- **アサーションメソッドの使用例**
  ```python
  import unittest
  from example import sum_function

  class SumTest(unittest.TestCase):
      def test_assert_equal(self):
          result = sum_function(1, 2)
          expected = 3
          self.assertEqual(result, expected)

      def test_assert_is_not_none(self):
          result = sum_function(1, 2)
          self.assertIsNotNone(result)

      def test_assert_is_instance(self):
          result = sum_function(1, 2)
          self.assertIsInstance(result, int)

      def test_assert_raises(self):
          with self.assertRaises(TypeError):
              sum_function(None, 2)

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

### テスト結果の注意点
  - `unittest`では、成功したアサーションの詳細は表示されない。
  - テスト結果は、結果の簡潔さを重視して失敗やエラー情報だけを表示する。
  - ただし、テストが成功した場合、「.」を表示する。
  - 成功したテストの詳細を確認する場合、ログや`print`文を使用する。

### 複数アサーションの実行とエラー処理 — subTest()メソッドとwith文
  - 1つのテストメソッドに複数のアサーションメソッドがある場合、エラーになった時点で以降のアサーションメソッドが実行されないことがある。
  - `subTest()`と`with`文を使って複数アサーションを1つのテストメソッド内で実行可能。

  ```python
  import unittest
  from example import sum_function

  class SumTest(unittest.TestCase):
      def test_sum_of_two_numbers(self):
          test_cases = [
              [2, 5, 7],
              [4, 1, 6],  # エラーケース
              [7, 2, 9],
          ]
          
          for idx, case in enumerate(test_cases):
              x, y, expected_result = case
              with self.subTest(f'{x} + {y} = {expected_result}', idx=idx):
                  self.assertEqual(sum_function(x, y), expected_result)

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

### テストの前処理 — `setUp()`, `setUpClass()`

テストの前処理とは、`setUp()`メソッドと`setUpClass()`メソッドはテスト実行前に自動実行されます。データベースへのテストデータの投入などの準備作業によく使われます。

- **`setUp()`**
  - テストメソッド実行前に呼び出されるメソッド。
  - 各テストメソッドの実行前に1回ずつ自動で呼ばれる。

- **`setUpClass()`**
  - クラス内のテストが実行される前に1回だけ呼び出されるクラスメソッド。
  - `@classmethod`でデコレートし、cls (クラス)を引数に取る必要があります。

  ```python
  import unittest

  class SampleTest(unittest.TestCase):
      def setUp(self):
          print('setUp 実行')

      @classmethod
      def setUpClass(cls):
          print('setUpClass 実行')

      def test_case1(self):
          print('test_case1 実行')

      def test_case2(self):
          print('test_case2 実行')

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

### テストの後処理 — `tearDown()`, `tearDownClass()`

テストが実行された後に、必要なクリーンアップ処理を行うために使用します。

- **`tearDown()`**
  - テストメソッド実行後に呼び出される。
  - テストの結果にかかわらず、`setUp()`が成功した場合にのみ呼ばれる。

- **`tearDownClass()`**
  - クラス内のテストが実行された後に1回だけ呼び出されるクラスメソッド。

  ```python
  import unittest

  class SampleTearDownTest(unittest.TestCase):
      def tearDown(self):
          print('tearDown 実行')

      @classmethod
      def tearDownClass(cls):
          print('tearDownClass 実行')

      def test_case1(self):
          print('test_case1 実行')

      def test_case2(self):
          print('test_case2 実行')

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

### コマンドラインでテストを実行する

- 特定モジュール単位のテスト実行:
  ```
  $ python -m unittest test_module1 test_module2
  ```
- クラス単位のテスト実行:
  ```
  $ python -m unittest test_module.TestClass
  ```
- メソッド単位のテスト実行:
  ```
  $ python -m unittest test_module.TestClass.test_method
  ```

- **コマンドラインオプション**
  - `-b`, `--buffer`: 標準出力とエラーをバッファリングし、最後にまとめて表示。
  - `-c`, `--catch`: 複数テスト実行中にCtrl + Cが押されても即中断せず、その時に実行中のテストが終わるところまで中断を遅延させる。（実行予定だったテストは実行されない。）
  - `-f`, `--failfast`: 初回のエラーでテストを停止。
  - `-k`: パターンにマッチするテストのみ実行。複数条件を指定できる -k 条件1 -k 条件2。
  - `--locals`: トレースバックに局所変数も表示（通常、局所変数は表示されない。）

--- 
# 確認問題

1. `unittest`とは何ですか？
   a) Pythonの標準ライブラリ  
   b) Pythonの標準ユニットテストフレームワーク  
   c) Pythonのデバッグツール  
   d) Pythonのパッケージ管理ツール  

2. テストランナーの役割は何ですか？
   a) テストケースを作成する  
   b) テストの実行と結果出力を管理する  
   c) テストのエラーを修正する  
   d) テストのパフォーマンスを測定する  

3. テストメソッド名はどのように命名する必要がありますか？
   a) testMethod  
   b) testMethodName  
   c) test_で始める  
   d) testMethod_  

4. `assertEqual(a, b)`の役割は何ですか？
   a) aとbが異なることを確認する  
   b) aとbが同一であることを確認する  
   c) aがTrueであることを確認する  
   d) aがNoneでないことを確認する  

5. `setUp()`メソッドはいつ呼び出されますか？
   a) テストクラスの初期化時  
   b) 各テストメソッドの実行前  
   c) テストメソッドの実行後  
   d) テストクラスの実行後  

6. `tearDown()`メソッドの目的は何ですか？
   a) テストの前処理を行う  
   b) テストの後処理を行う  
   c) テストの結果を表示する  
   d) テストの実行を中断する  

7. `setUpClass()`メソッドはどのようにデコレートされますか？
   a) `@staticmethod`
   b) `@classmethod`  
   c) `@unittest`  
   d) `@setup`  

8. `subTest()`メソッドの使用目的は何ですか？
   a) 複数のテストを一度に実行する  
   b) 複数のアサーションを1つのテストメソッド内で実行する  
   c) テストの結果をログに記録する  
   d) テストの実行をスキップする  

9. テスト結果が成功した場合、`unittest`は何を表示しますか？
   a) 成功したテストの詳細  
   b) エラー情報  
   c) "."（ドット）  
   d) "Success"  

10. コマンドラインで特定のテストメソッドを実行するためのコマンドはどれですか？
    a) `$ python -m unittest test_module.TestClass`  
    b) `$ python -m unittest test_module.TestClass.test_method`  
    c) `$ python -m unittest test_method`  
    d) `$ python -m unittest test_module`  

11. `assertRaises(exception)`の役割は何ですか？
    a) 指定した例外が発生しないことを確認する  
    b) 指定した例外が発生することを確認する  
    c) 例外の詳細を表示する  
    d) 例外を無視する  

12. `-f`オプションの役割は何ですか？
    a) テストを強制的に実行する  
    b) 初回のエラーでテストを停止する  
    c) テストの結果をファイルに保存する  
    d) テストの実行を遅延させる  

13. `assertIsNone(x)`の役割は何ですか？
    a) xがTrueであることを確認する  
    b) xがNoneでないことを確認する  
    c) xがNoneであることを確認する  
    d) xがFalseであることを確認する  

14. `unittest`でテストを実行する際、成功したアサーションの詳細はどうなりますか？
    a) 詳細が表示される  
    b) 成功したテストの数が表示される  
    c) 表示されない  
    d) エラー情報と共に表示される  

15. `tearDownClass()`メソッドはいつ呼び出されますか？
    a) 各テストメソッドの実行後  
    b) クラス内のテストが実行された後に1回だけ  
    c) テストメソッドの実行前  
    d) テストクラスの初期化時  

16. テストクラスのスーパークラスとして正しいものはどれですか？
    a) `unittest.Test`  
    b) `unittest.BaseTest`  
    c) `unittest.TestCase`  
    d) `unittest.TestSuite`  


解答:
1. b  
2. b  
3. c  
4. b  
5. b  
6. b  
7. b  
8. b  
9. c  
10. b  
11. b  
12. b  
13. c  
14. c  
15. b  
16. c