日本語資料・ English talk です
PyCon APAC 2023
2023/10/27 nikkie
株式会社ユーザベースのデータサイエンティスト(We're hiring!)
いまはまだできなくても大丈夫。これからできるようになればいい
最近ハマってる #ミリアニネタバレ感想
前提:Pythonの 関数 が書ける
いまはまだテストコードを書いたことがなくて大丈夫
テストコードが書けるメリット
doctestの使い方
pytestの使い方
Python 3.12.0 (latest🙌)
持てる知識を全て動員して書いたが、より適切な文法を知らなかった
新しく知った書き方 で書き直したい ➡️ 学びになり、Pythonの力がつく💪
def fizzbuzz(number: int) -> str:
if number % 3 == 0 and number % 5 == 0:
return "FizzBuzz"
elif number % 3 == 0:
return "Fizz"
elif number % 5 == 0:
return "Buzz"
else:
return str(number)
https://pycamp.pycon.jp/textbook/2_intro.html#fizzbuzz リスト2.14
def fizzbuzz(number: int) -> str:
match number % 3, number % 5:
case 0, 0: return "FizzBuzz"
case 0, _: return "Fizz"
case _, 0: return "Buzz"
case _, _: return str(number)
不安 に対処するいくつかのアプローチ
🙏🙏「どうか変わっていませんように」🙏🙏
振る舞いを変えていないか不安だが、 何も確認はしない
例えば対話的に fizzbuzz
関数を実行
>>> from fizzbuzz import fizzbuzz
>>> fizzbuzz(15)
'FizzBuzz'
安心できるが、 関数の数が増えて いくと現実的ではなさそう
この発表の本題
「手で動作確認」の 自動化 (テストコードを書く)
プログラムで使う部品のコードは、プログラムを書いて動作確認するという考え方
テストコードを実行すると、いずれか
pass (全て通る・成功)
fail (1つでも失敗・落ちる)
>>> actual = fizzbuzz(15) # テスト対象を実行した値
>>> expected = "FizzBuzz" # 期待結果
>>> actual == expected
True
actual |
expected |
|
|
|
|
|
|
実装中、仕様を満たす 動作するコード であると確認できる🙌
書き換える際も、おかしくしていたら気付ける 🙌(回帰テスト)
実装に加えてテストコードも書く
でも、デメリット << メリット だと思うから、📣練習して書けるようになるんだ!
テストコードにより、「この実装は仕様を満たす 動作 するコード」と 確認 できる
新しく知った文法を試して書き換えるとき、 誤 って振る舞いを変えてしまっても 気づける
テストコードが書けるメリット
doctestの使い方
pytestの使い方
テストコードをどう書くか Part 1/2
標準ライブラリ
https://docs.python.org/ja/3/library/doctest.html
対話的な実行例をテストする
クラス、関数、モジュールの最初の式である文字列リテラル
オブジェクトのドキュメントを書く標準的な場所
https://docs.python.org/ja/3/tutorial/controlflow.html#tut-docstrings
三連引用符 を用い、複数行にまたがった文字列リテラルとすることがほとんど
def fizzbuzz(number: int) -> str:
"""FizzBuzzゲームを解く関数(:1行要約)
...(後述)...
"""
def fizzbuzz(number: int) -> str:
"""FizzBuzzゲームを解く関数
>>> fizzbuzz(1)
'1'
>>> fizzbuzz(3)
'Fizz'
"""
.
└── fizzbuzz.py
python -m doctest fizzbuzz.py
$ python -m doctest fizzbuzz.py -v
Trying:
fizzbuzz(1)
Expecting:
'1'
ok
4 passed and 0 failed.
Test passed.
nikkieはテキストファイル(特にreStructuredText)で頻繁に使用
書籍執筆
発表資料 作成(本資料含む)
例:scikit-learn
>>> from sklearn.metrics import f1_score
>>> f1_score(y_true, y_pred, average='macro')
0.26...
https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html
def fizzbuzz(number: int) -> str:
"""FizzBuzzゲームを解く関数
>>> fizzbuzz(3)
"Fizz"
"""
$ python -m doctest fizzbuzz.py
Failed example:
fizzbuzz(3)
Expected:
"Fizz"
Got:
'Fizz'
***Test Failed*** 1 failures.
対話モードでは 文字列 は基本 シングルクォート で囲まれる
>>> "Fizz"
'Fizz'
doctestでも文字列はシングルクォートにする必要がある
repr
関数の返り値repr() 関数はインタープリタに読める(略)表現を返すためのもの
https://docs.python.org/ja/3/tutorial/inputoutput.html#fancier-output-formatting
repr
関数の返り値であることを利用した例 🏃♂️ (skip)class Awesome:
"""
>>> Awesome("PyCon APAC")
Awesome('PyCon APAC')
"""
def __init__(self, string: str) -> None:
self.string = string
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.string!r})"
(クラスのdocstringでdoctestの例にもなってます)
def fizzbuzz(number: int) -> str:
"""FizzBuzzゲームを解く関数
>>> fizzbuzz(3)
'Fizz'
"""
実行例を書くだけで、関数に ある値を入力したときの出力 を検証できた
3A という見方を導入
Arrange 準備
Act 実行
Assert 検証
※コメントを使って説明するため、対話モードで示します
>>> number = 3
>>> fizzbuzz(number)
'Fizz'
テストの 準備 (データの用意など)
>>> number = 3
>>> fizzbuzz(number)
'Fizz'
テスト対象の関数を 実行
>>> number = 3
>>> fizzbuzz(number)
'Fizz'
実行結果が期待値と等しいかを 検証
>>> number = 3
>>> fizzbuzz(number)
'Fizz'
クリーンアップ
『ロバストPython』第21章より
対話モードの 実行例を、docstringに書くだけ!
python -m doctest にPythonファイルを渡してテスト実行
テストコードの一歩目として非常にオススメです
関わっているコミュニティの ポスター @20F
Start Python Club (#stapy)
読書py
テストコードが書けるメリット
doctestの使い方
pytestの使い方
テストコードをどう書くか Part 2/2
サードパーティライブラリ https://pypi.org/project/pytest/
pip install pytest
ヒミツ:古くは py.test だった(Issue#1629)
テストコードのファイルを作る
テストコードとして、関数を書く
assert文
test_
で始まるPythonファイルを作成
.
├── fizzbuzz.py
└── test_fizzbuzz.py
test_
で始まる関数を書く
def test_3の倍数のときはFizzを返す():
...
assert 式
式が True
と評価されるかを検証
https://docs.python.org/ja/3/reference/simple_stmts.html#the-assert-statement
>>> 1 == 1
True
>>> assert 1 == 1
>>> 1 == 2
False
>>> assert 1 == 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
def test_3の倍数のときはFizzを返す():
number = 3 # Arrange
# Act & Assert (actual == expected)
assert fizzbuzz(number) == "Fizz"
.
├── fizzbuzz.py
└── test_fizzbuzz.py
$ pytest -v
============================= test session starts ==============================
collected 5 items
============================== 5 passed in 0.01s ===============================
test_fizzbuzz.py::test_15の倍数のときはFizzBuzzを返す FAILED [ 20%]
=================================== FAILURES ===================================
__________________________ test_15の倍数のときはFizzBuzzを返す ___________________________
def test_15の倍数のときはFizzBuzzを返す():
> assert fizzbuzz(15) == "FizzBuzz"
E AssertionError: assert 'Fizz' == 'FizzBuzz'
E - FizzBuzz
E + Fizz
assert文だが、 なぜAssertionErrorかが分かりやすい
テストコードに使うのはassert文だけと 簡単
failしたテストの理由が 分かりやすい
秘密は pytestがassert文を書き換え ている(assertion rewriting)
pytest --doctest-modules
def test_3の倍数のときはFizzを返す():
...
number
の 取りうる値は複数
3
6
9
def test_3の倍数のときはFizzを返す_3の場合():
...
def test_3の倍数のときはFizzを返す_6の場合():
...
@pytest.mark.parametrize
を使おう@pytest.mark.parametrize("number", [3, 6])
def test_3の倍数のときはFizzを返す(number):
assert fizzbuzz(number) == "Fizz"
$ pytest -v
test_fizzbuzz.py::test_3の倍数のときはFizzを返す[3] PASSED [ 40%]
test_fizzbuzz.py::test_3の倍数のときはFizzを返す[6] PASSED [ 60%]
個別に書いたのと同じ 結果が得られる
やや発展的話題(いまは分からなかったとしても大丈夫)
テストを書きたい関数 foo
処理A -> B -> Cの順で呼び出し
def foo():
a_func(42)
b_func("ham", "egg")
c_func()
推し:呼び出される処理を ニセモノ(=モック)に置き換え てテスト
全ての処理を通したテストも書ける
foo
関数で呼び出す各処理をテストにおいて 何もしない (=モック)に置き換える
モックは呼び出され方を記憶 している
@patch("test_with_mock.c_func")
@patch("test_with_mock.b_func")
@patch("test_with_mock.a_func")
def test_foo(a_func, b_func, c_func):
foo()
a_func.assert_called_once_with(42)
b_func.assert_called_once_with("ham", "egg")
c_func.assert_called_once_with()
処理の 呼び出しを検証
処理A,B,C自体はいずれも別途、徹底的に検証
時間のかかる関数(テストの実行時間が伸びる)
外部と通信する関数(通信エラーでテストが落ちうる)
出力が変わる関数(例:random)
test_ で始まるファイル・ test_ で始まる関数・ assert文
tips: パラメタ化 & モック
テストに慣れてきたらぜひ試してみてください!
テストを書くと、動作する? 間違えてない?という不安は 退屈 に変わる
関数の呼び出しと返り値を docstringに書くだけ で、doctestでテストできる!(一歩目)
(拡張された)assert文をはじめ、 テストコードが書きやすいpytest もぜひ!
パラメタ化テストを紹介
フィクスチャ
好きとか嫌いとかはいい、練習してテストを書けるようになるんだ
Practice, practice, practice!!!
『テスト駆動開発』(Kent Beck)
テストは不安を退屈に変える賢者の石だ。(第25章)
『ちょうぜつソフトウェア設計入門』(第6章)
sphinx-new-tab-link ・ hayasaka などを開発(感想・要望・スター待ってます!)
毎日1エントリ継続中 https://nikkie-ftnext.hatenablog.com/
テストコードは tests ディレクトリの下にまとめるのが一般的
.
├── hayasaka
└── tests
├── __init__.py
└── test_core.py
class Test_FizzBuzz数列と変換規則を扱うFizzBuzzクラス:
class Test_convertメソッドは数を文字列に変換する:
class Test_3の倍数のときは数の代わりにFizzに変換する:
def test_3を渡すと文字列Fizzを返す(self, fizzBuzz):
assert "Fizz" == fizzBuzz.convert(3)
TestCase
クラスを継承
assertXXX
メソッドでAssert
モックは unittest.mock から
拙ブログ unittest おすすめリンク集
Twitterで見かけた ちよ父 の画像
元は「トマトを食べるんだ」🍅
好きとか嫌いとかはいい。
— nikkie にっきー (@ftnext) April 29, 2023
テストを書くんだ
の亜種だ! https://t.co/f17s6awgNB