pythonでgoのtable driven testを書くならsubTestを使うと良いのかも

## はじめに

go界隈で[table driven test](https://github.com/golang/go/wiki/TableDrivenTests)と呼ばれる形式でunit testを書く事が好まれているらしい(とは言え、他の言語でも同様のものは存在したりはしていたので発明したというわけではないけれど)。

pythonでtable driven testを書くならどうすると良いかと言うことを考えてみた。

## unittestで普通に書くと長い

例えば、以下のようなaddという関数をテストするとする。

```python
def add(x, y):
    return x + y
```

pythonのunittestモジュールの範囲で愚直に書くと以下の様な感じ。

```python
import unittest


class Tests(unittest.TestCase):
    def _callFUT(self, x, y):
        from add import add
        return add(x, y)

    def test_with_positive(self):
        actual = self._callFUT(10, 10)
        self.assertEqual(actual, 20)

    def test_with_zero(self):
        actual = self._callFUT(10, 0)
        self.assertEqual(actual, 10)

    def test_with_negative(self):
        actual = self._callFUT(10, -10)
        self.assertEqual(actual, 0)

    def test_with_biiiiiiiiiig(self):
        actual = self._callFUT(
            10,
            10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,
        )
        self.assertEqual(
            actual,
            10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010,
        )
```

長い。だるい。

(callFUTについては信仰的なもの。詳しくは[この辺](https://pylonsproject.org/community-unit-testing-guidelines.html)を参照)

## table driven test

table driven testにするとシュッとした感じになる。[subTest](https://docs.python.jp/3/library/unittest.html#unittest.TestCase.subTest)を使うと良さそう。

```python
import unittest
from collections import namedtuple


class Tests(unittest.TestCase):
    def _callFUT(self, x, y):
        from add import add
        return add(x, y)

    def test_it(self):
        C = namedtuple("C", "msg x y expected")
        candidates = [
            C(msg="with positive", x=10, y=10, expected=20),
            C(msg="with zero", x=10, y=0, expected=10),
            C(msg="with negative", x=10, y=-10, expected=0),
            C(
                msg="with biiiiiiig",
                x=10,
                y=10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,
                expected=10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010,
            ),
        ]
        for c in candidates:
            with self.subTest(msg=c.msg):
                actual = self._callFUT(c.x, c.y)
                self.assertEqual(actual, c.expected)
```

以下は個人的な好み

- namedtupleを利用している
- msgも一緒に書く
- _callFUT(testメソッドが一つで終わるのなら分けなくても良いかもしれない)

### subTestの件数の集計方法

成功したときには1件としてカウントされる。

```
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK
```

失敗したときにはN件としてカウントされるので良い。

```

======================================================================
FAIL: test_it (__main__.Tests) [with positive]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 26, in test_it
    self.assertEqual(actual, c.expected + 1)
AssertionError: 20 != 21

======================================================================
FAIL: test_it (__main__.Tests) [with zero]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 26, in test_it
    self.assertEqual(actual, c.expected + 1)
AssertionError: 10 != 11

======================================================================
FAIL: test_it (__main__.Tests) [with negative]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 26, in test_it
    self.assertEqual(actual, c.expected + 1)
AssertionError: 0 != 1

======================================================================
FAIL: test_it (__main__.Tests) [with biiiiiiig]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 26, in test_it
    self.assertEqual(actual, c.expected + 1)
AssertionError: 10000[33 chars]000000000000000000000000000000000000000000000000000000000000010 != 10000[33 chars]000000000000000000000000000000000000000000000000000000000000011

----------------------------------------------------------------------
Ran 1 test in 0.003s

FAILED (failures=4)
```

## pytestを使った場合

pytestを使ったtable driven testについては誰か書いてくれたりしないかな。