この記事で直せること
pytest でテストを実行したあとに、本番用の記録ファイル(心拍ファイルなど)が勝手に上書きされてしまう不具合を、原因の切り分け方と直し方で確認できます。「テストはスタブしてあるから大丈夫」と思っていたコードが、実は本物の関数を呼んでいた場合の見つけ方です。
症状
心拍の記録ファイルが、実際には guard が走っていない時刻で上書きされていました。
guard 2026-11-03T10:25 走った0 落ちた0 runner=local
この行は朝の通知とコックピットが読む記録です。実際の guard はその時刻に走っていないのに、この記録のせいで「クラウドの定時が空振りした」という偽の報告が出る所でした。
原因
pytest のエラーではなく、pytest の実行そのものが本番の記録を書き換えていました。原因は次のテストです。
tests/test_cloud_cron.py::test_secrets_missing_is_a_skip_not_a_failure
このテストは cc.main() を本当に走らせます。環境変数だけ None に差し替えて、鍵が無いときに rc=0 で見送るかどうかを確認するテストでした。ところがその main() の中から heartbeat_file() が呼ばれ、data/cloud_cron/last_guard.json を実際に上書きしていました。
pytest を実行したこと自体はエラーではなく成功していたので、エラー確認だけでは気づきにくい不具合でした。テスト結果を見るだけでなく、テスト実行の前後で本番のファイルに変化がないかを確認して初めて見つかりました。
直し方
heartbeat_file() と heartbeat() の先頭に、pytest の実行中かどうかを判定する処理を入れました。
import sys
def heartbeat_file():
if "pytest" in sys.modules:
return
# 本来の処理
...
def heartbeat():
if "pytest" in sys.modules:
return
# 本来の処理
...
これは以前に別の不具合(テストが本物の外部サービスを落としていた件)で使ったのと同じ型の不具合でした。そのときと同じ守り方を、今回はテスト側のスタブではなくコード側に持たせました。スタブ任せにすると、スタブをかけ忘れた経路から同じ事故が起きるためです。
直したあとは、実際に guard を本番で走らせて確認しました。
python -m cc main
5回走らせて5回とも成功し、心拍の記録ファイルが正しい時刻で残ることを確認しました。
type C:/Users/<あなたのユーザー名>/<プロジェクトのパス>/data/cloud_cron/last_guard.json
併せて直した設定
cloud-cron.yml の push で走る束の既定が verify になっていたものを signal に戻しました。verify は LINE を1通使う処理のため、push のたびに無料枠(月200通)を減らしていました。verify は手動で実行するときだけに限定しました。
再発防止
- テストから本番の記録・本番のログ・他人のアプリに届かないようにする仕組みは、テスト側のスタブではなくコード側に持つ。
- 「テストはスタブしてあるから大丈夫」という判断は、スタブをかけ忘れた経路があると崩れる。実際に過去の不具合では3本のテストでスタブが抜けていた。
- pytest 実行後は、テストの合否だけでなく本番側のファイルの更新時刻も確認する。
それでも駄目なときに次に見る場所
- 記録ファイルの更新時刻を確認する。
typeやGet-Contentで中身の時刻を見て、実際の実行時刻とずれていないか確認する。 - 該当するテストが外部に効く関数(
main()やファイル書き込み関数)を本当に呼んでいないか、テストのコードを読み直す。 sys.modulesでの判定を入れた関数が複数ある場合、判定を入れ忘れている関数がないか、同じ名前の関数(heartbeat系など)を全部洗い出す。- CI の設定ファイル(
cloud-cron.ymlなど)で、push のたびに通知を使う束が既定になっていないか確認する。
追記(2026-11-03 16:00)
別件として、掲示板の採取の定時を 17:40 の1回から 21:30 / 23:30 / 05:30 / 07:40 の4回に増やしました。本人から「17時40分じゃ絶対に取れない」という指摘があったためです。scripts/learn_catchup.py 側で「直近18時間以内に採取済みなら何もしない」という条件を入れて、1日1回に抑えています。今回は登録自体は通りましたが(前回は権限分類器に止められていました)、PC の電源が入っていない間は採取できないという限界は変わっていません。

CSVの文字化けと列ずれが止まる場所をPythonのコードで具体的に潰す手順
n8nで複数のサービスをつなぐときの役割分担と、止まらない作り方
note自動投稿で記号が残る問題を正規表現の処理順序から直す方法