テスト・デバッグと暗号関連の要点
内容の確認日: 2026-10-04
この分野は『Python実践レシピ』の第 16 章 (テスト・3 問)・第 17 章 (デバッグ・2 問)・第 18 章 (暗号関連・1 問) に対応し、合わせて 40 問中 6 問が出題されます (pytest・traceback・cryptography の節は出題範囲外)。
unittest の実行順や mock.patch の置き換え先、logging のレベルによる絞り込みなど、「どう動くか」を知らないと答えにくい問題が多い分野です。実際にテストを書いて動かしてみると理解が早まります。
doctest
doctest は、docstring に書いた対話モードの実行例 (>>> の行と、その下の期待する出力) を実際に実行し、出力が一致するかを確かめます。ドキュメントの例が古くなっていないかを確認するのに向いています。
doctest.testmod() はモジュール内の docstring を調べ、すべて成功すれば何も表示せず、TestResults(failed=失敗数, attempted=実行数) を返します。失敗すると、期待した出力と実際の出力を表示します。コマンドラインからは python -m doctest ファイル名 -v で実行できます。
import doctestdef average(values): """平均を返す >>> average([1, 2, 3]) 2.0 >>> average([]) Traceback (most recent call last): ... ZeroDivisionError: division by zero """ return sum(values) / len(values)print(doctest.testmod())python3 main.py TestResults(failed=0, attempted=2)
例外が起きる例は、Traceback (most recent call last): の行と、最後の例外の行を書けば確かめられます (間のトレースバックは ... で省略できます)。
unittest
unittest.TestCase を継承したクラスを作り、test で始まるメソッドにテストを書きます。テストはメソッドごとに独立していて、1 つが失敗してもほかのテストは実行されます。
・setUp・tearDown: 各テストの前後に毎回呼ばれる
・setUpClass・tearDownClass: クラスのテストの前後に 1 回だけ呼ばれる (@classmethod を付ける)
・テストメソッドは名前の文字列順に実行される
・主な assert メソッド: assertEqual・assertTrue・assertIn・assertIsNone・assertAlmostEqual (浮動小数点数の比較)・assertRaises (例外が起きることを確かめる。with 文で使える)
・@unittest.skip("理由"): テストをスキップする
アサーションが失敗したテストは failures、テスト中に想定外の例外が起きたテストは errors として、別々に数えられます。python -m unittest で、test*.py のファイルを自動で探して実行できます。
import ioimport unittestclass TestPrice(unittest.TestCase): @classmethod def setUpClass(cls): print("setUpClass") def setUp(self): print("setUp", self._testMethodName) def test_tax(self): self.assertAlmostEqual(0.1 + 0.2, 0.3) def test_add(self): self.assertEqual(1 + 1, 3) def test_div(self): with self.assertRaises(ZeroDivisionError): 1 / 0 def test_error(self): {}["missing"]suite = unittest.TestLoader().loadTestsFromTestCase(TestPrice)result = unittest.TextTestRunner(stream=io.StringIO()).run(suite)print(result.testsRun, len(result.failures), len(result.errors), result.wasSuccessful())python3 main.py setUpClass setUp test_add setUp test_div setUp test_error setUp test_tax 4 1 1 False
モックを使ったテスト (unittest.mock)
Mock は、呼び出されると return_value を返し、呼ばれ方を記録するオブジェクトです。外部の API やデータベースなど、テストで本物を使いにくい部分を置き換えます。
・return_value: 戻り値。side_effect: 例外を送出させる、呼ぶたびに違う値を返す (リスト)、関数で戻り値を決める
・記録: called・call_count・call_args。確認: assert_called_once_with(...)・assert_called_with(...) (最後の呼び出し)
・MagicMock: __len__ などの特殊メソッドも設定できる。未設定の属性は自動で子のモックになる
・Mock(spec=クラス): そのクラスにない属性にアクセスすると AttributeError になり、名前の誤りに気付ける
mock.patch("モジュール.名前") は、with ブロックやデコレーターを付けた関数の間だけ、指定したオブジェクトをモックに置き換えます。置き換えるのは使われている場所の名前です。from a import f で取り込んだ f を b.py で使っているなら、b.f を置き換えます。クラスの属性は mock.patch.object(クラス, "名前") でも置き換えられます。
import randomfrom unittest import mockdef roll_twice(): return random.randint(1, 6) + random.randint(1, 6)with mock.patch("random.randint", side_effect=[6, 1]) as fake: print(roll_twice(), fake.call_count, fake.call_args)api = mock.Mock()api.get.return_value = {"status": "ok"}api.post.side_effect = TimeoutError("timeout")print(api.get("/health")["status"])api.get.assert_called_once_with("/health")try: api.post("/items")except TimeoutError as e: print("例外:", e)python3 main.py 7 2 call(1, 6) ok 例外: timeout
デバッグとログ (pdb・timeit・logging)
コードの止めたい位置に組み込み関数 breakpoint() を書くと、そこで対話的なデバッガー pdb が起動します。主なコマンドは n (次の行へ)・s (関数の中に入る)・c (次のブレークポイントまで続ける)・p 式 (値を表示)・l (ソースを表示)・q (終了) です。環境変数 PYTHONBREAKPOINT=0 で breakpoint() を無効にできます。
timeit.timeit(文, number=回数) は、文を指定した回数実行した合計時間を秒で返します。timeit.repeat はそれを繰り返してリストで返します。コマンドラインからは python -m timeit "式" で計測できます。
logging のレベルは低い順に DEBUG・INFO・WARNING・ERROR・CRITICAL で、既定では WARNING 以上だけが出力されます。ログはロガーのレベルとハンドラーのレベルの両方で絞り込まれます。ロガーは logging.getLogger(__name__) でモジュールごとに取得し、出力先 (ハンドラー) と形式 (フォーマッター) はアプリケーションの側で設定します。
import loggingimport syslogger = logging.getLogger("app.worker")logger.setLevel(logging.DEBUG)handler = logging.StreamHandler(sys.stdout)handler.setLevel(logging.INFO)handler.setFormatter(logging.Formatter("%(levelname)s %(name)s: %(message)s"))logger.addHandler(handler)logger.propagate = Falselogger.debug("詳細")logger.info("処理件数 %d 件", 42)try: 1 / 0except ZeroDivisionError: logger.error("計算に失敗しました")python3 main.py INFO app.worker: 処理件数 42 件 ERROR app.worker: 計算に失敗しました
メッセージの値は logger.info("%d 件", count) のように引数で渡すと、出力されないレベルのときに文字列の組み立てが省かれます。except 節の中で logger.exception(...) を使うと、ERROR のメッセージに加えてトレースバックも出力されます。
暗号関連 (secrets・hashlib)
パスワードの再設定用のトークンなど、推測されてはいけない値は secrets モジュールで作ります。random は再現性のある疑似乱数で、セキュリティの用途には使えません。
・secrets.token_hex(n): n バイトの乱数を 16 進数の文字列で (長さは 2n 文字)
・secrets.token_urlsafe(n): URL に使える文字の文字列で
・secrets.token_bytes(n): bytes で
・secrets.choice(シーケンス): 要素を 1 つ選ぶ
・secrets.compare_digest(a, b): タイミング攻撃を防ぐ方法で比べる
hashlib はハッシュ値を計算するモジュールで、sha256 などの関数にバイト列を渡します (文字列は TypeError)。update で少しずつデータを追加でき、まとめて渡した場合と同じ結果になります。hexdigest() は 16 進数の文字列、digest() は bytes です。
import hashlibimport secretstoken = secrets.token_hex(16)print(len(token), all(c in "0123456789abcdef" for c in token))h = hashlib.sha256()for chunk in [b"hello ", b"world"]: h.update(chunk)print(h.hexdigest() == hashlib.sha256("hello world".encode()).hexdigest())print(len(h.hexdigest()), len(h.digest()), hashlib.sha256(b"abc").hexdigest()[:16])python3 main.py 32 True True 64 32 ba7816bf8f01cfea
試験でよくある引っかけ
テストメソッドの実行順
定義した順ではなく、メソッド名の文字列順に実行されます。
setUpは各テストの前に毎回呼ばれます。failures と errors
アサーションの失敗は failures、想定外の例外は errors として数えられます。
浮動小数点数の比較
assertEqual(0.1 + 0.2, 0.3)は失敗します。assertAlmostEqualを使います。mock.patchの置き換え先定義された場所ではなく、使われている場所の名前を置き換えます。
side_effectのリスト呼ぶたびにリストの値を順に返し、要素が例外ならそれを送出します。
ロガーとハンドラーのレベル
ロガーが
DEBUGを受け付けても、ハンドラーがINFOならDEBUGのログは出力されません。randomをトークンに使わない推測されてはいけない値は
secretsで作ります。hashlibに文字列を渡すハッシュ関数は
bytesを受け取ります。文字列はencode()してから渡します。
問題で確認する
この分野の問題を、解説つきで読めます (登録不要)。
出典・参考
- Python エンジニア育成推進協会「Python 実践試験」(出題範囲)
- Python ドキュメント「doctest」
- Python ドキュメント「unittest」
- Python ドキュメント「unittest.mock」
- Python ドキュメント「logging」
- Python ドキュメント「secrets」
- Python ドキュメント「hashlib」
コード例は、実際に実行して結果を確認しています。