pytestのtest IDにパラメータ由来の日本語を使う方法

## TL;DR
- pytestのtest IDにパラメータ由来の日本語を真っ当に表示するためには `pytest.ini` で `disable_test_id_escaping_and_forfeit_all_rights_to_community_support` をTrueに設定する必要がある
- これを設定するとidsを指定すればきちんと表示されるようになるが、個別のidを設定すると表示が崩れる

## 何がしたかったか

パラメタライズドテストでパラメータごとに分かりやすい名前を日本語で付けたかった。
例えば、

```python
@pytest.mark.parametrize(
    "param",
    [True, False],
    ids=["真", "偽"],
)
def test_テスト(param: bool):
    assert param
```

このようなテストでは、

`test_テスト[偽] - assert False`

と出力されてほしい。


## 現実にはどうなってしまうか

このまま実行すると

`test_テスト[\u507d] - assert False`

と、パラメータ由来の日本語(正確には非ASCII文字)はエスケープされた状態で出力されてしまう。
なお、これはparameterizeのIDに限らず、例えば

```python
@pytest.mark.parametrize(
    "param",
    ["真", "偽"],
)
def test_テスト2(param: str):
    assert param == "真"
```

のようにパラメータがIDに使われる場合にでも、

`test_テスト2[\u507d] - AssertionError: assert '偽' == '真'`

このように残念な出力になってしまう。
(テスト名本体やassertの部分ではちゃんと日本語が表示できてるところが芸術点高いと思う)

## どうして……

この問題はpytestのissueに挙げられている。
[Is there anyway to not escape id in parametrization?](https://github.com/pytest-dev/pytest/issues/2482)
ざっと追ってみると、

> Those test IDs are used in various places - on Windows, even printing them if they're non-ascii can be problematic, and we also potentially use them as filenames and in other problematic places. 
> (雑訳) これらのテストIDは色んな場所で使われる。Windowsでは、非ASCII文字を印字したりファイル名にしたり、その他いろんなことに使うと問題になることがある

というコメントがあり、Windowsなど一部のUnicodeの扱いに難がある環境に配慮してのことらしい。
~~[90%以上のエンジニアが理想とする環境であるWindows()](https://prtimes.jp/main/html/rd/p/000000015.000053672.html)を無視するわけにもいかなかったのだろう~~


## どうすればいいか？

pytestには、この[エスケープを無効化するオプションが追加](https://github.com/pytest-dev/pytest/pull/2537)されている。
`pytest.ini` ファイルに、

```ini
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
```

を追加すれば、

```python
@pytest.mark.parametrize(
    "param",
    [True, False],
    ids=["真", "偽"],
)
def test_テスト(param: bool):
    assert param
```

このテストケースで、 `test_テスト[偽] - assert False` のように適切な表示がされるようになる。


## 残る問題

ただし、上記のような書き方は値とIDをバラバラにしてあるので、テストケースの追加・変更をしたいときに面倒である。
パラメータのセットに名前を付けたいというのがIDを付与するモチベーションであるので、当然、値とIDはセットで定義したい。
pytestでは、そのための記法が用意されている。

```python
@pytest.mark.parametrize(
    "param",
    [
        pytest.param(True, id="真"),
        pytest.param(False, id="偽"),
    ],
)
def test_テスト(param: bool):
    assert param
```

少々冗長になるが、まあ許容範囲である。
ところが、これを実行すると、 `test_テスト[\u507d] - assert False` となり、 `disable_test_id_escaping_and_forfeit_all_rights_to_community_support` を設定する前の状態に逆戻りしてしまう。
(このオプションを実装したときに考慮漏れだと思うので、そのうち修正PRを送ろうかなと思っている)

### 美しくない対処法1: パラメータに埋め込む

あまりキレイな対処方法ではないのだが、

- IDと値をセットで定義する
- 日本語も正しく出力する

を両立する方法もある。
ids(とid)を指定しない場合、test IDにはパラメータが羅列されるという仕様を利用して、名付けのためだけのパラメータを指定する。

```python
@pytest.mark.parametrize(
    "_id, param",
    [("真", True), ("偽", False)],
)
def test_テスト(_id: str, param: bool):
    assert param
```

のようにテストコードを書くことで、 `test_テスト[偽-False] - assert False` と、一応は自分で付けた名前が正しく出力されるようになる。


### 美しくない対処法2: IDと値のセットを別で定義する

このようにパラメータを辞書型で定義しておき、値のセットをvaluesで、IDをkeysで取り出す。

```python
_params_テスト = {
    "真": True,
    "偽": False,
}
@pytest.mark.parametrize(
    "param",
    list(_params_テスト.values()),
    ids=list(_params_テスト.keys()),
)
def test_テスト(param: bool):
    assert param
```

出力は `test_テスト[偽] - assert False` のように、余計なパラメータが表示されない分、スッキリはする。