[{"rendered_body":"\u003cp data-sourcepos=\"1:1-1:122\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4373344%2F7c525503-0523-4213-87fa-b85dac912105.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=077a7858f85e5b339fa7d79935d5e5ab\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4373344%2F7c525503-0523-4213-87fa-b85dac912105.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=077a7858f85e5b339fa7d79935d5e5ab\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4373344%2F7c525503-0523-4213-87fa-b85dac912105.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=f73adcc8d2052a4132d033ab7bd2523d 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4373344/7c525503-0523-4213-87fa-b85dac912105.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"4:1-4:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"6:1-6:590\"\u003eReactアプリが大きくなってくると、「prop drilling」という問題に直面します。コンポーネントツリーの深い場所にあるコンポーネントにstateを渡すために、3〜4層を経由してpropsをバケツリレーしていく、あの問題です。よく使われる解決策は \u003ccode\u003euseContext\u003c/code\u003e ですが、Contextにはパフォーマンス面で大きな欠点があります。stateが変わるたびに、\u003cstrong\u003eそのstateを使っていない子コンポーネントまで含めて、すべてが再レンダリングされてしまう\u003c/strong\u003eのです。\u003c/p\u003e\n\u003cp data-sourcepos=\"8:1-8:436\"\u003eたとえば、サイドバー・ヘッダー・データテーブルが同じContextを参照しているダッシュボードを想像してください。ヘッダーの小さな通知バッジを更新しただけで、サイドバーもデータテーブルも一緒に再レンダリングされます。複雑なアプリでは、特にローエンドのデバイスでこれが明らかなカクつきの原因になります。\u003c/p\u003e\n\u003cp data-sourcepos=\"10:1-10:258\"\u003eZustandはこの2つの問題をまとめて解決します。Providerで囲む必要のないグローバルstateと、\u003cstrong\u003e変更されたstateを実際に使っているコンポーネントだけ\u003c/strong\u003eを再レンダリングする仕組みを提供します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"12:1-14:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003enpm \u003cspan class=\"nb\"\u003einstall \u003c/span\u003ezustand\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"16:1-17:0\"\u003e\n\u003ch2 data-sourcepos=\"18:1-18:24\"\u003e\n\u003cspan id=\"基本的な使い方\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%BF%E3%81%84%E6%96%B9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e基本的な使い方\u003c/h2\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"20:1-29:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003ecreate\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euseCounterStore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"na\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eincrement\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecount\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"p\"\u003e})),\u003c/span\u003e\n  \u003cspan class=\"na\"\u003edecrement\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecount\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"p\"\u003e})),\u003c/span\u003e\n  \u003cspan class=\"na\"\u003ereset\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"31:1-31:37\"\u003eコンポーネントで使う場合:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"33:1-45:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eCounter\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ecount\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseCounterStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eincrement\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseCounterStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eincrement\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n  \u003cspan class=\"k\"\u003ereturn \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ediv\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003ecount\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e \u003cspan class=\"na\"\u003eonClick\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eincrement\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e+1\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ediv\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"47:1-47:47\"\u003eまず覚えておくべき3つのポイント:\u003c/p\u003e\n\u003ctable data-sourcepos=\"49:1-53:86\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"49:1-49:12\"\u003e\n\u003cth data-sourcepos=\"49:2-49:2\"\u003e\u003c/th\u003e\n\u003cth data-sourcepos=\"49:4-49:11\"\u003e説明\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"51:1-51:87\"\u003e\n\u003ctd data-sourcepos=\"51:2-51:11\"\u003e\u003ccode\u003ecreate\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-sourcepos=\"51:13-51:86\"\u003estoreを作成する。関数を受け取り、カスタムhookを返す\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"52:1-52:97\"\u003e\n\u003ctd data-sourcepos=\"52:2-52:8\"\u003e\u003ccode\u003eset\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-sourcepos=\"52:10-52:96\"\u003estateを更新する。現在のstateにマージされる（上書きではない）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"53:1-53:86\"\u003e\n\u003ctd data-sourcepos=\"53:2-53:13\"\u003e\u003ccode\u003eselector\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-sourcepos=\"53:15-53:85\"\u003ehookに渡して、必要なstateだけを選択するための関数\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr data-sourcepos=\"55:1-56:0\"\u003e\n\u003ch2 data-sourcepos=\"57:1-57:72\"\u003e\n\u003cspan id=\"ポイント1-セレクタで不要な再レンダリングを防ぐ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%881-%E3%82%BB%E3%83%AC%E3%82%AF%E3%82%BF%E3%81%A7%E4%B8%8D%E8%A6%81%E3%81%AA%E5%86%8D%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%92%E9%98%B2%E3%81%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eポイント1: セレクタで不要な再レンダリングを防ぐ\u003c/h2\u003e\n\u003cp data-sourcepos=\"59:1-59:210\"\u003e\u003ccode\u003euseContext\u003c/code\u003e との最大の違いがここです。必要なstateだけを選択することで、他のstateが変わったときにコンポーネントが\u003cstrong\u003e再レンダリングされなくなります\u003c/strong\u003e。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"61:1-67:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 良くない例: store全体を取得 → 何かが変わるたびに再レンダリング\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003estore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 良い例: usernameが変わったときだけ再レンダリング\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eusername\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"69:1-69:94\"\u003e複数の値を同時に取得したいとき、こう書きたくなるかもしれません:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"71:1-75:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 注意: レンダリングのたびに新しいオブジェクトを生成するため\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// Zustandがstateが常に変わったと判断してしまい、再レンダリングが続く\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eemail\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003eemail\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eemail\u003c/span\u003e \u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"77:1-77:410\"\u003e問題はここにあります。セレクタが実行されるたびに、\u003ccode\u003eusername\u003c/code\u003e も \u003ccode\u003eemail\u003c/code\u003e も変わっていないのに\u003cstrong\u003e新しいオブジェクト\u003c/strong\u003eが返されます。Zustandは \u003ccode\u003e===\u003c/code\u003e で比較するため、参照が異なる2つのオブジェクトは中身が同じでも「変わった」とみなされます。\u003ccode\u003euseShallow\u003c/code\u003e を使えば、参照ではなくキーごとに比較できます:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"79:1-86:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003euseShallow\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand/react/shallow\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// useShallowはキーごとに比較するため、usernameかemailが実際に変わったときだけ再レンダリング\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eemail\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003euseShallow\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eusername\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003eemail\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eemail\u003c/span\u003e \u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"88:1-89:0\"\u003e\n\u003ch2 data-sourcepos=\"90:1-90:63\"\u003e\n\u003cspan id=\"ポイント2-非同期アクションでapiを呼び出す\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%882-%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A7api%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eポイント2: 非同期アクションでAPIを呼び出す\u003c/h2\u003e\n\u003cp data-sourcepos=\"92:1-92:121\"\u003e\u003ccode\u003eset\u003c/code\u003e は通常の非同期関数の中でそのまま使えます。ミドルウェアや追加の設定は不要です:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"94:1-110:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euseUserStore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eloading\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\n  \u003cspan class=\"na\"\u003efetchUser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"k\"\u003easync \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eloading\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n    \u003cspan class=\"k\"\u003etry\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"nf\"\u003efetch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e`/api/users/\u003c/span\u003e\u003cspan class=\"p\"\u003e${\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e`\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003ethen\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n      \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003eloading\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003ecatch \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003emessage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003eloading\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"112:1-112:37\"\u003eコンポーネントで使う場合:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"114:1-127:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eUserProfile\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003eid\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euser\u003c/span\u003e      \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eloading\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eloading\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003efetchUser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003efetchUser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// fetchUserはstoreで定義されているため安定した参照を持つ\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// 依存配列に入れてもESLintのルールに従いつつ、無限ループにもならない\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003euseEffect\u003c/span\u003e\u003cspan class=\"p\"\u003e(()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nf\"\u003efetchUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003efetchUser\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\n  \u003cspan class=\"k\"\u003eif \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eloading\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e読み込み中...\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"nx\"\u003ename\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"129:1-130:0\"\u003e\n\u003ch2 data-sourcepos=\"131:1-131:63\"\u003e\n\u003cspan id=\"ポイント3-persistでlocalstorageにstateを保存する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%883-persist%E3%81%A7localstorage%E3%81%ABstate%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eポイント3: persistでlocalStorageにstateを保存する\u003c/h2\u003e\n\u003cp data-sourcepos=\"133:1-133:183\"\u003e\u003ccode\u003epersist\u003c/code\u003e ミドルウェアを使えば、ページをリロードしてもstateが消えません。テーマ、言語設定、カート、ユーザー設定などに便利です。\u003c/p\u003e\n\u003cblockquote data-sourcepos=\"135:1-135:257\"\u003e\n\u003cp data-sourcepos=\"135:3-135:257\"\u003e\u003cstrong\u003eセキュリティに関する注意:\u003c/strong\u003e 認証トークンをlocalStorageに保存するのは避けましょう。XSSによる盗難リスクがあります。トークンはサーバー側でhttpOnly Cookieを使って管理するのが適切です。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"137:1-154:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003ecreate\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003epersist\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand/middleware\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euseSettingsStore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n      \u003cspan class=\"na\"\u003etheme\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003elight\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n      \u003cspan class=\"na\"\u003elanguage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eja\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n      \u003cspan class=\"na\"\u003esetTheme\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e    \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etheme\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e    \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003etheme\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n      \u003cspan class=\"na\"\u003esetLanguage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elanguage\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003elanguage\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eapp-settings\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"c1\"\u003e// localStorageのキー名\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"156:1-156:89\"\u003e特定のフィールドだけを保存したい場合は、\u003ccode\u003epartialize\u003c/code\u003e を使います:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"158:1-170:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nf\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n    \u003cspan class=\"na\"\u003etheme\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003elight\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"na\"\u003etempData\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// 他のstate\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eapp-settings\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"na\"\u003epartialize\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003etheme\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etheme\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e \u003cspan class=\"c1\"\u003e// themeだけ保存、tempDataは除外\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"172:1-173:0\"\u003e\n\u003ch2 data-sourcepos=\"174:1-174:74\"\u003e\n\u003cspan id=\"ポイント4-大規模なstoreの整理にはスライスパターン\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%884-%E5%A4%A7%E8%A6%8F%E6%A8%A1%E3%81%AAstore%E3%81%AE%E6%95%B4%E7%90%86%E3%81%AB%E3%81%AF%E3%82%B9%E3%83%A9%E3%82%A4%E3%82%B9%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eポイント4: 大規模なstoreの整理にはスライスパターン\u003c/h2\u003e\n\u003cp data-sourcepos=\"176:1-176:175\"\u003eアプリが大きくなってきたら、すべてを1つのstoreに詰め込むのはやめましょう。複数の「スライス」に分割してから合体させます:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"178:1-201:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// store/userSlice.js\u003c/span\u003e\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ecreateUserSlice\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n  \u003cspan class=\"na\"\u003esetUser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003euser\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n  \u003cspan class=\"na\"\u003elogout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e  \u003cspan class=\"p\"\u003e()\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// store/cartSlice.js\u003c/span\u003e\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ecreateCartSlice\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[],\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eaddItem\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e    \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[...\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e})),\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eremoveItem\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003efilter\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e!==\u003c/span\u003e \u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e})),\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eclearCart\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e  \u003cspan class=\"p\"\u003e()\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// store/index.js: すべてをまとめる\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euseStore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"nf\"\u003ecreateUserSlice\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"nf\"\u003ecreateCartSlice\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"k\"\u003edefault\u003c/span\u003e \u003cspan class=\"nx\"\u003euseStore\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"203:1-203:192\"\u003e合体後は、すべてのスライスのstateとactionが \u003ccode\u003euseStore\u003c/code\u003e に集約されます。どのスライスに属しているかを意識せず、通常通りセレクタで使えます:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"205:1-222:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eHeader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// userSliceのstate\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euser\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003elogout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elogout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// cartSliceのstate: 同じuseStore、異なるセレクタ\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eitemCount\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseStore\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n  \u003cspan class=\"k\"\u003ereturn \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eようこそ、\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e?.\u003c/span\u003e\u003cspan class=\"nx\"\u003ename\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003eさん\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eカート: \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eitemCount\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e件\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e \u003cspan class=\"na\"\u003eonClick\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003elogout\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eログアウト\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"224:1-224:262\"\u003eセレクタを別ファイルにまとめて再利用するチームも多くあります。複数のコンポーネントで同じセレクタを書き直す手間が省け、フィールド名を変更する際のリファクタリングも楽になります:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"226:1-235:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// store/selectors.js\u003c/span\u003e\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eselectUser\u003c/span\u003e      \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eselectCartItems\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eitems\u003c/span\u003e\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eselectItemCount\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eitems\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// コンポーネントで使う: すっきりしてプロジェクト全体で一貫性が保てる\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euser\u003c/span\u003e      \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseStore\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eselectUser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eitemCount\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseStore\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eselectItemCount\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"237:1-238:0\"\u003e\n\u003ch2 data-sourcepos=\"239:1-239:62\"\u003e\n\u003cspan id=\"ポイント5-コンポーネントの外でstateを使う\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%885-%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E5%A4%96%E3%81%A7state%E3%82%92%E4%BD%BF%E3%81%86\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eポイント5: コンポーネントの外でstateを使う\u003c/h2\u003e\n\u003cp data-sourcepos=\"241:1-241:208\"\u003eZustandはコンポーネントやhookの外でもstateの読み書きができます。axiosのインターセプター、WebSocketハンドラー、ユーティリティファイルなどで役立ちます:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"243:1-249:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 現在のstateを読み取る\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ecurrentUser\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetState\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// コンポーネントの外からstateを更新する\u003c/span\u003e\n\u003cspan class=\"nx\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003esetState\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"251:1-251:93\"\u003e\u003cstrong\u003esubscribe\u003c/strong\u003e でstateの変化を監視できます。シグネチャは2種類あります:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"253:1-259:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 形式1: subscribe(listener) — store全体の変化を監視\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// ミドルウェアなしでそのまま使える\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eunsub\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003esubscribe\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eStore changed:\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"261:1-261:259\"\u003eセレクタ付きの形式2は、storeを作成するときに \u003ccode\u003esubscribeWithSelector\u003c/code\u003e ミドルウェアを追加する必要があります。追加しない場合、エラーが出ることもなくlistenerが呼ばれないので注意してください:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"263:1-283:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003esubscribeWithSelector\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand/middleware\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// ミドルウェアを追加してstoreを定義\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euseUserStore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003esubscribeWithSelector\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n    \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"na\"\u003esetUser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003euser\u003c/span\u003e \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 形式2: subscribe(selector, listener) — 指定したstateが変化したときだけ発火\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// selector: 監視したいstateを選択\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// listener: (新しい値, 前の値) を受け取る\u003c/span\u003e\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eunsub\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003euseUserStore\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003esubscribe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e          \u003cspan class=\"c1\"\u003e// selector: userだけ監視\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eprevUser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e           \u003cspan class=\"c1\"\u003e// listener: userが変わったときに実行\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eUser changed from\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eprevUser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eto\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"285:1-288:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// メモリリークを防ぐため、不要になったらsubscribeを解除する\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eunsub\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"290:1-291:0\"\u003e\n\u003ch2 data-sourcepos=\"292:1-292:64\"\u003e\n\u003cspan id=\"ポイント6-深くネストしたstateにはimmerを使う\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%886-%E6%B7%B1%E3%81%8F%E3%83%8D%E3%82%B9%E3%83%88%E3%81%97%E3%81%9Fstate%E3%81%AB%E3%81%AFimmer%E3%82%92%E4%BD%BF%E3%81%86\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eポイント6: 深くネストしたstateにはImmerを使う\u003c/h2\u003e\n\u003cp data-sourcepos=\"294:1-294:363\"\u003e深くネストしたオブジェクトを更新するとき、イミュータブルなコードは非常に長くなり、途中のspreadを忘れてデータが消えるバグも発生しやすいです。\u003ccode\u003eimmer\u003c/code\u003e ミドルウェアを使えば、直接「変更」するような書き方ができ、内部ではイミュータビリティが保証されます:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"296:1-298:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003enpm \u003cspan class=\"nb\"\u003einstall \u003c/span\u003eimmer\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"300:1-321:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003ecreate\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eimmer\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ezustand/middleware/immer\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003euseStore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003eimmer\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kd\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n    \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"na\"\u003eprofile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e''\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003eavatar\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e''\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n      \u003cspan class=\"na\"\u003eaddress\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ecity\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e''\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003edistrict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e''\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// 直接変更するように書くだけ — あとはImmerが処理してくれる\u003c/span\u003e\n    \u003cspan class=\"na\"\u003esetCity\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ecity\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddress\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecity\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ecity\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n\n    \u003cspan class=\"na\"\u003esetName\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eprofile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ename\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}),\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"323:1-323:33\"\u003eImmerなしの場合との比較:\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"js\" data-sourcepos=\"325:1-333:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 良くない例: 冗長で、spreadを忘れると他のフィールドのデータが消える\u003c/span\u003e\n\u003cspan class=\"nx\"\u003esetCity\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ecity\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"na\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eaddress\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"nx\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddress\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ecity\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"335:1-336:0\"\u003e\n\u003ch2 data-sourcepos=\"337:1-337:12\"\u003e\n\u003cspan id=\"まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eまとめ\u003c/h2\u003e\n\u003ctable data-sourcepos=\"339:1-347:72\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"339:1-339:37\"\u003e\n\u003cth data-sourcepos=\"339:2-339:21\"\u003eユースケース\u003c/th\u003e\n\u003cth data-sourcepos=\"339:23-339:36\"\u003e使うもの\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"341:1-341:37\"\u003e\n\u003ctd data-sourcepos=\"341:2-341:17\"\u003e基本のstore\u003c/td\u003e\n\u003ctd data-sourcepos=\"341:19-341:36\"\u003e\n\u003ccode\u003ecreate\u003c/code\u003e + \u003ccode\u003eset\u003c/code\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"342:1-342:73\"\u003e\n\u003ctd data-sourcepos=\"342:2-342:42\"\u003e不要な再レンダリングを防ぐ\u003c/td\u003e\n\u003ctd data-sourcepos=\"342:44-342:72\"\u003eセレクタ + \u003ccode\u003euseShallow\u003c/code\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"343:1-343:66\"\u003e\n\u003ctd data-sourcepos=\"343:2-343:29\"\u003estoreの中でAPIを呼ぶ\u003c/td\u003e\n\u003ctd data-sourcepos=\"343:31-343:65\"\u003e通常の非同期アクション\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"344:1-344:64\"\u003e\n\u003ctd data-sourcepos=\"344:2-344:32\"\u003elocalStorageにstateを保存\u003c/td\u003e\n\u003ctd data-sourcepos=\"344:34-344:63\"\u003e\n\u003ccode\u003epersist\u003c/code\u003e ミドルウェア\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"345:1-345:70\"\u003e\n\u003ctd data-sourcepos=\"345:2-345:42\"\u003e大規模アプリ、複数ドメイン\u003c/td\u003e\n\u003ctd data-sourcepos=\"345:44-345:69\"\u003eスライスパターン\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"346:1-346:83\"\u003e\n\u003ctd data-sourcepos=\"346:2-346:36\"\u003eコンポーネント外で使う\u003c/td\u003e\n\u003ctd data-sourcepos=\"346:38-346:82\"\u003e\n\u003ccode\u003egetState()\u003c/code\u003e / \u003ccode\u003esetState()\u003c/code\u003e / \u003ccode\u003esubscribe()\u003c/code\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"347:1-347:72\"\u003e\n\u003ctd data-sourcepos=\"347:2-347:42\"\u003e深くネストしたオブジェクト\u003c/td\u003e\n\u003ctd data-sourcepos=\"347:44-347:71\"\u003e\n\u003ccode\u003eimmer\u003c/code\u003e ミドルウェア\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"349:1-349:584\"\u003eZustandにはstoreの構成に「唯一の正解」はありません。シンプルに始めて、必要に応じて拡張できる柔軟さが魅力です。ただし、常にZustandが必要なわけではありません。コンポーネントが1〜2つ程度でstateを共有するだけなら、\u003ccode\u003euseState\u003c/code\u003e + propsや \u003ccode\u003euseContext\u003c/code\u003e で十分です。Zustandが真価を発揮するのは、コンポーネントツリーの中で互いに関係のない複数の場所から同じstateを参照する場合——そこが、Zustandが本当に解決してくれる問題です。\u003c/p\u003e\n","body":"![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4373344/7c525503-0523-4213-87fa-b85dac912105.png)\n\n\n## はじめに\n\nReactアプリが大きくなってくると、「prop drilling」という問題に直面します。コンポーネントツリーの深い場所にあるコンポーネントにstateを渡すために、3〜4層を経由してpropsをバケツリレーしていく、あの問題です。よく使われる解決策は `useContext` ですが、Contextにはパフォーマンス面で大きな欠点があります。stateが変わるたびに、**そのstateを使っていない子コンポーネントまで含めて、すべてが再レンダリングされてしまう**のです。\n\nたとえば、サイドバー・ヘッダー・データテーブルが同じContextを参照しているダッシュボードを想像してください。ヘッダーの小さな通知バッジを更新しただけで、サイドバーもデータテーブルも一緒に再レンダリングされます。複雑なアプリでは、特にローエンドのデバイスでこれが明らかなカクつきの原因になります。\n\nZustandはこの2つの問題をまとめて解決します。Providerで囲む必要のないグローバルstateと、**変更されたstateを実際に使っているコンポーネントだけ**を再レンダリングする仕組みを提供します。\n\n```bash\nnpm install zustand\n```\n\n---\n\n## 基本的な使い方\n\n```js\nimport { create } from 'zustand'\n\nconst useCounterStore = create((set) =\u003e ({\n  count: 0,\n  increment: () =\u003e set((state) =\u003e ({ count: state.count + 1 })),\n  decrement: () =\u003e set((state) =\u003e ({ count: state.count - 1 })),\n  reset:     () =\u003e set({ count: 0 }),\n}))\n```\n\nコンポーネントで使う場合:\n\n```jsx\nfunction Counter() {\n  const count     = useCounterStore((s) =\u003e s.count)\n  const increment = useCounterStore((s) =\u003e s.increment)\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003e{count}\u003c/p\u003e\n      \u003cbutton onClick={increment}\u003e+1\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nまず覚えておくべき3つのポイント:\n\n| | 説明 |\n|---|---|\n| `create` | storeを作成する。関数を受け取り、カスタムhookを返す |\n| `set` | stateを更新する。現在のstateにマージされる（上書きではない） |\n| `selector` | hookに渡して、必要なstateだけを選択するための関数 |\n\n---\n\n## ポイント1: セレクタで不要な再レンダリングを防ぐ\n\n`useContext` との最大の違いがここです。必要なstateだけを選択することで、他のstateが変わったときにコンポーネントが**再レンダリングされなくなります**。\n\n```js\n// 良くない例: store全体を取得 → 何かが変わるたびに再レンダリング\nconst store = useUserStore()\n\n// 良い例: usernameが変わったときだけ再レンダリング\nconst username = useUserStore((s) =\u003e s.username)\n```\n\n複数の値を同時に取得したいとき、こう書きたくなるかもしれません:\n\n```js\n// 注意: レンダリングのたびに新しいオブジェクトを生成するため\n// Zustandがstateが常に変わったと判断してしまい、再レンダリングが続く\nconst { username, email } = useUserStore((s) =\u003e ({ username: s.username, email: s.email }))\n```\n\n問題はここにあります。セレクタが実行されるたびに、`username` も `email` も変わっていないのに**新しいオブジェクト**が返されます。Zustandは `===` で比較するため、参照が異なる2つのオブジェクトは中身が同じでも「変わった」とみなされます。`useShallow` を使えば、参照ではなくキーごとに比較できます:\n\n```js\nimport { useShallow } from 'zustand/react/shallow'\n\n// useShallowはキーごとに比較するため、usernameかemailが実際に変わったときだけ再レンダリング\nconst { username, email } = useUserStore(\n  useShallow((s) =\u003e ({ username: s.username, email: s.email }))\n)\n```\n\n---\n\n## ポイント2: 非同期アクションでAPIを呼び出す\n\n`set` は通常の非同期関数の中でそのまま使えます。ミドルウェアや追加の設定は不要です:\n\n```js\nconst useUserStore = create((set) =\u003e ({\n  user: null,\n  loading: false,\n  error: null,\n\n  fetchUser: async (id) =\u003e {\n    set({ loading: true, error: null })\n    try {\n      const data = await fetch(`/api/users/${id}`).then((r) =\u003e r.json())\n      set({ user: data, loading: false })\n    } catch (err) {\n      set({ error: err.message, loading: false })\n    }\n  },\n}))\n```\n\nコンポーネントで使う場合:\n\n```jsx\nfunction UserProfile({ id }) {\n  const user      = useUserStore((s) =\u003e s.user)\n  const loading   = useUserStore((s) =\u003e s.loading)\n  const fetchUser = useUserStore((s) =\u003e s.fetchUser)\n\n  // fetchUserはstoreで定義されているため安定した参照を持つ\n  // 依存配列に入れてもESLintのルールに従いつつ、無限ループにもならない\n  useEffect(() =\u003e { fetchUser(id) }, [id, fetchUser])\n\n  if (loading) return \u003cp\u003e読み込み中...\u003c/p\u003e\n  return \u003cp\u003e{user?.name}\u003c/p\u003e\n}\n```\n\n---\n\n## ポイント3: persistでlocalStorageにstateを保存する\n\n`persist` ミドルウェアを使えば、ページをリロードしてもstateが消えません。テーマ、言語設定、カート、ユーザー設定などに便利です。\n\n\u003e **セキュリティに関する注意:** 認証トークンをlocalStorageに保存するのは避けましょう。XSSによる盗難リスクがあります。トークンはサーバー側でhttpOnly Cookieを使って管理するのが適切です。\n\n```js\nimport { create } from 'zustand'\nimport { persist } from 'zustand/middleware'\n\nconst useSettingsStore = create(\n  persist(\n    (set) =\u003e ({\n      theme: 'light',\n      language: 'ja',\n      setTheme:    (theme)    =\u003e set({ theme }),\n      setLanguage: (language) =\u003e set({ language }),\n    }),\n    {\n      name: 'app-settings', // localStorageのキー名\n    }\n  )\n)\n```\n\n特定のフィールドだけを保存したい場合は、`partialize` を使います:\n\n```js\npersist(\n  (set) =\u003e ({\n    theme: 'light',\n    tempData: null,\n    // 他のstate\n  }),\n  {\n    name: 'app-settings',\n    partialize: (state) =\u003e ({ theme: state.theme }), // themeだけ保存、tempDataは除外\n  }\n)\n```\n\n---\n\n## ポイント4: 大規模なstoreの整理にはスライスパターン\n\nアプリが大きくなってきたら、すべてを1つのstoreに詰め込むのはやめましょう。複数の「スライス」に分割してから合体させます:\n\n```js\n// store/userSlice.js\nexport const createUserSlice = (set) =\u003e ({\n  user: null,\n  setUser: (user) =\u003e set({ user }),\n  logout:  ()     =\u003e set({ user: null }),\n})\n\n// store/cartSlice.js\nexport const createCartSlice = (set) =\u003e ({\n  items: [],\n  addItem:    (item) =\u003e set((s) =\u003e ({ items: [...s.items, item] })),\n  removeItem: (id)   =\u003e set((s) =\u003e ({ items: s.items.filter((i) =\u003e i.id !== id) })),\n  clearCart:  ()     =\u003e set({ items: [] }),\n})\n\n// store/index.js: すべてをまとめる\nconst useStore = create((set, get) =\u003e ({\n  ...createUserSlice(set, get),\n  ...createCartSlice(set, get),\n}))\n\nexport default useStore\n```\n\n合体後は、すべてのスライスのstateとactionが `useStore` に集約されます。どのスライスに属しているかを意識せず、通常通りセレクタで使えます:\n\n```jsx\nfunction Header() {\n  // userSliceのstate\n  const user   = useStore((s) =\u003e s.user)\n  const logout = useStore((s) =\u003e s.logout)\n\n  // cartSliceのstate: 同じuseStore、異なるセレクタ\n  const itemCount = useStore((s) =\u003e s.items.length)\n\n  return (\n    \u003cheader\u003e\n      \u003cspan\u003eようこそ、{user?.name}さん\u003c/span\u003e\n      \u003cspan\u003eカート: {itemCount}件\u003c/span\u003e\n      \u003cbutton onClick={logout}\u003eログアウト\u003c/button\u003e\n    \u003c/header\u003e\n  )\n}\n```\n\nセレクタを別ファイルにまとめて再利用するチームも多くあります。複数のコンポーネントで同じセレクタを書き直す手間が省け、フィールド名を変更する際のリファクタリングも楽になります:\n\n```js\n// store/selectors.js\nexport const selectUser      = (s) =\u003e s.user\nexport const selectCartItems = (s) =\u003e s.items\nexport const selectItemCount = (s) =\u003e s.items.length\n\n// コンポーネントで使う: すっきりしてプロジェクト全体で一貫性が保てる\nconst user      = useStore(selectUser)\nconst itemCount = useStore(selectItemCount)\n```\n\n---\n\n## ポイント5: コンポーネントの外でstateを使う\n\nZustandはコンポーネントやhookの外でもstateの読み書きができます。axiosのインターセプター、WebSocketハンドラー、ユーティリティファイルなどで役立ちます:\n\n```js\n// 現在のstateを読み取る\nconst currentUser = useUserStore.getState().user\n\n// コンポーネントの外からstateを更新する\nuseUserStore.setState({ user: null })\n```\n\n**subscribe** でstateの変化を監視できます。シグネチャは2種類あります:\n\n```js\n// 形式1: subscribe(listener) — store全体の変化を監視\n// ミドルウェアなしでそのまま使える\nconst unsub = useUserStore.subscribe((state) =\u003e {\n  console.log('Store changed:', state)\n})\n```\n\nセレクタ付きの形式2は、storeを作成するときに `subscribeWithSelector` ミドルウェアを追加する必要があります。追加しない場合、エラーが出ることもなくlistenerが呼ばれないので注意してください:\n\n```js\nimport { subscribeWithSelector } from 'zustand/middleware'\n\n// ミドルウェアを追加してstoreを定義\nconst useUserStore = create(\n  subscribeWithSelector((set) =\u003e ({\n    user: null,\n    setUser: (user) =\u003e set({ user }),\n  }))\n)\n\n// 形式2: subscribe(selector, listener) — 指定したstateが変化したときだけ発火\n// selector: 監視したいstateを選択\n// listener: (新しい値, 前の値) を受け取る\nconst unsub = useUserStore.subscribe(\n  (state) =\u003e state.user,          // selector: userだけ監視\n  (user, prevUser) =\u003e {           // listener: userが変わったときに実行\n    console.log('User changed from', prevUser, 'to', user)\n  }\n)\n```\n\n```js\n// メモリリークを防ぐため、不要になったらsubscribeを解除する\nunsub()\n```\n\n---\n\n## ポイント6: 深くネストしたstateにはImmerを使う\n\n深くネストしたオブジェクトを更新するとき、イミュータブルなコードは非常に長くなり、途中のspreadを忘れてデータが消えるバグも発生しやすいです。`immer` ミドルウェアを使えば、直接「変更」するような書き方ができ、内部ではイミュータビリティが保証されます:\n\n```bash\nnpm install immer\n```\n\n```js\nimport { create } from 'zustand'\nimport { immer } from 'zustand/middleware/immer'\n\nconst useStore = create(\n  immer((set) =\u003e ({\n    user: {\n      profile: { name: '', avatar: '' },\n      address: { city: '', district: '' },\n    },\n\n    // 直接変更するように書くだけ — あとはImmerが処理してくれる\n    setCity: (city) =\u003e set((state) =\u003e {\n      state.user.address.city = city\n    }),\n\n    setName: (name) =\u003e set((state) =\u003e {\n      state.user.profile.name = name\n    }),\n  }))\n)\n```\n\nImmerなしの場合との比較:\n\n```js\n// 良くない例: 冗長で、spreadを忘れると他のフィールドのデータが消える\nsetCity: (city) =\u003e set((state) =\u003e ({\n  user: {\n    ...state.user,\n    address: { ...state.user.address, city },\n  },\n}))\n```\n\n---\n\n## まとめ\n\n| ユースケース | 使うもの |\n|---|---|\n| 基本のstore | `create` + `set` |\n| 不要な再レンダリングを防ぐ | セレクタ + `useShallow` |\n| storeの中でAPIを呼ぶ | 通常の非同期アクション |\n| localStorageにstateを保存 | `persist` ミドルウェア |\n| 大規模アプリ、複数ドメイン | スライスパターン |\n| コンポーネント外で使う | `getState()` / `setState()` / `subscribe()` |\n| 深くネストしたオブジェクト | `immer` ミドルウェア |\n\nZustandにはstoreの構成に「唯一の正解」はありません。シンプルに始めて、必要に応じて拡張できる柔軟さが魅力です。ただし、常にZustandが必要なわけではありません。コンポーネントが1〜2つ程度でstateを共有するだけなら、`useState` + propsや `useContext` で十分です。Zustandが真価を発揮するのは、コンポーネントツリーの中で互いに関係のない複数の場所から同じstateを参照する場合——そこが、Zustandが本当に解決してくれる問題です。\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:27:25+09:00","group":null,"id":"95b6c49cfdd148898689","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"JavaScript","versions":[]},{"name":"フロントエンド","versions":[]},{"name":"React","versions":[]},{"name":"Next.js","versions":[]},{"name":"zustand","versions":[]}],"title":"Zustandを使って気づいた、もっと早く知りたかった7つのこと","updated_at":"2026-05-06T11:27:25+09:00","url":"https://qiita.com/nhatcaofedev/items/95b6c49cfdd148898689","user":{"description":"","facebook_id":"https://www.facebook.com/Nhatinsane","followees_count":4,"followers_count":0,"github_login_name":null,"id":"nhatcaofedev","items_count":6,"linkedin_id":"","location":"","name":"Nhat Cao","organization":"TOMOSIA VIET NAM Co., Ltd","permanent_id":4373344,"profile_image_url":"https://lh3.googleusercontent.com/a/ACg8ocJgWl-MZCl2H2IhEiDX2p_Ezb3jEe4bxdTI3Q2T0DqE8qZlyXQ=s96-c","team_only":false,"twitter_screen_name":null,"website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":"tomosia","slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:19\"\u003e\n\u003cspan id=\"-学習時間\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#-%E5%AD%A6%E7%BF%92%E6%99%82%E9%96%93\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e🕒 学習時間\u003c/h1\u003e\n\u003cp data-sourcepos=\"2:1-2:11\"\u003e10:30~11:30\u003c/p\u003e\n\u003ch1 data-sourcepos=\"4:1-4:38\"\u003e\n\u003cspan id=\"-実施した学習内容\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#-%E5%AE%9F%E6%96%BD%E3%81%97%E3%81%9F%E5%AD%A6%E7%BF%92%E5%86%85%E5%AE%B9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e🧑‍💻 実施した学習内容\u003c/h1\u003e\n\u003ch2 data-sourcepos=\"6:1-6:15\"\u003e\n\u003cspan id=\"1-疑問点\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E7%96%91%E5%95%8F%E7%82%B9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. 疑問点\u003c/h2\u003e\n\u003cp data-sourcepos=\"7:1-7:21\"\u003eMockitoのany()とは\u003c/p\u003e\n\u003ch2 data-sourcepos=\"9:1-9:21\"\u003e\n\u003cspan id=\"2-技術の概要\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-%E6%8A%80%E8%A1%93%E3%81%AE%E6%A6%82%E8%A6%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. 技術の概要\u003c/h2\u003e\n\u003cp data-sourcepos=\"10:1-12:68\"\u003e◾️何をするものか\u003cbr\u003e\nMockitoのany()は「引数マッチャー（Argument Matcher）」の一種で、\u003cbr\u003e\n「どんな値が渡されてもOK」とするための仕組み。\u003c/p\u003e\n\u003cp data-sourcepos=\"14:1-17:66\"\u003e◾️背景・目的（なぜ必要か）\u003cbr\u003e\nテストでは「値そのもの」ではなく「振る舞い（メソッドが呼ばれたか）」を検証したいケースが多い。\u003cbr\u003e\n→ 毎回具体的な値を指定するとテストが冗長・脆くなるため、\u003cbr\u003e\n「値は何でもいい」という抽象化が必要になる。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"19:1-19:12\"\u003e\n\u003cspan id=\"3-内容\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-%E5%86%85%E5%AE%B9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. 内容\u003c/h2\u003e\n\u003cp data-sourcepos=\"20:1-20:58\"\u003eMockitoでは通常、引数はequalsで比較される。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"22:1-24:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"A\"\u003c/span\u003e\u003cspan class=\"o\"\u003e)).\u003c/span\u003e\u003cspan class=\"na\"\u003ethenReturn\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"OK\"\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"26:1-26:52\"\u003e→ \"A\"と完全一致しないとマッチしない\u003c/p\u003e\n\u003cp data-sourcepos=\"28:1-28:32\"\u003eしかし、any()を使うと：\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"30:1-32:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e())).\u003c/span\u003e\u003cspan class=\"na\"\u003ethenReturn\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"OK\"\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"34:1-34:33\"\u003e→ 引数が何であってもOK\u003c/p\u003e\n\u003cp data-sourcepos=\"36:1-37:60\"\u003eつまり、\u003cbr\u003e\n「値の一致チェックをスキップする仕組み」\u003c/p\u003e\n\u003cp data-sourcepos=\"39:1-39:15\"\u003eポイント：\u003c/p\u003e\n\u003cul data-sourcepos=\"40:1-42:0\"\u003e\n\u003cli data-sourcepos=\"40:1-40:94\"\u003eany()は「任意の値（null含む）」にマッチ :contentReference[oaicite:0]{index=0}\u003c/li\u003e\n\u003cli data-sourcepos=\"41:1-42:0\"\u003e型付き版もある（anyString, anyIntなど） :contentReference[oaicite:1]{index=1}\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"43:1-43:18\"\u003e\n\u003cspan id=\"4-用語定義\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E7%94%A8%E8%AA%9E%E5%AE%9A%E7%BE%A9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. 用語定義\u003c/h2\u003e\n\u003cul data-sourcepos=\"44:1-52:0\"\u003e\n\u003cli data-sourcepos=\"44:1-45:41\"\u003eモック（Mock）：\u003cbr\u003e\nテスト用の偽物オブジェクト\u003c/li\u003e\n\u003cli data-sourcepos=\"46:1-47:65\"\u003eスタブ（Stub）：\u003cbr\u003e\n「この入力ならこの出力を返す」と決める設定\u003c/li\u003e\n\u003cli data-sourcepos=\"48:1-49:44\"\u003e検証（verify）：\u003cbr\u003e\nメソッドが呼ばれたかチェック\u003c/li\u003e\n\u003cli data-sourcepos=\"50:1-52:0\"\u003e引数マッチャー：\u003cbr\u003e\n引数の条件を柔軟に指定する仕組み\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"53:1-53:39\"\u003e\n\u003cspan id=\"5-解決する課題メリット\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#5-%E8%A7%A3%E6%B1%BA%E3%81%99%E3%82%8B%E8%AA%B2%E9%A1%8C%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5. 解決する課題・メリット\u003c/h2\u003e\n\u003cul data-sourcepos=\"54:1-57:0\"\u003e\n\u003cli data-sourcepos=\"54:1-54:32\"\u003eテストコードの簡略化\u003c/li\u003e\n\u003cli data-sourcepos=\"55:1-55:55\"\u003e動的な値（ID・日時など）に対応できる\u003c/li\u003e\n\u003cli data-sourcepos=\"56:1-57:0\"\u003e本質的な振る舞いに集中できる\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"58:1-59:25\"\u003e例：\u003cbr\u003e\nverify(repo).save(any());\u003c/p\u003e\n\u003cp data-sourcepos=\"61:1-61:56\"\u003e→ 「saveが呼ばれたか」だけを検証できる\u003c/p\u003e\n\u003ch2 data-sourcepos=\"63:1-63:48\"\u003e\n\u003cspan id=\"6-使用する注意事項デメリット\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#6-%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6. 使用する注意事項・デメリット\u003c/h2\u003e\n\u003cp data-sourcepos=\"65:1-65:29\"\u003e① matcherは統一が必要\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"67:1-73:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e//NG\u003c/span\u003e\n\u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e(),\u003c/span\u003e \u003cspan class=\"s\"\u003e\"A\"\u003c/span\u003e\u003cspan class=\"o\"\u003e))\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e//OK\u003c/span\u003e\n\u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003eeq\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"A\"\u003c/span\u003e\u003cspan class=\"o\"\u003e)))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"75:1-75:38\"\u003e→ matcherと通常値は混在不可\u003c/p\u003e\n\u003cp data-sourcepos=\"77:1-78:75\"\u003e② any()はnullを返すことがある\u003cbr\u003e\n→ プリミティブ型でNPE注意 :contentReference[oaicite:2]{index=2}\u003c/p\u003e\n\u003cp data-sourcepos=\"80:1-81:38\"\u003e③ 値の検証が弱くなる\u003cbr\u003e\n→ 重要な値はeq()を使うべき\u003c/p\u003e\n\u003ch2 data-sourcepos=\"83:1-83:30\"\u003e\n\u003cspan id=\"7-類似技術との比較\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#7-%E9%A1%9E%E4%BC%BC%E6%8A%80%E8%A1%93%E3%81%A8%E3%81%AE%E6%AF%94%E8%BC%83\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e7. 類似技術との比較\u003c/h2\u003e\n\u003ctable data-sourcepos=\"85:1-90:44\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"85:1-85:28\"\u003e\n\u003cth data-sourcepos=\"85:2-85:9\"\u003e技術\u003c/th\u003e\n\u003cth data-sourcepos=\"85:11-85:18\"\u003e意味\u003c/th\u003e\n\u003cth data-sourcepos=\"85:20-85:27\"\u003e用途\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"87:1-87:47\"\u003e\n\u003ctd data-sourcepos=\"87:2-87:8\"\u003eany()\u003c/td\u003e\n\u003ctd data-sourcepos=\"87:10-87:22\"\u003e何でもOK\u003c/td\u003e\n\u003ctd data-sourcepos=\"87:24-87:46\"\u003e値を気にしない\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"88:1-88:43\"\u003e\n\u003ctd data-sourcepos=\"88:2-88:8\"\u003eeq(x)\u003c/td\u003e\n\u003ctd data-sourcepos=\"88:10-88:21\"\u003exと一致\u003c/td\u003e\n\u003ctd data-sourcepos=\"88:23-88:42\"\u003e特定値を検証\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"89:1-89:46\"\u003e\n\u003ctd data-sourcepos=\"89:2-89:12\"\u003eargThat()\u003c/td\u003e\n\u003ctd data-sourcepos=\"89:14-89:27\"\u003e条件指定\u003c/td\u003e\n\u003ctd data-sourcepos=\"89:29-89:45\"\u003e複雑な条件\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"90:1-90:44\"\u003e\n\u003ctd data-sourcepos=\"90:2-90:11\"\u003eisNull()\u003c/td\u003e\n\u003ctd data-sourcepos=\"90:13-90:24\"\u003enullのみ\u003c/td\u003e\n\u003ctd data-sourcepos=\"90:26-90:43\"\u003enullチェック\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 data-sourcepos=\"92:1-92:39\"\u003e\n\u003cspan id=\"8-インストール環境構築\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#8-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e8. インストール・環境構築\u003c/h2\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"94:1-100:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eGradle:\nimplementation 'org.mockito:mockito-core:5.x.x'\n\nJUnit5:\ntestImplementation 'org.junit.jupiter:junit-jupiter:5.x.x'\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"102:1-102:63\"\u003e\n\u003cspan id=\"9-基本的な使い方実装サンプルコード\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#9-%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%BF%E3%81%84%E6%96%B9%E5%AE%9F%E8%A3%85%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e9. 基本的な使い方・実装（サンプルコード）\u003c/h2\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"103:1-126:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003estatic\u003c/span\u003e \u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emockito\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMockito\u003c/span\u003e\u003cspan class=\"o\"\u003e.*;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003estatic\u003c/span\u003e \u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emockito\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eArgumentMatchers\u003c/span\u003e\u003cspan class=\"o\"\u003e.*;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eUserService\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"nf\"\u003egetUser\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"s\"\u003e\"real\"\u003c/span\u003e\u003cspan class=\"o\"\u003e;\u003c/span\u003e\n    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestExample\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// モック作成\u003c/span\u003e\n        \u003cspan class=\"nc\"\u003eUserService\u003c/span\u003e \u003cspan class=\"n\"\u003emock\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emock\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eUserService\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// any()で「どんな引数でもOK」にする\u003c/span\u003e\n        \u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emock\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUser\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e())).\u003c/span\u003e\u003cspan class=\"na\"\u003ethenReturn\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"mocked\"\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// 実行\u003c/span\u003e\n        \u003cspan class=\"nc\"\u003eSystem\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emock\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUser\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"123\"\u003c/span\u003e\u003cspan class=\"o\"\u003e));\u003c/span\u003e \u003cspan class=\"c1\"\u003e// mocked\u003c/span\u003e\n        \u003cspan class=\"nc\"\u003eSystem\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emock\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUser\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"999\"\u003c/span\u003e\u003cspan class=\"o\"\u003e));\u003c/span\u003e \u003cspan class=\"c1\"\u003e// mocked\u003c/span\u003e\n    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"128:1-128:12\"\u003e◾️解説\u003c/p\u003e\n\u003cul data-sourcepos=\"129:1-132:0\"\u003e\n\u003cli data-sourcepos=\"129:1-129:45\"\u003eany() → 引数チェックをスキップ\u003c/li\u003e\n\u003cli data-sourcepos=\"130:1-130:26\"\u003e同じ戻り値を返す\u003c/li\u003e\n\u003cli data-sourcepos=\"131:1-132:0\"\u003eテストの柔軟性が上がる\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"133:1-133:58\"\u003e\n\u003cspan id=\"10-デザインパターンサンプルコード\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#10-%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e10. デザインパターン（サンプルコード）\u003c/h2\u003e\n\u003cp data-sourcepos=\"134:1-134:56\"\u003eStrategy的発想（比較ロジックの切り替え）\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"136:1-161:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// マッチング戦略インターフェース\u003c/span\u003e\n\u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"nc\"\u003eMatcherStrategy\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e \u003cspan class=\"nf\"\u003ematch\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 任意一致（any的）\u003c/span\u003e\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eAnyMatcher\u003c/span\u003e \u003cspan class=\"kd\"\u003eimplements\u003c/span\u003e \u003cspan class=\"nc\"\u003eMatcherStrategy\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e \u003cspan class=\"nf\"\u003ematch\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"o\"\u003e;\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 常にtrue\u003c/span\u003e\n    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 特定一致（eq的）\u003c/span\u003e\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eExactMatcher\u003c/span\u003e \u003cspan class=\"kd\"\u003eimplements\u003c/span\u003e \u003cspan class=\"nc\"\u003eMatcherStrategy\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"n\"\u003eexpected\u003c/span\u003e\u003cspan class=\"o\"\u003e;\u003c/span\u003e\n\n    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"nf\"\u003eExactMatcher\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"n\"\u003eexpected\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eexpected\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eexpected\u003c/span\u003e\u003cspan class=\"o\"\u003e;\u003c/span\u003e\n    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e \u003cspan class=\"nf\"\u003ematch\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nc\"\u003eString\u003c/span\u003e \u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eexpected\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eequals\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"163:1-163:12\"\u003e◾️解説\u003c/p\u003e\n\u003cul data-sourcepos=\"164:1-167:0\"\u003e\n\u003cli data-sourcepos=\"164:1-164:35\"\u003eany()は「常にtrueの戦略」\u003c/li\u003e\n\u003cli data-sourcepos=\"165:1-167:0\"\u003eeq()は「一致戦略」\u003cbr\u003e\n→ Mockito内部はこういう抽象化で動く\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"168:1-168:55\"\u003e\n\u003cspan id=\"11-アンチパターンサンプルコード\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#11-%E3%82%A2%E3%83%B3%E3%83%81%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e11. アンチパターン（サンプルコード）\u003c/h2\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"169:1-172:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 悪い例：全部any()\u003c/span\u003e\n\u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreate\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e())).\u003c/span\u003e\u003cspan class=\"na\"\u003ethenReturn\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"174:1-174:12\"\u003e◾️問題\u003c/p\u003e\n\u003cul data-sourcepos=\"175:1-177:0\"\u003e\n\u003cli data-sourcepos=\"175:1-175:47\"\u003e何をテストしているかわからない\u003c/li\u003e\n\u003cli data-sourcepos=\"176:1-177:0\"\u003eバグを見逃す\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"178:1-178:9\"\u003e改善：\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"java\" data-sourcepos=\"179:1-181:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewhen\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreate\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eeq\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\"user\"\u003c/span\u003e\u003cspan class=\"o\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"o\"\u003e())).\u003c/span\u003e\u003cspan class=\"na\"\u003ethenReturn\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"o\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"183:1-183:28\"\u003e→ 重要な値だけ固定\u003c/p\u003e\n","body":"# 🕒 学習時間\n10:30~11:30\n\n# 🧑‍💻 実施した学習内容\n\n## 1. 疑問点\nMockitoのany()とは\n\n## 2. 技術の概要\n◾️何をするものか\nMockitoのany()は「引数マッチャー（Argument Matcher）」の一種で、\n「どんな値が渡されてもOK」とするための仕組み。\n\n◾️背景・目的（なぜ必要か）\nテストでは「値そのもの」ではなく「振る舞い（メソッドが呼ばれたか）」を検証したいケースが多い。\n→ 毎回具体的な値を指定するとテストが冗長・脆くなるため、\n「値は何でもいい」という抽象化が必要になる。\n\n## 3. 内容\nMockitoでは通常、引数はequalsで比較される。\n\n```java\nwhen(service.get(\"A\")).thenReturn(\"OK\");\n```\n\n→ \"A\"と完全一致しないとマッチしない\n\nしかし、any()を使うと：\n\n```java\nwhen(service.get(any())).thenReturn(\"OK\");\n```\n\n→ 引数が何であってもOK\n\nつまり、\n「値の一致チェックをスキップする仕組み」\n\nポイント：\n- any()は「任意の値（null含む）」にマッチ :contentReference[oaicite:0]{index=0}\n- 型付き版もある（anyString, anyIntなど） :contentReference[oaicite:1]{index=1}\n\n## 4. 用語定義\n- モック（Mock）：\n  テスト用の偽物オブジェクト\n- スタブ（Stub）：\n  「この入力ならこの出力を返す」と決める設定\n- 検証（verify）：\n  メソッドが呼ばれたかチェック\n- 引数マッチャー：\n  引数の条件を柔軟に指定する仕組み\n\n## 5. 解決する課題・メリット\n- テストコードの簡略化\n- 動的な値（ID・日時など）に対応できる\n- 本質的な振る舞いに集中できる\n\n例：\nverify(repo).save(any());\n\n→ 「saveが呼ばれたか」だけを検証できる\n\n## 6. 使用する注意事項・デメリット\n\n① matcherは統一が必要\n\n```java\n//NG\nwhen(service.get(any(), \"A\"))\n\n//OK\nwhen(service.get(any(), eq(\"A\")))\n```\n\n→ matcherと通常値は混在不可\n\n② any()はnullを返すことがある\n→ プリミティブ型でNPE注意 :contentReference[oaicite:2]{index=2}\n\n③ 値の検証が弱くなる\n→ 重要な値はeq()を使うべき\n\n## 7. 類似技術との比較\n\n| 技術 | 意味 | 用途 |\n|------|------|------|\n| any() | 何でもOK | 値を気にしない |\n| eq(x) | xと一致 | 特定値を検証 |\n| argThat() | 条件指定 | 複雑な条件 |\n| isNull() | nullのみ | nullチェック |\n\n## 8. インストール・環境構築\n\n```\nGradle:\nimplementation 'org.mockito:mockito-core:5.x.x'\n\nJUnit5:\ntestImplementation 'org.junit.jupiter:junit-jupiter:5.x.x'\n```\n\n## 9. 基本的な使い方・実装（サンプルコード）\n```java\nimport static org.mockito.Mockito.*;\nimport static org.mockito.ArgumentMatchers.*;\n\nclass UserService {\n    String getUser(String id) {\n        return \"real\";\n    }\n}\n\npublic class TestExample {\n    public static void main(String[] args) {\n        // モック作成\n        UserService mock = mock(UserService.class);\n\n        // any()で「どんな引数でもOK」にする\n        when(mock.getUser(any())).thenReturn(\"mocked\");\n\n        // 実行\n        System.out.println(mock.getUser(\"123\")); // mocked\n        System.out.println(mock.getUser(\"999\")); // mocked\n    }\n}\n```\n\n◾️解説\n- any() → 引数チェックをスキップ\n- 同じ戻り値を返す\n- テストの柔軟性が上がる\n\n## 10. デザインパターン（サンプルコード）\nStrategy的発想（比較ロジックの切り替え）\n\n```java\n// マッチング戦略インターフェース\ninterface MatcherStrategy {\n    boolean match(String input);\n}\n\n// 任意一致（any的）\nclass AnyMatcher implements MatcherStrategy {\n    public boolean match(String input) {\n        return true; // 常にtrue\n    }\n}\n\n// 特定一致（eq的）\nclass ExactMatcher implements MatcherStrategy {\n    private String expected;\n\n    public ExactMatcher(String expected) {\n        this.expected = expected;\n    }\n\n    public boolean match(String input) {\n        return expected.equals(input);\n    }\n}\n```\n\n◾️解説\n- any()は「常にtrueの戦略」\n- eq()は「一致戦略」\n→ Mockito内部はこういう抽象化で動く\n\n## 11. アンチパターン（サンプルコード）\n```java\n// 悪い例：全部any()\nwhen(service.create(any(), any(), any())).thenReturn(true);\n```\n\n◾️問題\n- 何をテストしているかわからない\n- バグを見逃す\n\n改善：\n```java\nwhen(service.create(eq(\"user\"), any(), any())).thenReturn(true);\n```\n\n→ 重要な値だけ固定\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:26:20+09:00","group":null,"id":"bb696a1d279959426d67","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"Mockito","versions":[]},{"name":"junit5","versions":[]}],"title":"Mockitoのany()メソッド","updated_at":"2026-05-06T11:26:20+09:00","url":"https://qiita.com/Higasizono/items/bb696a1d279959426d67","user":{"description":null,"facebook_id":null,"followees_count":1,"followers_count":0,"github_login_name":"Higasizono","id":"Higasizono","items_count":21,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":1456202,"profile_image_url":"https://avatars.githubusercontent.com/u/83799384?v=4","team_only":false,"twitter_screen_name":null,"website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cp data-sourcepos=\"1:1-1:99\"\u003eプラケースの蟹さんドングルをフリスクの金属ケースに入れてみました。\u003c/p\u003e\n\u003cp data-sourcepos=\"3:1-3:105\"\u003e底にUSBコネクタを出す四角い開けてわきにアンテナを通す丸穴を開けました。\u003c/p\u003e\n\u003cp data-sourcepos=\"5:1-5:145\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F104066%2F83039607-fcb7-4ea4-bbf2-b9606544a522.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=00b3602e312e4ecb139cf58d0fb53723\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F104066%2F83039607-fcb7-4ea4-bbf2-b9606544a522.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=00b3602e312e4ecb139cf58d0fb53723\" alt=\"写真（2026-05-06 11.10）.jpg\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F104066%2F83039607-fcb7-4ea4-bbf2-b9606544a522.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=530de895d758c3d4d8c3309c552cb821 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/83039607-fcb7-4ea4-bbf2-b9606544a522.jpeg\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"7:1-7:123\"\u003e連休最終日最終日だからかもしれませんが、ログがいつもより多く流れている気がします。\u003c/p\u003e\n\u003cp data-sourcepos=\"9:1-9:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F104066%2F8c3edfde-01b3-4f88-b5f9-ba6126c8adf4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=effbfdfb6a46d4bd2868f7119177489b\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F104066%2F8c3edfde-01b3-4f88-b5f9-ba6126c8adf4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=effbfdfb6a46d4bd2868f7119177489b\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F104066%2F8c3edfde-01b3-4f88-b5f9-ba6126c8adf4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=6c358e30f20671ccd681f5cbdd52d824 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/8c3edfde-01b3-4f88-b5f9-ba6126c8adf4.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n","body":"プラケースの蟹さんドングルをフリスクの金属ケースに入れてみました。\n\n底にUSBコネクタを出す四角い開けてわきにアンテナを通す丸穴を開けました。\n\n![写真（2026-05-06 11.10）.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/83039607-fcb7-4ea4-bbf2-b9606544a522.jpeg)\n\n連休最終日最終日だからかもしれませんが、ログがいつもより多く流れている気がします。\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/8c3edfde-01b3-4f88-b5f9-ba6126c8adf4.png)\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:24:41+09:00","group":null,"id":"00e275e739a0368b2c72","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"SDR","versions":[]}],"title":"蟹さんドングルをケースに入れてみた","updated_at":"2026-05-06T11:24:41+09:00","url":"https://qiita.com/yamori813/items/00e275e739a0368b2c72","user":{"description":"","facebook_id":"","followees_count":0,"followers_count":73,"github_login_name":null,"id":"yamori813","items_count":929,"linkedin_id":"","location":"","name":"もり や","organization":"","permanent_id":104066,"profile_image_url":"https://qiita-image-store.s3.amazonaws.com/0/104066/profile-images/1473709191","team_only":false,"twitter_screen_name":"yamori813","website_url":"https://yamori813.github.io/hp/"},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"3:1-3:187\"\u003e\u003ca href=\"https://www.udesk.jp/\" rel=\"nofollow noopener\" target=\"_blank\"\u003eカスタマーサポート\u003c/a\u003eやコンタクトセンターの運用担当者の皆様、日々の応対業務でこのようなお悩みはございませんか？\u003c/p\u003e\n\u003cp data-sourcepos=\"5:1-5:382\"\u003e顧客が通話中に話を割り込んだ際、従来の音声AIが応答停止やフリーズを起こしてしまう。複数の質問を同時に受けた際、AIがニーズを読み間違え、的外れな回答をしてしまう。オペレーターは電話応対と事務処理を並行して行うため、業務負担が大きく、人為的ミスも発生しやすい。\u003c/p\u003e\n\u003cp data-sourcepos=\"7:1-7:257\"\u003e従来型の音声AIエージェントは、顧客の自然な割り込みに対応できず、真のニーズを正確に把握できないほか、複数業務の同時処理にも対応しておらず、単調な機械応答に限界がありました。\u003c/p\u003e\n\u003cp data-sourcepos=\"9:1-9:408\"\u003eこのたび\u003cstrong\u003eUDESK\u003c/strong\u003eでは、音声AIエージェントの核心性能を大幅にアップグレードいたしました。「自然な割り込み応対」「高精度なニーズ検知」「マルチタスク同時実行」の3つの強化ポイントを軸に、人間に近い自然な会話応対を実現し、企業の運用コスト削減と顧客満足度向上を同時に支援いたします。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"11:1-11:93\"\u003e\n\u003cspan id=\"進化１割り込み応対性能を向上ストレスのない通話環境を実現\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%80%B2%E5%8C%96%EF%BC%91%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF%E5%BF%9C%E5%AF%BE%E6%80%A7%E8%83%BD%E3%82%92%E5%90%91%E4%B8%8A%E3%82%B9%E3%83%88%E3%83%AC%E3%82%B9%E3%81%AE%E3%81%AA%E3%81%84%E9%80%9A%E8%A9%B1%E7%92%B0%E5%A2%83%E3%82%92%E5%AE%9F%E7%8F%BE\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e進化１：割り込み応対性能を向上、ストレスのない通話環境を実現\u003c/h2\u003e\n\u003cp data-sourcepos=\"13:1-13:320\"\u003e実際の電話応対では、顧客が途中で話を遮って質問するケースが非常に多く発生します。しかし旧来の音声AIは割り込みを無視したり、応対ロジックが混乱して会話が途切れたりすることで、応対効率や顧客体験を大きく損ねていました。\u003c/p\u003e\n\u003cp data-sourcepos=\"15:1-15:482\"\u003eUDESKは独自開発の割り込み検知アルゴリズムを導入し、VAD音声区間検知技術と高精度な意味解析を融合させています。不要な雑音を除外し、顧客の有意な割り込みだけを瞬時に判別。自然な流れで応答を切り替え、スムーズな双方向通話を安定的に維持します。周囲が騒がしい環境でも音声認識精度を高く保てるため、あらゆる現場で安心して活用可能です。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"17:1-17:87\"\u003e\n\u003cspan id=\"進化２真の顧客ニーズを見極め応対の的中率を大幅アップ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%80%B2%E5%8C%96%EF%BC%92%E7%9C%9F%E3%81%AE%E9%A1%A7%E5%AE%A2%E3%83%8B%E3%83%BC%E3%82%BA%E3%82%92%E8%A6%8B%E6%A5%B5%E3%82%81%E5%BF%9C%E5%AF%BE%E3%81%AE%E7%9A%84%E4%B8%AD%E7%8E%87%E3%82%92%E5%A4%A7%E5%B9%85%E3%82%A2%E3%83%83%E3%83%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e進化２：真の顧客ニーズを見極め、応対の的中率を大幅アップ\u003c/h2\u003e\n\u003cp data-sourcepos=\"19:1-19:329\"\u003e従来の音声AIは、顧客の言い回しが曖昧な場合や複数の要望が重なる場面に弱く、適切な回答ができず有人転送につながるケースが後を絶ちませんでした。結果としてオペレーターの負担が減らず、運用改善が進まない課題が長年続いています。\u003c/p\u003e\n\u003cp data-sourcepos=\"21:1-21:387\"\u003e今回のアップグレードでは、大規模言語モデル（LLM）を音声応対の基盤回路に直接連携させ、中間の複雑な処理工程を短縮化しました。単純な問い合わせはもちろん、顧客が言葉に出しにくい潜在的な要望、複数が重なった複合ニーズまで漏らさず検知し、最適な回答を自動生成します。\u003c/p\u003e\n\u003cp data-sourcepos=\"23:1-23:125\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4409001%2F5b67b92d-bbe4-48af-a19f-5b9a9c2c6cf6.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=88c184f3a689bcd1d134c5fad61ba39b\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4409001%2F5b67b92d-bbe4-48af-a19f-5b9a9c2c6cf6.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=88c184f3a689bcd1d134c5fad61ba39b\" alt=\"配图2.JPG\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4409001%2F5b67b92d-bbe4-48af-a19f-5b9a9c2c6cf6.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=679f69fd9774a2efe1c117862c15d757 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4409001/5b67b92d-bbe4-48af-a19f-5b9a9c2c6cf6.jpeg\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"25:1-25:468\"\u003e例えば小売店の問い合わせ業務で、顧客が商品価格と下取りサービスを同時に確認したい場合、旧型AIは片方の質問しか理解できません。UDESKの音声AIエージェントは両方のニーズを同時に把握し、順番に分かりやすく案内できるため、顧客が何度も同じ質問を繰り返す必要がなくなります。有人転送率の低下と顧客満足度の改善に直接貢献します。\u003c/p\u003e\n\u003cp data-sourcepos=\"27:1-27:435\"\u003eさらに会話の文脈を記憶する機能と情報自動補完機能を標準搭載。訪問修理・出張サポートの応対時には、顧客が曖昧に話した住所情報を自動で補正・整備し、基幹システムと連携してサポートチケットを自動発行できます。ニーズ確認から事務手続きまでを途切れなく連携させ、人間の手を介さずに業務を完了できます。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"29:1-29:78\"\u003e\n\u003cspan id=\"進化３複数業務を同時処理応対現場の生産性を倍増\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%80%B2%E5%8C%96%EF%BC%93%E8%A4%87%E6%95%B0%E6%A5%AD%E5%8B%99%E3%82%92%E5%90%8C%E6%99%82%E5%87%A6%E7%90%86%E5%BF%9C%E5%AF%BE%E7%8F%BE%E5%A0%B4%E3%81%AE%E7%94%9F%E7%94%A3%E6%80%A7%E3%82%92%E5%80%8D%E5%A2%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e進化３：複数業務を同時処理、応対現場の生産性を倍増\u003c/h2\u003e\n\u003cp data-sourcepos=\"31:1-31:305\"\u003e旧来の音声AIは一度に一つの業務しか実行できず、電話を受けながらチケット作成やデータ照会を並行して行うことができませんでした。そのため本来の省力化・コスト削減効果が十分に得られないケースが多く見られました。\u003c/p\u003e\n\u003cp data-sourcepos=\"33:1-33:311\"\u003eUDESKは従来のシングルタスク制限を解消し、豊富な外部連携インターフェースを標準装備しています。企業のCRMシステムやチケット管理ツールと簡単に連携し、「通話応対→業務登録→データ集計」までを一気通貫で自動化します。\u003c/p\u003e\n\u003cp data-sourcepos=\"35:1-35:125\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4409001%2Fbbcedf35-e405-478e-835b-cf87a7582300.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=a820653ed87baa7aff3df7fcea4e2ba4\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4409001%2Fbbcedf35-e405-478e-835b-cf87a7582300.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=a820653ed87baa7aff3df7fcea4e2ba4\" alt=\"配图1.JPG\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4409001%2Fbbcedf35-e405-478e-835b-cf87a7582300.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=3a169a13246834fa4d56f4861eb5ed8b 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4409001/bbcedf35-e405-478e-835b-cf87a7582300.jpeg\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"37:1-37:417\"\u003e大手コーヒーチェーンの顧客アンケート業務では、実際の商品体験をヒアリングしながら、同時に顧客の感想記録、タグ分類、集計作業をすべて自動処理。最後には分析用の可視化レポートまで自動作成するため、店舗や本部の業務負担を大幅に削減し、施策改善のための正確なデータをすぐに活用できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"39:1-39:589\"\u003e公共機関の総合案内、製造業のアフターメンテナンス、小売・飲食のカスタマー応対、各種ブランドの顧客フォロー調査など、幅広い業務シーンに向けた最適なソリューションを提供します。UDESKは音声AIの技術力を、単なる理論上の機能ではなく、現場でしっかり使える実践的な価値へと昇華させます。運用コストの適正化、業務スピードの向上、顧客体験の改善を同時に実現し、全業種のデジタル化推進を強力にサポートいたします。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"41:1-41:15\"\u003e\n\u003cspan id=\"次回予告\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%AC%A1%E5%9B%9E%E4%BA%88%E5%91%8A\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e次回予告\u003c/h2\u003e\n\u003cp data-sourcepos=\"43:1-43:51\"\u003e\u003cstrong\u003e音声 AI エージェント進化（第4弾）\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"45:1-45:281\"\u003e企業のグローバル展開が加速する現在、言語の違いが新たな応対課題となっています。次回は多言語対応機能の全貌を大公開し、国境を越えたAI応対の新たな可能性をご紹介いたします。ぜひご期待ください。\u003c/p\u003e\n\u003cp data-sourcepos=\"47:1-47:47\"\u003e\u003ca href=\"https://www.udesk.jp/contact\" rel=\"nofollow noopener\" target=\"_blank\"\u003e無料で試す\u003c/a\u003e\u003c/p\u003e\n","body":"## はじめに\n\n[カスタマーサポート](https://www.udesk.jp/)やコンタクトセンターの運用担当者の皆様、日々の応対業務でこのようなお悩みはございませんか？\n\n顧客が通話中に話を割り込んだ際、従来の音声AIが応答停止やフリーズを起こしてしまう。複数の質問を同時に受けた際、AIがニーズを読み間違え、的外れな回答をしてしまう。オペレーターは電話応対と事務処理を並行して行うため、業務負担が大きく、人為的ミスも発生しやすい。\n\n従来型の音声AIエージェントは、顧客の自然な割り込みに対応できず、真のニーズを正確に把握できないほか、複数業務の同時処理にも対応しておらず、単調な機械応答に限界がありました。\n\nこのたび**UDESK**では、音声AIエージェントの核心性能を大幅にアップグレードいたしました。「自然な割り込み応対」「高精度なニーズ検知」「マルチタスク同時実行」の3つの強化ポイントを軸に、人間に近い自然な会話応対を実現し、企業の運用コスト削減と顧客満足度向上を同時に支援いたします。\n\n## 進化１：割り込み応対性能を向上、ストレスのない通話環境を実現\n\n実際の電話応対では、顧客が途中で話を遮って質問するケースが非常に多く発生します。しかし旧来の音声AIは割り込みを無視したり、応対ロジックが混乱して会話が途切れたりすることで、応対効率や顧客体験を大きく損ねていました。\n\nUDESKは独自開発の割り込み検知アルゴリズムを導入し、VAD音声区間検知技術と高精度な意味解析を融合させています。不要な雑音を除外し、顧客の有意な割り込みだけを瞬時に判別。自然な流れで応答を切り替え、スムーズな双方向通話を安定的に維持します。周囲が騒がしい環境でも音声認識精度を高く保てるため、あらゆる現場で安心して活用可能です。\n\n## 進化２：真の顧客ニーズを見極め、応対の的中率を大幅アップ\n\n従来の音声AIは、顧客の言い回しが曖昧な場合や複数の要望が重なる場面に弱く、適切な回答ができず有人転送につながるケースが後を絶ちませんでした。結果としてオペレーターの負担が減らず、運用改善が進まない課題が長年続いています。\n\n今回のアップグレードでは、大規模言語モデル（LLM）を音声応対の基盤回路に直接連携させ、中間の複雑な処理工程を短縮化しました。単純な問い合わせはもちろん、顧客が言葉に出しにくい潜在的な要望、複数が重なった複合ニーズまで漏らさず検知し、最適な回答を自動生成します。\n\n![配图2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4409001/5b67b92d-bbe4-48af-a19f-5b9a9c2c6cf6.jpeg)\n\n例えば小売店の問い合わせ業務で、顧客が商品価格と下取りサービスを同時に確認したい場合、旧型AIは片方の質問しか理解できません。UDESKの音声AIエージェントは両方のニーズを同時に把握し、順番に分かりやすく案内できるため、顧客が何度も同じ質問を繰り返す必要がなくなります。有人転送率の低下と顧客満足度の改善に直接貢献します。\n\nさらに会話の文脈を記憶する機能と情報自動補完機能を標準搭載。訪問修理・出張サポートの応対時には、顧客が曖昧に話した住所情報を自動で補正・整備し、基幹システムと連携してサポートチケットを自動発行できます。ニーズ確認から事務手続きまでを途切れなく連携させ、人間の手を介さずに業務を完了できます。\n\n## 進化３：複数業務を同時処理、応対現場の生産性を倍増\n\n旧来の音声AIは一度に一つの業務しか実行できず、電話を受けながらチケット作成やデータ照会を並行して行うことができませんでした。そのため本来の省力化・コスト削減効果が十分に得られないケースが多く見られました。\n\nUDESKは従来のシングルタスク制限を解消し、豊富な外部連携インターフェースを標準装備しています。企業のCRMシステムやチケット管理ツールと簡単に連携し、「通話応対→業務登録→データ集計」までを一気通貫で自動化します。\n\n![配图1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4409001/bbcedf35-e405-478e-835b-cf87a7582300.jpeg)\n\n大手コーヒーチェーンの顧客アンケート業務では、実際の商品体験をヒアリングしながら、同時に顧客の感想記録、タグ分類、集計作業をすべて自動処理。最後には分析用の可視化レポートまで自動作成するため、店舗や本部の業務負担を大幅に削減し、施策改善のための正確なデータをすぐに活用できます。\n\n公共機関の総合案内、製造業のアフターメンテナンス、小売・飲食のカスタマー応対、各種ブランドの顧客フォロー調査など、幅広い業務シーンに向けた最適なソリューションを提供します。UDESKは音声AIの技術力を、単なる理論上の機能ではなく、現場でしっかり使える実践的な価値へと昇華させます。運用コストの適正化、業務スピードの向上、顧客体験の改善を同時に実現し、全業種のデジタル化推進を強力にサポートいたします。\n\n## 次回予告\n\n**音声 AI エージェント進化（第4弾）**\n\n企業のグローバル展開が加速する現在、言語の違いが新たな応対課題となっています。次回は多言語対応機能の全貌を大公開し、国境を越えたAI応対の新たな可能性をご紹介いたします。ぜひご期待ください。\n\n[無料で試す](https://www.udesk.jp/contact)\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:24:30+09:00","group":null,"id":"922178a558fc0d1cf4f3","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"業務効率化","versions":[]},{"name":"音声AIエージェント","versions":[]},{"name":"顧客体験最適化","versions":[]}],"title":"音声AIエージェント進化（第3弾）新たな知能で通話体験を革新","updated_at":"2026-05-06T11:24:30+09:00","url":"https://qiita.com/Udesk/items/922178a558fc0d1cf4f3","user":{"description":null,"facebook_id":null,"followees_count":1,"followers_count":0,"github_login_name":null,"id":"Udesk","items_count":2,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":4409001,"profile_image_url":"https://lh3.googleusercontent.com/a/ACg8ocLHoNOB7RQCG9IMriqZ6S8RLMtlasAcoE3CRWrRLi8BhYEfbw=s96-c","team_only":false,"twitter_screen_name":null,"website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"2:1-3:99\"\u003e先日、X（旧Twitter）で秋葉原の「ロボットほこ天」というイベントの様子を見かけ、\u003cbr\u003e\nそこで初めてｽﾀｯｸﾁｬﾝという小さなロボットの存在を知りました。\u003c/p\u003e\n\u003cp data-sourcepos=\"5:1-7:111\"\u003e調べてみると、\u003cbr\u003e\n自分で組み立てられて、サーボで頭や足を動かせて、AIで会話までできる“ｽｰﾊﾟｰｶﾜｲｲﾛﾎﾞｯﾄ” とのこと。\u003cbr\u003e\n気づいたらすぐに部品を注文していて、そのまま勢いで組み立ててしまいました。\u003c/p\u003e\n\u003cp data-sourcepos=\"9:1-9:208\"\u003eこの記事では、参考にしたサイトや組み立てで躓いたポイントを 備忘録としてまとめつつ、これから作る人の助けになる情報を残しておこうと思います。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"11:1-11:18\"\u003e\n\u003cspan id=\"必要なもの\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%BF%85%E8%A6%81%E3%81%AA%E3%82%82%E3%81%AE\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e必要なもの\u003c/h2\u003e\n\u003cp data-sourcepos=\"12:1-14:38\"\u003e・\u003cstrong\u003eM5Stack本体（推奨はCore2 かCore2 for AWS）\u003c/strong\u003e\u003cbr\u003e\n　私はバッテリー容量が多いことと、AWS IoTも触ってみたかったので\u003cbr\u003e\n　Core2 for AWS を選びました。\u003c/p\u003e\n\u003cp data-sourcepos=\"16:1-16:44\"\u003e\u003ciframe id=\"qiita-embed-content__c3c37ddb0911137b4a0dd3d3331e568f\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__c3c37ddb0911137b4a0dd3d3331e568f\" data-content=\"https%3A%2F%2Fwww.switch-science.com%2Fproducts%2F6784\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"18:1-21:79\"\u003e　\u003cbr\u003e\n・\u003cstrong\u003eサーボSG90 2個\u003c/strong\u003e\u003cbr\u003e\n　Amazon や AliExpress でも買えますが、不良率が高いという報告が多く、\u003cbr\u003e\n　秋月電子の SG90 が最も安定 していると言われています。\u003c/p\u003e\n\u003cp data-sourcepos=\"23:1-23:44\"\u003e\u003ciframe id=\"qiita-embed-content__7575f9d17c808081e2c47135114c139a\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__7575f9d17c808081e2c47135114c139a\" data-content=\"https%3A%2F%2Fakizukidenshi.com%2Fcatalog%2Fg%2Fg108761%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"25:1-27:96\"\u003e　\u003cbr\u003e\n・\u003cstrong\u003eｽﾀｯｸﾁｬﾝ タカオ版 組み立てキット\u003c/strong\u003e\u003cbr\u003e\n　Booth にてタカオさんが販売されている組み立てキットを購入します。\u003c/p\u003e\n\u003cp data-sourcepos=\"29:1-29:39\"\u003e\u003ciframe id=\"qiita-embed-content__61d79f8f3735c66bccb47124f07808eb\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__61d79f8f3735c66bccb47124f07808eb\" data-content=\"https%3A%2F%2Fmongonta.booth.pm%2Fitems%2F5505450\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"31:1-34:106\"\u003e　\u003cbr\u003e\n・\u003cstrong\u003eStack-chan_Takao_Base\u003c/strong\u003e\u003cbr\u003e\n　サーボと M5Stack を簡単に接続できる基板です。\u003cbr\u003e\n　※タカオ版組み立てキットでtakao_base付のものを購入しているなら不要です\u003c/p\u003e\n\u003cp data-sourcepos=\"36:1-36:72\"\u003e\u003ciframe id=\"qiita-embed-content__b5a927649f57d00a914e2b105b77d7df\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__b5a927649f57d00a914e2b105b77d7df\" data-content=\"https%3A%2F%2Fwww.switch-science.com%2Fproducts%2F8905%3F_pos%3D1%26_sid%3D9f5f36bbd%26_ss%3Dr\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"38:1-40:109\"\u003e　\u003cbr\u003e\n・\u003cstrong\u003emicroSD 16GB\u003c/strong\u003e\u003cbr\u003e\n　M5Stackの仕様上、16GBが最も安定すると言われているため、16GBを購入しました。\u003c/p\u003e\n\u003cp data-sourcepos=\"42:1-42:28\"\u003e\u003ciframe id=\"qiita-embed-content__03a85d27d4a5a89918cf8c11205fffe9\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__03a85d27d4a5a89918cf8c11205fffe9\" data-content=\"https%3A%2F%2Famzn.asia%2Fd%2F0aJFM7nz\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"44:1-47:133\"\u003e　\u003cbr\u003e\n・\u003cstrong\u003eM3 Screw Kit\u003c/strong\u003e\u003cbr\u003e\n　\u003ccode\u003eCore2 for AWS\u003c/code\u003e版を分解なし手順（後述）で作成する場合、\u003cbr\u003e\n　タカオ版組み立てキット付属のボルトでは長さが足りないので、別途こちらの購入が必要です。\u003c/p\u003e\n\u003cp data-sourcepos=\"49:2-49:45\"\u003e\u003ciframe id=\"qiita-embed-content__d2308693f53fb477e64fe42d3e5b13f4\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__d2308693f53fb477e64fe42d3e5b13f4\" data-content=\"https%3A%2F%2Fwww.switch-science.com%2Fproducts%2F6631\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"51:1-54:51\"\u003e　\u003cbr\u003e\n・\u003cstrong\u003e六角レンチ\u003c/strong\u003e\u003cbr\u003e\n・\u003cstrong\u003e精密ドライバー\u003c/strong\u003e\u003cbr\u003e\n　100均に売っているものでOKでした。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"56:1-56:55\"\u003e\n\u003cspan id=\"m5stack側の準備ｽﾀｯｸﾁｬﾝの頭\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#m5stack%E5%81%B4%E3%81%AE%E6%BA%96%E5%82%99%EF%BD%BD%EF%BE%80%EF%BD%AF%EF%BD%B8%EF%BE%81%EF%BD%AC%EF%BE%9D%E3%81%AE%E9%A0%AD\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eM5stack側の準備（ｽﾀｯｸﾁｬﾝの頭）\u003c/h2\u003e\n\u003cp data-sourcepos=\"57:1-57:110\"\u003eM5Stackをｽﾀｯｸﾁｬﾝとして動かすために、まずは以下の4ステップを行います。\u003c/p\u003e\n\u003col data-sourcepos=\"58:1-62:0\"\u003e\n\u003cli data-sourcepos=\"58:1-58:21\"\u003eAPIキーの取得\u003c/li\u003e\n\u003cli data-sourcepos=\"59:1-59:47\"\u003eSDカードに必要な情報を書き込み\u003c/li\u003e\n\u003cli data-sourcepos=\"60:1-60:33\"\u003eファームウェアの準備\u003c/li\u003e\n\u003cli data-sourcepos=\"61:1-62:0\"\u003eｽﾀｯｸﾁｬﾝの起動\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 data-sourcepos=\"63:1-63:24\"\u003e\n\u003cspan id=\"1apiキーの取得\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1api%E3%82%AD%E3%83%BC%E3%81%AE%E5%8F%96%E5%BE%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1.APIキーの取得\u003c/h3\u003e\n\u003cp data-sourcepos=\"64:1-64:96\"\u003e取得方法については、こちらのREADMEでわかりやすく説明されています。\u003c/p\u003e\n\u003cp data-sourcepos=\"66:1-66:67\"\u003e\u003ciframe id=\"qiita-embed-content__25edb81488a088541038590b3295a2f2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__25edb81488a088541038590b3295a2f2\" data-content=\"https%3A%2F%2Fgithub.com%2Frobo8080%2FAI_StackChan2_README%3Ftab%3Dreadme-ov-file\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cdiv data-sourcepos=\"68:1-71:3\" class=\"note warn\"\u003e\n\u003cspan class=\"fa fa-fw fa-exclamation-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"69:1-70:52\"\u003e現在（2026/05/06）、ChatGPT（OpenAI）の無料クレジットは廃止されており、\u003cbr\u003e\n最低 $5 のクレジット購入が必要です。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch3 data-sourcepos=\"74:1-74:50\"\u003e\n\u003cspan id=\"2sdカードに必要な情報を書き込み\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2sd%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%83%85%E5%A0%B1%E3%82%92%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.SDカードに必要な情報を書き込み\u003c/h3\u003e\n\u003cp data-sourcepos=\"75:1-76:48\"\u003eSDカードのルートディレクトリに\u003ccode\u003ewifi.txt\u003c/code\u003e と \u003ccode\u003eapikey.txt\u003c/code\u003eを用意します。\u003cbr\u003e\n必要な情報はそれぞれ以下の通り。\u003c/p\u003e\n\u003cul data-sourcepos=\"78:1-86:0\"\u003e\n\u003cli data-sourcepos=\"78:1-81:3\"\u003ewifi.txt\u003cbr\u003e\n1行目にWifiのSSID\u003cbr\u003e\n2行目にWifiのパスワード\u003cbr\u003e\n　\u003c/li\u003e\n\u003cli data-sourcepos=\"82:1-86:0\"\u003eapikey.txt\u003cbr\u003e\n1行目にOpenAIのAPIキー\u003cbr\u003e\n2行目にVOICEVOXのAPIキー\u003cbr\u003e\n3行目にGoogle Cloud STT APIキー\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"87:1-87:42\"\u003e\n\u003cspan id=\"3ファームウェアの書き込み\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3%E3%83%95%E3%82%A1%E3%83%BC%E3%83%A0%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3.ファームウェアの書き込み\u003c/h3\u003e\n\u003cp data-sourcepos=\"88:1-88:68\"\u003eM5Burnerを使用してファームウェアを書き込みます。\u003c/p\u003e\n\u003ch4 data-sourcepos=\"90:1-90:26\"\u003e\n\u003cspan id=\"必要なドライバ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e必要なドライバ\u003c/h4\u003e\n\u003cul data-sourcepos=\"91:1-93:0\"\u003e\n\u003cli data-sourcepos=\"91:1-91:25\"\u003eM5Burner Win10 x64 v3.0\u003c/li\u003e\n\u003cli data-sourcepos=\"92:1-93:0\"\u003eCH9102_VCP_SER_Windows（USBシリアルドライバ）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"94:1-94:33\"\u003eダウンロードはこちら：\u003c/p\u003e\n\u003cp data-sourcepos=\"96:1-96:36\"\u003e\u003ciframe id=\"qiita-embed-content__40b37d2bf4f31e2041d465b2d76847d2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__40b37d2bf4f31e2041d465b2d76847d2\" data-content=\"https%3A%2F%2Fdocs.m5stack.com%2Fen%2Fdownload\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003ch4 data-sourcepos=\"98:1-98:22\"\u003e\n\u003cspan id=\"m5burnerの操作\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#m5burner%E3%81%AE%E6%93%8D%E4%BD%9C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eM5Burnerの操作\u003c/h4\u003e\n\u003col data-sourcepos=\"99:1-103:0\"\u003e\n\u003cli data-sourcepos=\"99:1-99:20\"\u003eM5Burnerを起動\u003c/li\u003e\n\u003cli data-sourcepos=\"100:1-100:54\"\u003e左側のカテゴリから CORE2 \u0026amp; TOUGH を選択\u003c/li\u003e\n\u003cli data-sourcepos=\"101:1-101:32\"\u003e検索窓に「AI」と入力\u003c/li\u003e\n\u003cli data-sourcepos=\"102:1-103:0\"\u003e以下のファームウェアを選択\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"104:1-104:116\"\u003e\u003cstrong\u003eAIスタックチャン2 (FY24/7月 証明書更新＋ChatGPT-4o mini) 0.0.10 (GPT-4o mini) サーボ: Port.A版\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv data-sourcepos=\"106:1-109:3\" class=\"note warn\"\u003e\n\u003cspan class=\"fa fa-fw fa-exclamation-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"107:1-108:108\"\u003ePort.C版もありますが、タカオ版筐体の場合は基本 Port.A（正面から見て左側） にサーボを接続します。\u003cbr\u003e\nもし 正面上側（Port.C）にサーボを挿したい場合のみ Port.C版 を選んでください。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch3 data-sourcepos=\"111:1-111:36\"\u003e\n\u003cspan id=\"4ｽﾀｯｸﾁｬﾝの起動\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4%EF%BD%BD%EF%BE%80%EF%BD%AF%EF%BD%B8%EF%BE%81%EF%BD%AC%EF%BE%9D%E3%81%AE%E8%B5%B7%E5%8B%95\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4.ｽﾀｯｸﾁｬﾝの起動\u003c/h3\u003e\n\u003cp data-sourcepos=\"112:1-112:84\"\u003e先ほど準備したものを使い、ｽﾀｯｸﾁｬﾝを起動させます。\u003c/p\u003e\n\u003col data-sourcepos=\"113:1-116:0\"\u003e\n\u003cli data-sourcepos=\"113:1-113:39\"\u003eM5StackにSDカードを差し込む\u003c/li\u003e\n\u003cli data-sourcepos=\"114:1-114:33\"\u003eM5StackとPCをUSB接続する\u003c/li\u003e\n\u003cli data-sourcepos=\"115:1-116:0\"\u003eAIスタックチャン2 (FY24/7月 証明書更新＋ChatGPT-4o mini)をBurnする\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"117:1-117:86\"\u003eWi-Fiに接続成功すると、ｽﾀｯｸﾁｬﾝのお顔が表示されます。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"120:1-120:39\"\u003e\n\u003cspan id=\"ｽﾀｯｸﾁｬﾝの組み立て\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%EF%BD%BD%EF%BE%80%EF%BD%AF%EF%BD%B8%EF%BE%81%EF%BD%AC%EF%BE%9D%E3%81%AE%E7%B5%84%E3%81%BF%E7%AB%8B%E3%81%A6\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eｽﾀｯｸﾁｬﾝの組み立て\u003c/h2\u003e\n\u003cp data-sourcepos=\"121:1-125:36\"\u003eスタックチャンの組み立てには、\u003cbr\u003e\n\u003cstrong\u003e内蔵バッテリーを外す「分解あり手順」\u003c/strong\u003e と\u003cbr\u003e\n\u003cstrong\u003eバッテリーを外さない「分解なし手順」\u003c/strong\u003e\u003cbr\u003e\nの2種類があります。\u003cbr\u003e\n簡単に違いを説明します。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"127:1-127:31\"\u003e\n\u003cspan id=\"分解あり手順の場合\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%88%86%E8%A7%A3%E3%81%82%E3%82%8A%E6%89%8B%E9%A0%86%E3%81%AE%E5%A0%B4%E5%90%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e分解あり手順の場合\u003c/h3\u003e\n\u003cp data-sourcepos=\"128:1-129:57\"\u003eM5Stack Core2 / Core2 for AWS の背面を開けて、内蔵バッテリーを取り外す方式\u003cbr\u003e\n重心が安定し、見た目もスッキリします。\u003c/p\u003e\n\u003cp data-sourcepos=\"131:1-132:75\"\u003eただし、Core2 for AWS のバッテリーは強力な両面テープで固定されているため、\u003cbr\u003e\n無理に剥がすとバッテリーを損傷する危険があります。\u003c/p\u003e\n\u003cdiv data-sourcepos=\"134:1-138:3\" class=\"note warn\"\u003e\n\u003cspan class=\"fa fa-fw fa-exclamation-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"135:1-137:51\"\u003eCore2 for AWS のバッテリーは強力に貼り付いています。\u003cbr\u003e\n無理に剥がすと 膨張・発煙・最悪発火のリスク があるため、\u003cbr\u003e\n初心者は分解なし手順を推奨します。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch3 data-sourcepos=\"140:1-140:31\"\u003e\n\u003cspan id=\"分解なし手順の場合\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%88%86%E8%A7%A3%E3%81%AA%E3%81%97%E6%89%8B%E9%A0%86%E3%81%AE%E5%A0%B4%E5%90%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e分解なし手順の場合\u003c/h3\u003e\n\u003cp data-sourcepos=\"141:1-144:78\"\u003e内蔵バッテリーをそのまま残して組み立てる方式\u003cbr\u003e\n安全で簡単\u003cbr\u003e\nただし、バッテリーの厚み分だけ 頭が少し大きくなる（頭でっかち）\u003cbr\u003e\n私は安全性を優先して、分解なし手順で組み立てました。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"147:1-147:34\"\u003e\n\u003cspan id=\"組み立て手順について\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%B5%84%E3%81%BF%E7%AB%8B%E3%81%A6%E6%89%8B%E9%A0%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e組み立て手順について\u003c/h3\u003e\n\u003cp data-sourcepos=\"148:1-150:72\"\u003e組み立て手順そのものは、先駆者の方々が非常に分かりやすくまとめてくださっています。\u003cbr\u003e\n以下の解説を参考に進めるのが確実です。\u003cbr\u003e\n分解なし手順で参考にしたサイトをまとめています。\u003c/p\u003e\n\u003cp data-sourcepos=\"152:1-152:72\"\u003e\u003ciframe id=\"qiita-embed-content__974456352a62250a8f3f453ab3321483\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__974456352a62250a8f3f453ab3321483\" data-content=\"https%3A%2F%2Fraspberrypi.mongonta.com%2Fhow-to-build-easy-stackchan-m5gobottom%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"154:1-154:50\"\u003e\u003ciframe id=\"qiita-embed-content__178b7f46056e530d4f5194365f0bf004\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__178b7f46056e530d4f5194365f0bf004\" data-content=\"https%3A%2F%2Fzenn.dev%2Fryo_tsukuda%2Fscraps%2F353d7931ec44f4\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003ch2 data-sourcepos=\"156:1-156:33\"\u003e\n\u003cspan id=\"ｽﾀｯｸﾁｬﾝの完成\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%EF%BD%BD%EF%BE%80%EF%BD%AF%EF%BD%B8%EF%BE%81%EF%BD%AC%EF%BE%9D%E3%81%AE%E5%AE%8C%E6%88%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eｽﾀｯｸﾁｬﾝの完成\u003c/h2\u003e\n\u003cp data-sourcepos=\"157:1-159:69\"\u003e分解なし手順で無事に組み立てが完了しました。\u003cbr\u003e\n内蔵バッテリーを残した分、少し頭でっかちにはなりますが、これはこれで愛嬌があります。\u003cbr\u003e\n完成したスタックチャンの写真はこんな感じです。\u003c/p\u003e\n\u003cp data-sourcepos=\"161:1-161:140\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4245000%2F5539e03f-4932-4595-98e9-a1848f3aeca4.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=c727f8239738588da05b28c4d2f32bb3\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4245000%2F5539e03f-4932-4595-98e9-a1848f3aeca4.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=c727f8239738588da05b28c4d2f32bb3\" alt=\"20260414_132050593_iOS.jpg\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4245000%2F5539e03f-4932-4595-98e9-a1848f3aeca4.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=b4d1acd161d9b3354c8e6808f809d064 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4245000/5539e03f-4932-4595-98e9-a1848f3aeca4.jpeg\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"163:1-164:51\"\u003eそして、何もしていないときでも キョロキョロと周りを見回していて、ｶﾜｲｲです。\u003cbr\u003e\nただ置いておくだけで癒やされます。\u003c/p\u003e\n\u003cp data-sourcepos=\"166:1-166:155\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4245000%2F1444a326-f681-4fdc-997d-78577511a5c9.gif?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=806a25fa9a46197c59340be2d57382d7\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4245000%2F1444a326-f681-4fdc-997d-78577511a5c9.gif?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=806a25fa9a46197c59340be2d57382d7\" alt=\"Adobe Express - 20260414_134133000_iOS.gif\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4245000%2F1444a326-f681-4fdc-997d-78577511a5c9.gif?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=74ab5f1f6ec81cb0edab829d6008c247 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4245000/1444a326-f681-4fdc-997d-78577511a5c9.gif\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"169:1-169:60\"\u003e\n\u003cspan id=\"参考にさせていただいたサイトまとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%95%E3%81%9B%E3%81%A6%E3%81%84%E3%81%9F%E3%81%A0%E3%81%84%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e参考にさせていただいたサイト（まとめ）\u003c/h2\u003e\n\u003cp data-sourcepos=\"170:1-171:117\"\u003e今回スタックチャンを組み立てるにあたり、以下のサイトを参考にさせていただきました。\u003cbr\u003e\nどれも非常に分かりやすく、初めての方でも安心して進められる内容になっています。\u003c/p\u003e\n\u003cp data-sourcepos=\"173:1-173:48\"\u003e\u003ciframe id=\"qiita-embed-content__dc9ac06680af91f6bf1468a050c030c7\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__dc9ac06680af91f6bf1468a050c030c7\" data-content=\"https%3A%2F%2Fnote.com%2Fmochi_mochi_lab%2Fn%2Fn45aa4cd54f89\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"175:1-175:50\"\u003e\u003ciframe id=\"qiita-embed-content__2f6d280226cc1cbdf27292b171ff72f2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__2f6d280226cc1cbdf27292b171ff72f2\" data-content=\"https%3A%2F%2Fzenn.dev%2Fryo_tsukuda%2Fscraps%2F353d7931ec44f4\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"177:1-177:51\"\u003e\u003ciframe id=\"qiita-embed-content__965f2a74de06b212318911ffaecd0682\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__965f2a74de06b212318911ffaecd0682\" data-content=\"https%3A%2F%2Fqiita.com%2Fseigot%2Fitems%2F6be5d199afada1e63729\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"179:1-179:72\"\u003e\u003ciframe id=\"qiita-embed-content__ddbee4e537ea47bde4ba510535a9cac7\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__ddbee4e537ea47bde4ba510535a9cac7\" data-content=\"https%3A%2F%2Fraspberrypi.mongonta.com%2Fhow-to-build-easy-stackchan-m5gobottom%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"181:1-182:163\"\u003eまた、X（旧Twitter）でも多くの方がノウハウや改造例を投稿されています。\u003cbr\u003e\n「ｽﾀｯｸﾁｬﾝ」「Stack-chan」などで検索すると、最新の情報がたくさん見つかるので、エゴサするのもオススメです。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"185:1-185:15\"\u003e\n\u003cspan id=\"おわりに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eおわりに\u003c/h2\u003e\n\u003cp data-sourcepos=\"186:1-187:66\"\u003e今回は AIｽﾀｯｸﾁｬﾝを組み立ててみました。\u003cbr\u003e\nとはいえ、まだまだ改善したいところがあって、\u003c/p\u003e\n\u003cul data-sourcepos=\"189:1-192:0\"\u003e\n\u003cli data-sourcepos=\"189:1-189:41\"\u003e音声の聞き取りが安定しない\u003c/li\u003e\n\u003cli data-sourcepos=\"190:1-190:35\"\u003eサーボの動きが少し渋い\u003c/li\u003e\n\u003cli data-sourcepos=\"191:1-192:0\"\u003eたまに反応が遅れる\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"193:1-196:54\"\u003eなど、課題はたくさんあります・・・。\u003cbr\u003e\n作ってみて分かったのは、スタックチャンは“完成してからが本番\"ということ。\u003cbr\u003e\n動かすほど愛着が湧いて、気づいたら改造したくなってしまいますね。\u003cbr\u003e\n改善や進捗があれば、また投稿します。\u003c/p\u003e\n","body":"## はじめに\n先日、X（旧Twitter）で秋葉原の「ロボットほこ天」というイベントの様子を見かけ、\nそこで初めてｽﾀｯｸﾁｬﾝという小さなロボットの存在を知りました。\n\n調べてみると、\n自分で組み立てられて、サーボで頭や足を動かせて、AIで会話までできる“ｽｰﾊﾟｰｶﾜｲｲﾛﾎﾞｯﾄ” とのこと。\n気づいたらすぐに部品を注文していて、そのまま勢いで組み立ててしまいました。\n\nこの記事では、参考にしたサイトや組み立てで躓いたポイントを 備忘録としてまとめつつ、これから作る人の助けになる情報を残しておこうと思います。\n\n## 必要なもの\n・**M5Stack本体（推奨はCore2 かCore2 for AWS）**\n　私はバッテリー容量が多いことと、AWS IoTも触ってみたかったので\n　Core2 for AWS を選びました。\n\nhttps://www.switch-science.com/products/6784\n\n　\n・**サーボSG90 2個**\n　Amazon や AliExpress でも買えますが、不良率が高いという報告が多く、\n　秋月電子の SG90 が最も安定 していると言われています。\n\nhttps://akizukidenshi.com/catalog/g/g108761/\n\n　\n・**ｽﾀｯｸﾁｬﾝ タカオ版 組み立てキット**\n　Booth にてタカオさんが販売されている組み立てキットを購入します。\n\nhttps://mongonta.booth.pm/items/5505450\n\n　\n・**Stack-chan_Takao_Base**\n　サーボと M5Stack を簡単に接続できる基板です。\n   　※タカオ版組み立てキットでtakao_base付のものを購入しているなら不要です\n    \nhttps://www.switch-science.com/products/8905?_pos=1\u0026_sid=9f5f36bbd\u0026_ss=r\n    \n　\n・**microSD 16GB**\n 　M5Stackの仕様上、16GBが最も安定すると言われているため、16GBを購入しました。\n\nhttps://amzn.asia/d/0aJFM7nz\n\n　\n・**M3 Screw Kit**\n　`Core2 for AWS`版を分解なし手順（後述）で作成する場合、\n 　タカオ版組み立てキット付属のボルトでは長さが足りないので、別途こちらの購入が必要です。\n\n https://www.switch-science.com/products/6631\n\n　\n ・**六角レンチ**\n ・**精密ドライバー**\n 　100均に売っているものでOKでした。\n\n## M5stack側の準備（ｽﾀｯｸﾁｬﾝの頭）\nM5Stackをｽﾀｯｸﾁｬﾝとして動かすために、まずは以下の4ステップを行います。\n1) APIキーの取得\n2) SDカードに必要な情報を書き込み\n3) ファームウェアの準備\n4) ｽﾀｯｸﾁｬﾝの起動\n\n### 1.APIキーの取得\n取得方法については、こちらのREADMEでわかりやすく説明されています。\n\nhttps://github.com/robo8080/AI_StackChan2_README?tab=readme-ov-file\n\n:::note warn\n現在（2026/05/06）、ChatGPT（OpenAI）の無料クレジットは廃止されており、\n最低 $5 のクレジット購入が必要です。\n:::\n\n\n### 2.SDカードに必要な情報を書き込み\nSDカードのルートディレクトリに`wifi.txt` と `apikey.txt`を用意します。\n必要な情報はそれぞれ以下の通り。\n\n* wifi.txt\n1行目にWifiのSSID\n2行目にWifiのパスワード\n　\n* apikey.txt\n1行目にOpenAIのAPIキー\n2行目にVOICEVOXのAPIキー\n3行目にGoogle Cloud STT APIキー\n\n### 3.ファームウェアの書き込み\nM5Burnerを使用してファームウェアを書き込みます。\n\n#### 必要なドライバ\n* M5Burner Win10 x64 v3.0\n* CH9102_VCP_SER_Windows（USBシリアルドライバ）\n\nダウンロードはこちら：\n\nhttps://docs.m5stack.com/en/download\n\n#### M5Burnerの操作\n1) M5Burnerを起動\n2) 左側のカテゴリから CORE2 \u0026 TOUGH を選択\n3) 検索窓に「AI」と入力\n4) 以下のファームウェアを選択\n\n**AIスタックチャン2 (FY24/7月 証明書更新＋ChatGPT-4o mini) 0.0.10 (GPT-4o mini) サーボ: Port.A版**\n\n:::note warn\nPort.C版もありますが、タカオ版筐体の場合は基本 Port.A（正面から見て左側） にサーボを接続します。\nもし 正面上側（Port.C）にサーボを挿したい場合のみ Port.C版 を選んでください。\n:::\n\n### 4.ｽﾀｯｸﾁｬﾝの起動\n先ほど準備したものを使い、ｽﾀｯｸﾁｬﾝを起動させます。\n1) M5StackにSDカードを差し込む\n2) M5StackとPCをUSB接続する\n3) AIスタックチャン2 (FY24/7月 証明書更新＋ChatGPT-4o mini)をBurnする\n\nWi-Fiに接続成功すると、ｽﾀｯｸﾁｬﾝのお顔が表示されます。\n\n\n## ｽﾀｯｸﾁｬﾝの組み立て\nスタックチャンの組み立てには、\n**内蔵バッテリーを外す「分解あり手順」** と\n**バッテリーを外さない「分解なし手順」**  \nの2種類があります。\n簡単に違いを説明します。\n\n### 分解あり手順の場合\nM5Stack Core2 / Core2 for AWS の背面を開けて、内蔵バッテリーを取り外す方式\n重心が安定し、見た目もスッキリします。\n\nただし、Core2 for AWS のバッテリーは強力な両面テープで固定されているため、\n無理に剥がすとバッテリーを損傷する危険があります。\n\n:::note warn\nCore2 for AWS のバッテリーは強力に貼り付いています。\n無理に剥がすと 膨張・発煙・最悪発火のリスク があるため、\n初心者は分解なし手順を推奨します。\n:::\n\n### 分解なし手順の場合\n内蔵バッテリーをそのまま残して組み立てる方式\n安全で簡単\nただし、バッテリーの厚み分だけ 頭が少し大きくなる（頭でっかち）\n私は安全性を優先して、分解なし手順で組み立てました。\n\n\n### 組み立て手順について\n組み立て手順そのものは、先駆者の方々が非常に分かりやすくまとめてくださっています。\n以下の解説を参考に進めるのが確実です。\n分解なし手順で参考にしたサイトをまとめています。\n\nhttps://raspberrypi.mongonta.com/how-to-build-easy-stackchan-m5gobottom/\n\nhttps://zenn.dev/ryo_tsukuda/scraps/353d7931ec44f4\n\n## ｽﾀｯｸﾁｬﾝの完成\n分解なし手順で無事に組み立てが完了しました。\n内蔵バッテリーを残した分、少し頭でっかちにはなりますが、これはこれで愛嬌があります。\n完成したスタックチャンの写真はこんな感じです。\n\n![20260414_132050593_iOS.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4245000/5539e03f-4932-4595-98e9-a1848f3aeca4.jpeg)\n\nそして、何もしていないときでも キョロキョロと周りを見回していて、ｶﾜｲｲです。\nただ置いておくだけで癒やされます。\n\n![Adobe Express - 20260414_134133000_iOS.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4245000/1444a326-f681-4fdc-997d-78577511a5c9.gif)\n\n\n## 参考にさせていただいたサイト（まとめ）\n今回スタックチャンを組み立てるにあたり、以下のサイトを参考にさせていただきました。\nどれも非常に分かりやすく、初めての方でも安心して進められる内容になっています。\n\nhttps://note.com/mochi_mochi_lab/n/n45aa4cd54f89\n\nhttps://zenn.dev/ryo_tsukuda/scraps/353d7931ec44f4\n\nhttps://qiita.com/seigot/items/6be5d199afada1e63729\n\nhttps://raspberrypi.mongonta.com/how-to-build-easy-stackchan-m5gobottom/\n\nまた、X（旧Twitter）でも多くの方がノウハウや改造例を投稿されています。\n「ｽﾀｯｸﾁｬﾝ」「Stack-chan」などで検索すると、最新の情報がたくさん見つかるので、エゴサするのもオススメです。\n\n\n## おわりに\n今回は AIｽﾀｯｸﾁｬﾝを組み立ててみました。\nとはいえ、まだまだ改善したいところがあって、\n\n* 音声の聞き取りが安定しない\n* サーボの動きが少し渋い\n* たまに反応が遅れる\n\nなど、課題はたくさんあります・・・。\n作ってみて分かったのは、スタックチャンは“完成してからが本番\"ということ。\n動かすほど愛着が湧いて、気づいたら改造したくなってしまいますね。\n改善や進捗があれば、また投稿します。\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:23:13+09:00","group":null,"id":"40e5086091cdd962c290","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"電子工作","versions":[]},{"name":"M5stack","versions":[]},{"name":"ｽﾀｯｸﾁｬﾝ","versions":[]}],"title":"AIｽﾀｯｸﾁｬﾝを作ってみた","updated_at":"2026-05-06T11:23:13+09:00","url":"https://qiita.com/denden888/items/40e5086091cdd962c290","user":{"description":"普段は組込みソフトウェアエンジニアとして働いています。\r\n最近は電子工作にも興味があり、いろいろ試しています🤖","facebook_id":"","followees_count":0,"followers_count":5,"github_login_name":null,"id":"denden888","items_count":13,"linkedin_id":"","location":"","name":"tasyu","organization":"","permanent_id":4245000,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/4245000/317436bf304548e44d15fa47230b05ce5f23af85/x_large.png?1762136660","team_only":false,"twitter_screen_name":null,"website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:18\"\u003e\n\u003cspan id=\"1-はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. はじめに\u003c/h2\u003e\n\u003cul data-sourcepos=\"2:1-4:0\"\u003e\n\u003cli data-sourcepos=\"2:1-2:73\"\u003e\n\u003cstrong\u003e対象読者:\u003c/strong\u003e CTF初心者、Linuxの権限周りを学びたい人\u003c/li\u003e\n\u003cli data-sourcepos=\"3:1-4:0\"\u003e\n\u003cstrong\u003e記事の目的:\u003c/strong\u003e picoCTFの問題を通して、sudo権限が設定された環境で、許可されたバイナリ（今回はEmacs）を利用して特権昇格する方法を解説する\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"5:1-5:21\"\u003e\n\u003cspan id=\"2-問題の概要\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-%E5%95%8F%E9%A1%8C%E3%81%AE%E6%A6%82%E8%A6%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. 問題の概要\u003c/h2\u003e\n\u003cul data-sourcepos=\"6:1-9:0\"\u003e\n\u003cli data-sourcepos=\"6:1-6:40\"\u003e\n\u003cstrong\u003e問題名:\u003c/strong\u003e SUDO MAKE ME A SANDWICH\u003c/li\u003e\n\u003cli data-sourcepos=\"7:1-7:23\"\u003e\n\u003cstrong\u003e難易度:\u003c/strong\u003e　Easy\u003c/li\u003e\n\u003cli data-sourcepos=\"8:1-9:0\"\u003e\n\u003cstrong\u003eカテゴリ:\u003c/strong\u003e　GeneralSkill\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"10:1-10:12\"\u003e\n\u003cspan id=\"3-状況\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-%E7%8A%B6%E6%B3%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. 状況\u003c/h2\u003e\n\u003cp data-sourcepos=\"11:1-14:55\"\u003eインスタンス起動で出てくるsshコマンドとパスワードを使ってwslから接続\u003cbr\u003e\n\u003ccode\u003els\u003c/code\u003eで見てみると、flag.txtファイルがある\u003cbr\u003e\nしかし\u003ccode\u003ecat flag.txt\u003c/code\u003e を実行しても \u003ccode\u003ePermission denied\u003c/code\u003e で読めない\u003cbr\u003e\n\u003ccode\u003esudo cat flag.txt\u003c/code\u003eを実行しても読み込めない\u003c/p\u003e\n\u003ch2 data-sourcepos=\"16:1-16:51\"\u003e\n\u003cspan id=\"4-列挙enumerationsudo権限の確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E5%88%97%E6%8C%99enumerationsudo%E6%A8%A9%E9%99%90%E3%81%AE%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. 列挙（Enumeration）：sudo権限の確認\u003c/h2\u003e\n\u003cp data-sourcepos=\"17:1-18:38\"\u003e\u003ccode\u003esudo -l\u003c/code\u003e コマンドで、sudo権限で何が実行できるのか確認する\u003cbr\u003e\nemacsが実行可能と書いてある\u003c/p\u003e\n\u003cblockquote data-sourcepos=\"19:1-19:28\"\u003e\n\u003cp data-sourcepos=\"19:3-19:28\"\u003e(ALL) NOPASSWD: /bin/emacs\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 data-sourcepos=\"21:1-21:44\"\u003e\n\u003cspan id=\"4-攻略emacsからのエスケープ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E6%94%BB%E7%95%A5emacs%E3%81%8B%E3%82%89%E3%81%AE%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. 攻略：Emacsからのエスケープ\u003c/h2\u003e\n\u003cp data-sourcepos=\"22:1-26:54\"\u003eEmacs内からシェルを起動する\u003cbr\u003e\n\u003ccode\u003esudo emacs -nw\u003c/code\u003e で起動。\u003cbr\u003e\nCtrl + x を押し、指を離してから Ctrl + f を押す（Find File）\u003cbr\u003e\n画面の一番下に Find file: ~/ と表示されるので、バックスペースで消して\u003ccode\u003e/home/ctf-player/flag.txt\u003c/code\u003eと入力して Enter を押す\u003cbr\u003e\nflag.txtの中身（flag文字列）が表示される\u003c/p\u003e\n\u003ch2 data-sourcepos=\"28:1-28:15\"\u003e\n\u003cspan id=\"6-まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#6-%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6. まとめ\u003c/h2\u003e\n\u003cul data-sourcepos=\"29:1-30:49\"\u003e\n\u003cli data-sourcepos=\"29:1-29:55\"\u003esudoで何ができるかを調べるには\u003ccode\u003esudo -l\u003c/code\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"30:1-30:49\"\u003eemacsからシェルコマンドを動かせる\u003c/li\u003e\n\u003c/ul\u003e\n","body":"## 1. はじめに\n* **対象読者:** CTF初心者、Linuxの権限周りを学びたい人\n* **記事の目的:** picoCTFの問題を通して、sudo権限が設定された環境で、許可されたバイナリ（今回はEmacs）を利用して特権昇格する方法を解説する\n\n## 2. 問題の概要\n* **問題名:** SUDO MAKE ME A SANDWICH\n* **難易度:**　Easy\n* **カテゴリ:**　GeneralSkill\n\n## 3. 状況\nインスタンス起動で出てくるsshコマンドとパスワードを使ってwslから接続\n`ls`で見てみると、flag.txtファイルがある\nしかし`cat flag.txt` を実行しても `Permission denied` で読めない\n`sudo cat flag.txt`を実行しても読み込めない\n\n## 4. 列挙（Enumeration）：sudo権限の確認\n`sudo -l` コマンドで、sudo権限で何が実行できるのか確認する\nemacsが実行可能と書いてある\n\u003e (ALL) NOPASSWD: /bin/emacs\n\n## 4. 攻略：Emacsからのエスケープ\nEmacs内からシェルを起動する\n`sudo emacs -nw` で起動。\nCtrl + x を押し、指を離してから Ctrl + f を押す（Find File）\n画面の一番下に Find file: ~/ と表示されるので、バックスペースで消して`/home/ctf-player/flag.txt`と入力して Enter を押す\nflag.txtの中身（flag文字列）が表示される\n\n## 6. まとめ\n* sudoで何ができるかを調べるには`sudo -l` \n* emacsからシェルコマンドを動かせる\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:19:51+09:00","group":null,"id":"9edf44a5354a682ee82a","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"Emacs","versions":[]},{"name":"Linux","versions":[]},{"name":"CTF","versions":[]},{"name":"WSL","versions":[]},{"name":"picoCTF","versions":[]}],"title":"[picoCTF_WriteUP]「SUDO MAKE ME A SANDWICH」sudo権限の不備","updated_at":"2026-05-06T11:19:51+09:00","url":"https://qiita.com/Wakako19972899/items/9edf44a5354a682ee82a","user":{"description":"化学系の仕事ののち、現在はITセキュリティに関わる仕事をしています。\r\n競技プログラミングとCTFが好き。","facebook_id":"","followees_count":2,"followers_count":0,"github_login_name":"wakakochiba","id":"Wakako19972899","items_count":25,"linkedin_id":"wakako-ando-649b80243","location":"","name":"Wakako","organization":"","permanent_id":353370,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/353370/3e47b3b24ec693b49f99f1e6a2b928288527f414/x_large.png?1584228437","team_only":false,"twitter_screen_name":"Soymilk19972899","website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cp data-sourcepos=\"1:1-1:617\"\u003e2026年5月1日、株式会社マネーフォワードは、ソフトウェア開発およびシステム管理に利用していた GitHub の認証情報が漏えいし、その認証情報を用いた第三者による不正アクセスが発生、GitHub 内のリポジトリがコピーされたことを公表しました。(\u003ca href=\"https://corp.moneyforward.com/news/info/20260501-mf-press-1/%22\" rel=\"nofollow noopener\" target=\"_blank\"\u003e株式会社マネーフォワード\u003c/a\u003e) また、同社は不正アクセスの経路となった認証情報の無効化、アカウントの遮断、ソースコードに含まれる各種認証キー・パスワードの無効化と再発行を進めたと説明しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"3:1-3:189\"\u003e本記事では、このインシデントを題材に、「機密情報をソースコードや平文ファイルに置くと何が起き得るのか」を漫画形式で整理します。\u003c/p\u003e\n\u003cdiv data-sourcepos=\"6:1-10:3\" class=\"note info\"\u003e\n\u003cspan class=\"fa fa-fw fa-check-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"7:1-9:105\"\u003e本漫画は、公式発表で確認できる内容をもとにした教育目的の再構成です。\u003cbr\u003e\n認証情報の漏えい経路、検知方法、社内対応の詳細は公開されていないため、一部は一般的なインシデント対応の流れを参考に筆者が補完しています。\u003cbr\u003e\n実際の社内手順を示すものではなく、特定企業を批判する意図もありません。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"12:1-12:142\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2Fb7b18dec-d676-4284-8a35-19c7d67930b9.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=be2e09e00722b6f2c56c0167dfbe921b\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2Fb7b18dec-d676-4284-8a35-19c7d67930b9.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=be2e09e00722b6f2c56c0167dfbe921b\" alt=\"マネーフォワード1.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2Fb7b18dec-d676-4284-8a35-19c7d67930b9.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=9c250e7e518883b4709dd1dd563be41a 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/b7b18dec-d676-4284-8a35-19c7d67930b9.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"14:1-14:142\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F72394d71-52f5-4e07-81e0-78d1e9a5e49a.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=baac1450df0505db32199ce4f78f6344\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F72394d71-52f5-4e07-81e0-78d1e9a5e49a.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=baac1450df0505db32199ce4f78f6344\" alt=\"マネーフォワード2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F72394d71-52f5-4e07-81e0-78d1e9a5e49a.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=e1c4bde69cca0bd21be4d405b659e79c 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/72394d71-52f5-4e07-81e0-78d1e9a5e49a.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"16:1-16:142\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F7bbe4415-fc9e-4e18-9f28-3761341b182d.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=99947c9b9a3efbd42edad320566f7f45\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F7bbe4415-fc9e-4e18-9f28-3761341b182d.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=99947c9b9a3efbd42edad320566f7f45\" alt=\"マネーフォワード3.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F7bbe4415-fc9e-4e18-9f28-3761341b182d.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=25239f3331c1cfb0c4da438f6f65ab52 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/7bbe4415-fc9e-4e18-9f28-3761341b182d.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"18:1-18:142\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F6ace3419-68a9-470a-b0cd-bb12a2ce7e41.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=bc59eea9771b84e48ff3256dd53265b1\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F6ace3419-68a9-470a-b0cd-bb12a2ce7e41.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=bc59eea9771b84e48ff3256dd53265b1\" alt=\"マネーフォワード4.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2F6ace3419-68a9-470a-b0cd-bb12a2ce7e41.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=a1bbfe2a12cff449febcaf0931348545 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/6ace3419-68a9-470a-b0cd-bb12a2ce7e41.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"20:1-20:142\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2Fa9125c6b-5946-4dd3-955c-9af546e03217.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f15fb593cba12a3954706fa5151b7a65\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2Fa9125c6b-5946-4dd3-955c-9af546e03217.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f15fb593cba12a3954706fa5151b7a65\" alt=\"マネーフォワード5.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4289265%2Fa9125c6b-5946-4dd3-955c-9af546e03217.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=1ae3789496a813bb0ff38c0ca7b353fe 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/a9125c6b-5946-4dd3-955c-9af546e03217.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"23:1-23:12\"\u003e\n\u003cspan id=\"まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eまとめ\u003c/h2\u003e\n\u003cp data-sourcepos=\"25:1-25:66\"\u003e今回の漫画で伝えたいポイントはシンプルです。\u003c/p\u003e\n\u003cp data-sourcepos=\"27:1-27:70\"\u003e\u003cstrong\u003e認証情報は「書かない・置かない・見せない」。\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"29:1-29:174\"\u003e便利だからといって、ソースコードや平文ファイルにシークレットを置いてしまうと、漏えい時の影響範囲が一気に広がります。\u003c/p\u003e\n\u003cp data-sourcepos=\"31:1-31:45\"\u003e日頃から以下を意識したいです。\u003c/p\u003e\n\u003cul data-sourcepos=\"33:1-37:0\"\u003e\n\u003cli data-sourcepos=\"33:1-33:47\"\u003eシークレット管理サービスを使う\u003c/li\u003e\n\u003cli data-sourcepos=\"34:1-34:47\"\u003eトークンには最小権限を設定する\u003c/li\u003e\n\u003cli data-sourcepos=\"35:1-35:50\"\u003e使わなくなった認証情報は削除する\u003c/li\u003e\n\u003cli data-sourcepos=\"36:1-37:0\"\u003e定期的にローテーションする\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"38:1-39:162\"\u003eセキュリティ事故は、どの組織でも起こり得ます。\u003cbr\u003e\nだからこそ、個別の事例を責めるのではなく、そこから学び、自分たちの開発・運用に活かすことが大切だと思います。\u003c/p\u003e\n\u003cdiv data-sourcepos=\"41:1-44:3\" class=\"note warn\"\u003e\n\u003cspan class=\"fa fa-fw fa-exclamation-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"42:1-43:147\"\u003e本記事は個人による学習・啓発目的の記事であり、各社の公式見解を示すものではありません。\u003cbr\u003e\n漫画内のキャラクター、画面、会話、検知・対応フローの一部は、公開情報をもとにした筆者の再構成です。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n","body":"2026年5月1日、株式会社マネーフォワードは、ソフトウェア開発およびシステム管理に利用していた GitHub の認証情報が漏えいし、その認証情報を用いた第三者による不正アクセスが発生、GitHub 内のリポジトリがコピーされたことを公表しました。([株式会社マネーフォワード][1]) また、同社は不正アクセスの経路となった認証情報の無効化、アカウントの遮断、ソースコードに含まれる各種認証キー・パスワードの無効化と再発行を進めたと説明しています。\n\n本記事では、このインシデントを題材に、「機密情報をソースコードや平文ファイルに置くと何が起き得るのか」を漫画形式で整理します。\n\n\n:::note info\n本漫画は、公式発表で確認できる内容をもとにした教育目的の再構成です。\n認証情報の漏えい経路、検知方法、社内対応の詳細は公開されていないため、一部は一般的なインシデント対応の流れを参考に筆者が補完しています。\n実際の社内手順を示すものではなく、特定企業を批判する意図もありません。\n:::\n\n![マネーフォワード1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/b7b18dec-d676-4284-8a35-19c7d67930b9.png)\n\n![マネーフォワード2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/72394d71-52f5-4e07-81e0-78d1e9a5e49a.png)\n\n![マネーフォワード3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/7bbe4415-fc9e-4e18-9f28-3761341b182d.png)\n\n![マネーフォワード4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/6ace3419-68a9-470a-b0cd-bb12a2ce7e41.png)\n\n![マネーフォワード5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289265/a9125c6b-5946-4dd3-955c-9af546e03217.png)\n\n\n## まとめ\n\n今回の漫画で伝えたいポイントはシンプルです。\n\n**認証情報は「書かない・置かない・見せない」。**\n\n便利だからといって、ソースコードや平文ファイルにシークレットを置いてしまうと、漏えい時の影響範囲が一気に広がります。\n\n日頃から以下を意識したいです。\n\n* シークレット管理サービスを使う\n* トークンには最小権限を設定する\n* 使わなくなった認証情報は削除する\n* 定期的にローテーションする\n\nセキュリティ事故は、どの組織でも起こり得ます。\nだからこそ、個別の事例を責めるのではなく、そこから学び、自分たちの開発・運用に活かすことが大切だと思います。\n\n:::note warn\n本記事は個人による学習・啓発目的の記事であり、各社の公式見解を示すものではありません。\n漫画内のキャラクター、画面、会話、検知・対応フローの一部は、公開情報をもとにした筆者の再構成です。\n:::\n\n[1]: https://corp.moneyforward.com/news/info/20260501-mf-press-1/\"\n\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:17:55+09:00","group":null,"id":"1ff8efdf59880f8eb3a4","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"GitHub","versions":[]},{"name":"Security","versions":[]},{"name":"不正アクセス","versions":[]},{"name":"シークレット管理","versions":[]},{"name":"認証情報管理","versions":[]}],"title":"GitHub認証情報漏えいインシデントを漫画で学ぶ：シークレット管理の大切さ","updated_at":"2026-05-06T11:17:55+09:00","url":"https://qiita.com/Chisho/items/1ff8efdf59880f8eb3a4","user":{"description":"","facebook_id":"","followees_count":1,"followers_count":2,"github_login_name":null,"id":"Chisho","items_count":22,"linkedin_id":"","location":"","name":"","organization":"","permanent_id":4289265,"profile_image_url":"https://secure.gravatar.com/avatar/71a00758dc2c3dce20f380f1cfdfc847","team_only":false,"twitter_screen_name":null,"website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:17\"\u003e\n\u003cspan id=\"1-はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. はじめに\u003c/h1\u003e\n\u003cp data-sourcepos=\"3:1-4:173\"\u003e今回はYouTubeの動画「【初心者完全版】0からReactでCI/CD構築までできるチュートリアル【GitHub Actions/Firebase/Vitest/TypeScript】」を参考に、React + TypeScript + ViteでシンプルなTodoアプリを作りながら、CI/CDパイプラインを構築するまでの流れを学びました。\u003cbr\u003e\n「CI/CDって上級者向けでしょ」と思っていたのですが、個人開発レベルなら思ってたより全然シンプルに始められるみたいです。\u003c/p\u003e\n\u003cp data-sourcepos=\"6:1-6:83\"\u003e\u003ciframe id=\"qiita-embed-content__db9de38889bab75ed2cf4eb851096aa2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__db9de38889bab75ed2cf4eb851096aa2\" data-content=\"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dn0fHY-uX2KY%26list%3DPL89AUoaDo0E9AliMM1xwvTE39UtUeSy3H\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"8:1-9:159\"\u003e具体的には以下の技術スタックを使いました。\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F19a69b34-3ce2-4eae-a377-5603ff5a2255.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=7c46697765bffdfc903294e76edac084\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F19a69b34-3ce2-4eae-a377-5603ff5a2255.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=7c46697765bffdfc903294e76edac084\" alt=\"9954d9b3-259d-4e3d-af97-40958410bf31_r1_c2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F19a69b34-3ce2-4eae-a377-5603ff5a2255.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=7b85aebff51c1eda21265e823489a2d0 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/19a69b34-3ce2-4eae-a377-5603ff5a2255.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"11:1-16:0\"\u003e\n\u003cli data-sourcepos=\"11:1-11:55\"\u003e\n\u003cstrong\u003eフロントエンド\u003c/strong\u003e：React + TypeScript + Vite\u003c/li\u003e\n\u003cli data-sourcepos=\"12:1-12:39\"\u003e\n\u003cstrong\u003eスタイリング\u003c/strong\u003e：Tailwind CSS\u003c/li\u003e\n\u003cli data-sourcepos=\"13:1-13:48\"\u003e\n\u003cstrong\u003e自動テスト\u003c/strong\u003e：Vitest + Testing Library\u003c/li\u003e\n\u003cli data-sourcepos=\"14:1-14:40\"\u003e\n\u003cstrong\u003eデプロイ先\u003c/strong\u003e：Firebase Hosting\u003c/li\u003e\n\u003cli data-sourcepos=\"15:1-16:0\"\u003e\n\u003cstrong\u003eCI/CDパイプライン\u003c/strong\u003e：GitHub Actions\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 data-sourcepos=\"17:1-17:16\"\u003e\n\u003cspan id=\"2-cicdとは\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-cicd%E3%81%A8%E3%81%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. CI/CDとは\u003c/h1\u003e\n\u003cp data-sourcepos=\"18:1-18:160\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F64da1e18-3d16-4640-8014-523b2c6409a4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=d9f8b417201cb12cf5f2c2a52cafede5\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F64da1e18-3d16-4640-8014-523b2c6409a4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=d9f8b417201cb12cf5f2c2a52cafede5\" alt=\"9954d9b3-259d-4e3d-af97-40958410b1f31_r1_c2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F64da1e18-3d16-4640-8014-523b2c6409a4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=c7006b65d9a78c77ff40789a23eafe64 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/64da1e18-3d16-4640-8014-523b2c6409a4.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"20:1-20:23\"\u003e\n\u003cspan id=\"21-ciとcdの違い\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#21-ci%E3%81%A8cd%E3%81%AE%E9%81%95%E3%81%84\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.1 CIとCDの違い\u003c/h2\u003e\n\u003cp data-sourcepos=\"22:1-23:135\"\u003e**CI（継続的インテグレーション）**とは、コードをGitHubにプッシュした瞬間に自動でビルドとテストが走る仕組みのようです。\u003cbr\u003e\n**CD（継続的デリバリー）**とは、テストが全て通ったら自動でデプロイまで行う仕組みのようです。\u003c/p\u003e\n\u003cp data-sourcepos=\"25:1-25:199\"\u003eこの2つが組み合わさることで、「コードを書く→プッシュ→自動テスト→自動デプロイ→世界に公開」という流れが全て自動化されるみたいです。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"27:1-27:36\"\u003e\n\u003cspan id=\"22-cicdがない場合の問題\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#22-cicd%E3%81%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AE%E5%95%8F%E9%A1%8C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.2 CI/CDがない場合の問題\u003c/h2\u003e\n\u003cp data-sourcepos=\"29:1-31:167\"\u003eCI/CDがない現場だと、半年間コードを書き続けてまとめてリリースする、いわゆるビッグバンリリースになりがちなようです。\u003cbr\u003e\nその結果、手動テストでバグが大量に見つかってリリースが延期になるといった問題が起きやすいと理解しました。\u003cbr\u003e\nCI/CDを導入すると細かい単位でデプロイするので、バグが見つかっても影響範囲が狭く済むのが大きなメリットみたいです。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"33:1-33:27\"\u003e\n\u003cspan id=\"3-todoアプリの実装\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-todo%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E5%AE%9F%E8%A3%85\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. Todoアプリの実装\u003c/h1\u003e\n\u003cp data-sourcepos=\"34:1-34:160\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F81facde2-b4e0-4038-9f9b-0337db7b0599.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=4ae33e0fc8c28ed5d1a30229da5ed966\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F81facde2-b4e0-4038-9f9b-0337db7b0599.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=4ae33e0fc8c28ed5d1a30229da5ed966\" alt=\"9954d9b3-259d-4e13d-af97-40958410bf31_r1_c2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F81facde2-b4e0-4038-9f9b-0337db7b0599.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=c462570c6ab6e9332acc886fe5ddf914 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/81facde2-b4e0-4038-9f9b-0337db7b0599.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"36:1-36:67\"\u003e今回作ったTodoアプリのコード全体を確認します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"38:1-126:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003euseState\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ereact\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e./index.css\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// Todoオブジェクトの型定義（TypeScriptで型安全にするため）\u003c/span\u003e\n\u003cspan class=\"nx\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eTodo\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003enumber\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n  \u003cspan class=\"nl\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n  \u003cspan class=\"nl\"\u003ecompleted\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003eboolean\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eApp\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// TodoリストをStateで管理（変更があると画面が自動で再レンダリングされる）\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esetTodos\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003euseState\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003eTodo\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e([]);\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// 入力フォームの文字列をStateで管理\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esetTitle\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003euseState\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e''\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// 追加ボタンを押した時にTodoを追加する処理\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehandleAddTodo\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// titleが空の場合は何もしない（空のTodoを追加させない）\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// スプレッド構文で新しい配列を作ってStateを更新する\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// pushで直接変更するとReactが変化を検知できないため必ずこの形にする\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003esetTodos\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003ecompleted\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// 追加後はフォームをリセット\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003esetTitle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e''\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// チェックボックスを押した時にcompletedを反転させる処理\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehandleToggleTodo\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003enumber\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// mapは新しい配列を返すのでスプレッド構文なしでStateの更新ができる\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003esetTodos\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n      \u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// IDが一致するTodoだけcompletedを反転し、それ以外はそのまま返す\u003c/span\u003e\n        \u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"nx\"\u003eid\u003c/span\u003e\n          \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"na\"\u003ecompleted\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecompleted\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n          \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e);\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// filterでcompletedがtrueのものだけ取り出して件数を数える\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ecompletedCount\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003efilter\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecompleted\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n  \u003cspan class=\"k\"\u003ereturn \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ediv\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003eh1\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eTodoアプリ\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003eh1\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003einput\u003c/span\u003e\n        \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"text\"\u003c/span\u003e\n        \u003cspan class=\"na\"\u003eplaceholder\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"新しいタスクを入力\"\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// valueにStateを渡すことでフォームとStateを同期させる\u003c/span\u003e\n        \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etitle\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// 文字が入力されるたびにtitleのStateを更新する\u003c/span\u003e\n        \u003cspan class=\"na\"\u003eonChange\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003esetTitle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e/\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e \u003cspan class=\"na\"\u003eonClick\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003ehandleAddTodo\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e追加\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\n      \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"cm\"\u003e/* todosが1件以上あればリストを表示、なければメッセージを表示 */\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n      \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003eul\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n          \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003eli\u003c/span\u003e \u003cspan class=\"na\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n              \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003einput\u003c/span\u003e\n                \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"checkbox\"\u003c/span\u003e\n                \u003cspan class=\"c1\"\u003e// completedの値でチェック状態を制御する\u003c/span\u003e\n                \u003cspan class=\"na\"\u003echecked\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecompleted\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n                \u003cspan class=\"na\"\u003eonChange\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandleToggleTodo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n              \u003cspan class=\"p\"\u003e/\u0026gt;\u003c/span\u003e\n              \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etitle\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n            \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003eli\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n          \u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n        \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003eul\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eタスクがありません。新しいタスクを追加してください。\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n\n      \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"cm\"\u003e/* todosが1件以上ある時だけ完了件数を表示する */\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n      \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e完了済み \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003ecompletedCount\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e / \u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003etodos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ediv\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"k\"\u003edefault\u003c/span\u003e \u003cspan class=\"nx\"\u003eApp\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch1 data-sourcepos=\"128:1-128:50\"\u003e\n\u003cspan id=\"4-自動テストvitest--testing-library\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88vitest--testing-library\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. 自動テスト（Vitest + Testing Library）\u003c/h1\u003e\n\u003cp data-sourcepos=\"129:1-129:160\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F4def9de0-d77e-44a0-97dc-ead53aec8bb4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=1f8c897d52d183344da32a56a19a2ea4\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F4def9de0-d77e-44a0-97dc-ead53aec8bb4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=1f8c897d52d183344da32a56a19a2ea4\" alt=\"9954d9b3-259d-4e3d-af97-40958410bf311_r2_c2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F4def9de0-d77e-44a0-97dc-ead53aec8bb4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=428a28727a2296b6cb62050a40cceca1 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/4def9de0-d77e-44a0-97dc-ead53aec8bb4.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"131:1-132:137\"\u003eCI/CDを構築するうえで自動テストは欠かせない要素です。\u003cbr\u003e\nテストが通ったことを条件にデプロイするので、テストがなければCI/CDは成立しないと理解しました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"134:1-134:52\"\u003e\n\u003cspan id=\"41-パッケージのインストールと役割\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#41-%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%A8%E5%BD%B9%E5%89%B2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4.1 パッケージのインストールと役割\u003c/h2\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"136:1-139:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// 開発用パッケージとしてインストール（本番ビルドには含まれない）\u003c/span\u003e\n\u003cspan class=\"nx\"\u003enpm\u003c/span\u003e \u003cspan class=\"nx\"\u003einstall\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003eD\u003c/span\u003e \u003cspan class=\"nx\"\u003evitest\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"nd\"\u003etesting\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003elibrary\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"nx\"\u003ereact\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"nd\"\u003etesting\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003elibrary\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"nx\"\u003edom\u003c/span\u003e \u003cspan class=\"p\"\u003e@\u003c/span\u003e\u003cspan class=\"nd\"\u003etesting\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003elibrary\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"nx\"\u003ejest\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003edom\u003c/span\u003e \u003cspan class=\"nx\"\u003ejsdom\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"141:1-141:87\"\u003eインストールするパッケージの役割はそれぞれ以下のようです。\u003c/p\u003e\n\u003cp data-sourcepos=\"143:1-143:10\"\u003e\u003cstrong\u003evitest\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"145:1-146:207\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003evitest\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eはViteと相性の良いテストフレームワークで、テスト全体を動かすエンジン部分になります。\u003cbr\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003etest\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003e・\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003edescribe\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003e・\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003eexpect\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eといったテストの骨格を作る関数が使えるようになります。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"148:1-159:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003edescribe\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eexpect\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003evitest\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// describe：テストケースをグループにまとめる（どのコンポーネントのテストかを示す）\u003c/span\u003e\n\u003cspan class=\"nf\"\u003edescribe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eAppコンポーネント\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// test：1つのテストケースを定義する（第1引数がテストの仕様説明になる）\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eタイトルが表示されている\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// expect：実際の値と期待値が一致するかを検証する\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"161:1-161:26\"\u003e\u003cstrong\u003e@testing-library/react\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"163:1-164:171\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e@testing-library/react\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eはReactコンポーネントを仮想的にレンダリングするためのライブラリです。\u003cbr\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003erender\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eでコンポーネントを描画し、\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003escreen\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eで描画されたHTML要素を取得できます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"166:1-179:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e@testing-library/react\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nx\"\u003eApp\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e../App\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eタイトルが表示されている\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// render：テスト内でAppコンポーネントを仮想的に描画する\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// ブラウザを開かなくてもHTMLが生成される\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// screen.getByRole：描画されたHTMLからロールと名前で要素を取得する\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// ユーザーが画面を見る目線で要素を特定するのがポイント\u003c/span\u003e\n  \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eheading\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eheading\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eTodoアプリ\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"181:1-181:24\"\u003e\u003cstrong\u003e@testing-library/dom\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"183:1-184:142\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e@testing-library/dom\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eは描画されたHTMLに対してユーザー操作を模倣するための関数を提供するライブラリです。\u003cbr\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003efireEvent\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eでクリックや入力といったブラウザイベントをテスト内で再現できます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"186:1-196:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e@testing-library/react\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// fireEvent.change：inputに文字が入力されたイベントを発火する\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// 実際にキーボードで入力した時と同じ状態を作り出す\u003c/span\u003e\n\u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003echange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eテストタスク\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// fireEvent.click：ボタンがクリックされたイベントを発火する\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// 実際にマウスでクリックした時と同じ状態を作り出す\u003c/span\u003e\n\u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ebutton\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"198:1-198:29\"\u003e\u003cstrong\u003e@testing-library/jest-dom\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"200:1-201:232\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e@testing-library/jest-dom\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eはDOMに特化したマッチャーを追加するライブラリです。\u003cbr\u003e\nこのライブラリを入れることで\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003etoBeInTheDocument()\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eや\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003etoBeChecked()\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eといった直感的な検証メソッドが使えるようになるみたいです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"203:1-209:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// toBeInTheDocument：その要素が画面上に存在するかを確認する\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eheading\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeInTheDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// toBeChecked：チェックボックスがチェック済み状態かを確認する\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckbox\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeChecked\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"211:1-211:9\"\u003e\u003cstrong\u003ejsdom\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"213:1-215:155\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003ejsdom\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eはNode.js環境上に仮想のブラウザ環境を作るためのライブラリです。\u003cbr\u003e\nテストはブラウザではなくNode.jsで実行されるので、DOMを扱うためにこの仮想環境が必要になるようです。\u003cbr\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003evitest.config.ts\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eで\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003eenvironment: 'jsdom'\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eと設定することで有効になります。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"217:1-217:34\"\u003e\n\u003cspan id=\"42-設定ファイルの準備\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#42-%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%BA%96%E5%82%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4.2 設定ファイルの準備\u003c/h2\u003e\n\u003cp data-sourcepos=\"219:1-219:86\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003evitest.config.ts\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eに以下の設定を書きます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"221:1-236:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003edefineConfig\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003evitest/config\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nx\"\u003ereact\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e@vitejs/plugin-react\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003eexport\u003c/span\u003e \u003cspan class=\"k\"\u003edefault\u003c/span\u003e \u003cspan class=\"nf\"\u003edefineConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n  \u003cspan class=\"na\"\u003eplugins\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nf\"\u003ereact\u003c/span\u003e\u003cspan class=\"p\"\u003e()],\u003c/span\u003e\n  \u003cspan class=\"na\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// jsdomで仮想ブラウザ環境を作りDOMを扱えるようにする\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eenvironment\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ejsdom\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// test・describe・expectをimportなしで使えるようにする\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eglobals\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// 全テストの実行前に必ず読み込まれるセットアップファイルを指定する\u003c/span\u003e\n    \u003cspan class=\"na\"\u003esetupFiles\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e./vitest.setup.ts\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"238:1-239:147\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003evitest.setup.ts\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eに以下を書きます。\u003cbr\u003e\n全テストの実行前に自動でインポートされるので、各テストファイルに個別に書く必要がなくなるようです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"241:1-244:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// toBeInTheDocumentなどのDOM用マッチャーを全テストで使えるようにする\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e@testing-library/jest-dom\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"246:1-246:37\"\u003e\n\u003cspan id=\"43-テストコードの全体像\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#43-%E3%83%86%E3%82%B9%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%85%A8%E4%BD%93%E5%83%8F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4.3 テストコードの全体像\u003c/h2\u003e\n\u003cp data-sourcepos=\"248:1-248:69\"\u003e以下がアプリ全体をカバーするテストコードです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"jsx\" data-sourcepos=\"250:1-347:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ewithin\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e@testing-library/react\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003edescribe\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eexpect\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003evitest\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nx\"\u003eApp\u003c/span\u003e \u003cspan class=\"k\"\u003efrom\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e../App\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// describe：Appコンポーネントに関するテストをひとまとめにする\u003c/span\u003e\n\u003cspan class=\"nf\"\u003edescribe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eApp\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\n  \u003cspan class=\"c1\"\u003e// test第1引数はアプリの「仕様」を書く\u003c/span\u003e\n  \u003cspan class=\"c1\"\u003e// CIでこのテストが落ちた時に「どの仕様が壊れたか」が一目でわかる\u003c/span\u003e\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eアプリタイトルが表示されている\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// render：仮想ブラウザ上にAppコンポーネントを描画する\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// screen.getByRole：headingロールを持つ「Todoアプリ」という名前の要素を取得する\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eheading\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eheading\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eTodoアプリ\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// toBeInTheDocument：取得した要素が画面上に存在することを確認する\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eheading\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeInTheDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eTodoを追加することができる\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// screen.getByRole：ユーザーが画面を見る目線で要素を特定する\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// textboxロールのinputとbuttonロールの追加ボタンを取得する\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003einput\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003etextbox\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sr\"\u003e/新しいタスクを入力/\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ebutton\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e追加\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// fireEvent.change：inputに「テストタスク」と入力したイベントを発火する\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003echange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eテストタスク\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// fireEvent.click：追加ボタンをクリックしたイベントを発火する\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// screen.getByRole：listロールの要素（ul）を取得する\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003elist\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003elist\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// within：listの中だけを対象に絞り込んで「テストタスク」を探す\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// withinを使うことでリスト外の同名テキストに誤反応しなくなる\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nf\"\u003ewithin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eテストタスク\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e)).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeInTheDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eTodoを完了することができる\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003einput\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003etextbox\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sr\"\u003e/新しいタスクを入力/\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ebutton\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e追加\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// まずTodoを1件追加してからチェックボックスを操作する\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003echange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eテストタスク\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// screen.getAllByRole：checkboxロールを持つ要素を全件配列で取得する\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003echeckboxes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetAllByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003echeckbox\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// fireEvent.click：最初のチェックボックスをクリックして完了状態にする\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckboxes\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// toBeChecked：チェックボックスがチェック済みになっているかを確認する\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckboxes\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeChecked\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e完了したTodoのカウントが表示されている\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003einput\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003etextbox\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sr\"\u003e/新しいタスクを入力/\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ebutton\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e追加\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// 2件追加して1件だけチェックする\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003echange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eテストタスク1\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003echange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eテストタスク2\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003echeckboxes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetAllByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003echeckbox\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// 最初の1件だけチェックして「完了済み 1 / 2」になることを確認する\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckboxes\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// screen.getByText：画面上に「完了済み 1 / 2」というテキストが存在するか確認する\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e完了済み 1 / 2\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e)).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeInTheDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eTodoがない場合はメッセージが表示されている\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// Todoを追加しない状態でレンダリングして初期メッセージを確認する\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n      \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eタスクがありません。新しいタスクを追加してください。\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeInTheDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n  \u003cspan class=\"nf\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e空のタイトルでTodoは追加されない\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003erender\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u0026lt;\u003c/span\u003e\u003cspan class=\"nc\"\u003eApp\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;);\u003c/span\u003e\n    \u003cspan class=\"kd\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByRole\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003ebutton\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003e追加\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// 何も入力せずに追加ボタンをクリックする\u003c/span\u003e\n    \u003cspan class=\"nx\"\u003efireEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddButton\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// 空追加後もメッセージが残ったままであることを確認する\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// もしtitleの空チェック処理が消えたらここでテストが落ちてCIが止まる\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n      \u003cspan class=\"nx\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003egetByText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"s1\"\u003eタスクがありません。新しいタスクを追加してください。\u003c/span\u003e\u003cspan class=\"dl\"\u003e'\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003etoBeInTheDocument\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"349:1-349:31\"\u003e\n\u003cspan id=\"44-テストのポイント\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#44-%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4.4 テストのポイント\u003c/h2\u003e\n\u003cp data-sourcepos=\"351:1-352:220\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003egetByRole\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eでユーザーが実際に画面を見た時の目線でHTML要素を取得するのがポイントのようです。\u003cbr\u003e\n「追加」という文字が書かれたボタンを\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003ebutton\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eロールで取得することで、ユーザーの操作をそのままテストに落とし込めると理解しました。\u003c/p\u003e\n\u003cp data-sourcepos=\"354:1-355:117\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003ewithin\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eを使うと特定の要素の中だけを対象に絞り込んで検索できます。\u003cbr\u003e\nリスト全体を対象にするより範囲が狭まるので、より確実なテストが書けるようです。\u003c/p\u003e\n\u003cdiv data-sourcepos=\"357:1-360:3\" class=\"note info\"\u003e\n\u003cspan class=\"fa fa-fw fa-check-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"358:1-359:200\"\u003eテストの説明文（第1引数の文字列）は「仕様書」として書くとよいみたいです。\u003cbr\u003e\nこのテストを読んだだけでアプリの仕様が分かるようにしておくと、CI/CDのパイプラインが落ちた時にどの仕様が壊れたかが一目で分かるようです。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch1 data-sourcepos=\"362:1-362:39\"\u003e\n\u003cspan id=\"5-firebase-hostingへのデプロイ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#5-firebase-hosting%E3%81%B8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5. Firebase Hostingへのデプロイ\u003c/h1\u003e\n\u003cp data-sourcepos=\"363:1-363:160\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2Fba6beb35-d4bf-4f3a-87d3-45e10c3546c7.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f8d76f788d682002e564641cc9bdb798\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2Fba6beb35-d4bf-4f3a-87d3-45e10c3546c7.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f8d76f788d682002e564641cc9bdb798\" alt=\"9954d9b3-259d-4e3d-af97-409584101bf31_r2_c2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2Fba6beb35-d4bf-4f3a-87d3-45e10c3546c7.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=21d2b60a317a68cc4cfa044370d84dc8 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/ba6beb35-d4bf-4f3a-87d3-45e10c3546c7.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"365:1-367:134\"\u003eFirebase Hostingとは、Googleが提供する静的サイトのホスティングサービスです。\u003cbr\u003e\nReactアプリをビルドしてできたファイルをFirebaseのサーバーに置くことで、インターネット上に公開できるみたいです。\u003cbr\u003e\n今回はまず手動でデプロイの流れを確認してから、CI/CDパイプラインに組み込む流れで進めました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"369:1-369:54\"\u003e\n\u003cspan id=\"51-firebaseプロジェクトの作成とcli設定\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#51-firebase%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8cli%E8%A8%AD%E5%AE%9A\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5.1 Firebaseプロジェクトの作成とCLI設定\u003c/h2\u003e\n\u003cp data-sourcepos=\"371:1-371:69\"\u003eまずFirebase CLIをグローバルにインストールします。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"373:1-376:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// グローバルインストール（どのディレクトリからでもfirebaseコマンドが使えるようになる）\u003c/span\u003e\n\u003cspan class=\"nx\"\u003enpm\u003c/span\u003e \u003cspan class=\"nx\"\u003einstall\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003eg\u003c/span\u003e \u003cspan class=\"nx\"\u003efirebase\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003etools\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"378:1-378:93\"\u003eその後、Firebase CLIでログインし、プロジェクトに設定を追加します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"380:1-386:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// Googleアカウントでログインする\u003c/span\u003e\n\u003cspan class=\"nx\"\u003efirebase\u003c/span\u003e \u003cspan class=\"nx\"\u003elogin\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// Hostingの初期化（firebase.jsonと.firebasercが自動生成される）\u003c/span\u003e\n\u003cspan class=\"nx\"\u003efirebase\u003c/span\u003e \u003cspan class=\"nx\"\u003einit\u003c/span\u003e \u003cspan class=\"nx\"\u003ehosting\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cdiv data-sourcepos=\"388:1-391:3\" class=\"note info\"\u003e\n\u003cspan class=\"fa fa-fw fa-check-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"389:1-390:169\"\u003e自分の環境では\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003efirebase.json\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eの\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e\"public\"\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eの値が最初から\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e\"dist\"\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eになっていました。\u003cbr\u003e\n動画内では異なる値になっていたので、もし\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e\"dist\"\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003e以外になっていた場合は手動で修正してください。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch2 data-sourcepos=\"393:1-393:34\"\u003e\n\u003cspan id=\"52-手動デプロイの確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#52-%E6%89%8B%E5%8B%95%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%AE%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5.2 手動デプロイの確認\u003c/h2\u003e\n\u003cp data-sourcepos=\"395:1-395:81\"\u003e設定が完了したら以下のコマンドだけでデプロイできます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"397:1-400:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// ビルドとデプロイをまとめて実行する\u003c/span\u003e\n\u003cspan class=\"nx\"\u003efirebase\u003c/span\u003e \u003cspan class=\"nx\"\u003edeploy\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"402:1-403:69\"\u003eURLが発行され、インターネット上に公開されるのが確認できました。\u003cbr\u003e\n思ったよりシンプルにデプロイできるみたいです。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"405:1-405:55\"\u003e\n\u003cspan id=\"53-サービスアカウント鍵のエンコード\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#53-%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E9%8D%B5%E3%81%AE%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5.3 サービスアカウント鍵のエンコード\u003c/h2\u003e\n\u003cp data-sourcepos=\"407:1-408:225\"\u003eCI/CD環境からFirebaseにデプロイするには、パイプラインにFirebaseの認証情報を渡す必要があります。\u003cbr\u003e\nGCPでサービスアカウントを作成してダウンロードした鍵ファイルをそのまま使うのは危険なので、Base64でエンコードしてからGitHub ActionsのSecretsに登録するみたいです。\u003c/p\u003e\n\u003cp data-sourcepos=\"410:1-410:60\"\u003eエンコードは以下のコマンドで行いました。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"javascript\" data-sourcepos=\"412:1-415:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e// ダウンロードした鍵ファイルをBase64でエンコードしてテキストファイルに保存する\u003c/span\u003e\n\u003cspan class=\"nx\"\u003ebase64\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"o\"\u003e~\u003c/span\u003e\u003cspan class=\"sr\"\u003e/ダウンロード/\u003c/span\u003e\u003cspan class=\"nx\"\u003eサービスアカウントキー\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ejson\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003eencoded_file\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etxt\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"417:1-418:147\"\u003eエンコードした文字列をGitHub RepositoryのSettings→Secrets→ActionsからSecretsとして登録します。\u003cbr\u003e\nパイプライン内では\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003esecrets.GOOGLE_APPLICATION_CREDENTIALS\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eとして参照できるようになります。\u003c/p\u003e\n\u003cdiv data-sourcepos=\"420:1-423:3\" class=\"note warn\"\u003e\n\u003cspan class=\"fa fa-fw fa-exclamation-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"421:1-422:178\"\u003eサービスアカウントの鍵ファイルはGitにプッシュしてはいけないようです。\u003cbr\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003eencoded_file.txt\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eも同様にプッシュしないよう\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e.gitignore\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eに追加しておく必要があります。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch1 data-sourcepos=\"425:1-425:60\"\u003e\n\u003cspan id=\"6-github-actionsでcicdパイプラインを構築する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#6-github-actions%E3%81%A7cicd%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6. GitHub ActionsでCI/CDパイプラインを構築する\u003c/h1\u003e\n\u003cp data-sourcepos=\"426:1-426:160\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F797bd44b-968e-4f4d-9634-d014dbe05f25.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f0b1b0e2f2ddb093a6f4de64b508522d\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F797bd44b-968e-4f4d-9634-d014dbe05f25.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f0b1b0e2f2ddb093a6f4de64b508522d\" alt=\"99154d9b3-259d-4e3d-af97-40958410bf31_r2_c2.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F797bd44b-968e-4f4d-9634-d014dbe05f25.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=7206bcd6ca09a7e4bb9ce432cd127646 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/797bd44b-968e-4f4d-9634-d014dbe05f25.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"428:1-428:153\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F12d48997-6b70-4b45-b28e-9bcbdfc6ab00.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=205ecd10ca1a0b14fe83aa5fbb734f51\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F12d48997-6b70-4b45-b28e-9bcbdfc6ab00.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=205ecd10ca1a0b14fe83aa5fbb734f51\" alt=\"7f8b95bf-f9e9-4081-8b47-7520d7460bde.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4379308%2F12d48997-6b70-4b45-b28e-9bcbdfc6ab00.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=80db6005936bd5a585bce42d8654d190 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/12d48997-6b70-4b45-b28e-9bcbdfc6ab00.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"430:1-431:126\"\u003eいよいよCI/CDパイプラインの構築です。\u003cbr\u003e\n以下のワークフローファイル全体を先に確認してから、各ジョブのポイントを見ていきます。\u003c/p\u003e\n\u003cp data-sourcepos=\"433:1-433:94\"\u003e\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e.github/workflows/pipeline.yml\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eに以下を記述します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"yaml\" data-sourcepos=\"435:1-550:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003etodo-app-cicd-pipe\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e# メインブランチへのプッシュ時にパイプラインを起動する\u003c/span\u003e\n\u003cspan class=\"na\"\u003eon\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n  \u003cspan class=\"na\"\u003epush\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"na\"\u003ebranches\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"s\"\u003emain\u003c/span\u003e\n\n\u003cspan class=\"na\"\u003ejobs\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n  \u003cspan class=\"na\"\u003ebuild\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eビルドフェーズ\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e# GitHubが用意するUbuntu環境の仮想マシン上で実行する\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eruns-on\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eubuntu-latest\u003c/span\u003e\n    \u003cspan class=\"na\"\u003esteps\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eコードをチェックアウト\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# GitHubにプッシュしたコードをこの仮想マシン上にクローンする\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/checkout@v4\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eNode.jsをセットアップ\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# 仮想マシンにNode.jsをインストールする\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/setup-node@v4\u003c/span\u003e\n        \u003cspan class=\"na\"\u003ewith\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n          \u003cspan class=\"na\"\u003enode-version\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"m\"\u003e20\u003c/span\u003e\n          \u003cspan class=\"c1\"\u003e# 2回目以降はキャッシュを使ってインストールを高速化する\u003c/span\u003e\n          \u003cspan class=\"na\"\u003ecache\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u003c/span\u003e\u003cspan class=\"s\"\u003enpm'\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e依存関係をインストール\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# package.jsonを読み取って必要なライブラリを全てインストールする\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003enpm install\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eアプリケーションをビルド\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# ReactコードをブラウザやFirebaseが読める静的ファイルに変換する\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# 変換後のファイルはdistフォルダに出力される\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003enpm run build\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eビルドアーティファクトをアップロード\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# distフォルダを保存して次のジョブ（別の仮想マシン）に渡せるようにする\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# ジョブをまたいでファイルを受け渡すにはこのアーティファクト機能が必要\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/upload-artifact@v4\u003c/span\u003e\n        \u003cspan class=\"na\"\u003ewith\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n          \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003ebuild-files\u003c/span\u003e\n          \u003cspan class=\"na\"\u003epath\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003edist\u003c/span\u003e\n          \u003cspan class=\"c1\"\u003e# 1日後に自動削除する\u003c/span\u003e\n          \u003cspan class=\"na\"\u003eretention-days\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\n  \u003cspan class=\"na\"\u003etest\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eテストフェーズ\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e# buildジョブが成功してからテストを実行する（失敗したら止まる）\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eneeds\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003ebuild\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eruns-on\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eubuntu-latest\u003c/span\u003e\n    \u003cspan class=\"na\"\u003esteps\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eコードをチェックアウト\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/checkout@v4\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eNode.jsをセットアップ\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/setup-node@v4\u003c/span\u003e\n        \u003cspan class=\"na\"\u003ewith\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n          \u003cspan class=\"na\"\u003enode-version\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"m\"\u003e20\u003c/span\u003e\n          \u003cspan class=\"na\"\u003ecache\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u003c/span\u003e\u003cspan class=\"s\"\u003enpm'\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e依存関係をインストール\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003enpm install\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eテストを実行\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# vitestが全テストを実行する（1つでも失敗するとここで止まりデプロイされない）\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003enpm run test\u003c/span\u003e\n\n  \u003cspan class=\"na\"\u003edeploy\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eデプロイフェーズ\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e# buildとtestの両方が成功した時だけデプロイを実行する\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eneeds\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"pi\"\u003e[\u003c/span\u003e\u003cspan class=\"nv\"\u003ebuild\u003c/span\u003e\u003cspan class=\"pi\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003etest\u003c/span\u003e\u003cspan class=\"pi\"\u003e]\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eruns-on\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eubuntu-latest\u003c/span\u003e\n    \u003cspan class=\"na\"\u003esteps\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eコードをチェックアウト\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/checkout@v4\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eNode.jsをセットアップ\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/setup-node@v4\u003c/span\u003e\n        \u003cspan class=\"na\"\u003ewith\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n          \u003cspan class=\"na\"\u003enode-version\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"m\"\u003e20\u003c/span\u003e\n          \u003cspan class=\"na\"\u003ecache\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u003c/span\u003e\u003cspan class=\"s\"\u003enpm'\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eビルドアーティファクトをダウンロード\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# buildジョブで保存したdistフォルダをこの仮想マシンに取得する\u003c/span\u003e\n        \u003cspan class=\"na\"\u003euses\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eactions/download-artifact@v4\u003c/span\u003e\n        \u003cspan class=\"na\"\u003ewith\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n          \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003ebuild-files\u003c/span\u003e\n          \u003cspan class=\"na\"\u003epath\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003edist\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e依存関係をインストール\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003enpm install\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eFirebase CLIをインストール\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# firebaseコマンドをこの仮想マシンでも使えるようにする\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003enpm install -g firebase-tools\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eGoogleクレデンシャルを準備\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"pi\"\u003e|\u003c/span\u003e\n          \u003cspan class=\"s\"\u003e# Secretsに登録したBase64エンコード済みの鍵をデコードしてファイルに保存する\u003c/span\u003e\n          \u003cspan class=\"s\"\u003eecho ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} | base64 -d \u0026gt; $HOME/private-key.json\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003eFirebaseにデプロイ\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"pi\"\u003e|\u003c/span\u003e\n          \u003cspan class=\"s\"\u003e# 鍵ファイルの場所をFirebaseに伝える環境変数を設定する\u003c/span\u003e\n          \u003cspan class=\"s\"\u003eexport GOOGLE_APPLICATION_CREDENTIALS=$HOME/private-key.json\u003c/span\u003e\n          \u003cspan class=\"s\"\u003eexport FIREBASE_EXPERIMENTS_ENABLED=true\u003c/span\u003e\n          \u003cspan class=\"s\"\u003e# Hostingだけをデプロイする（他のFirebase機能には触らない）\u003c/span\u003e\n          \u003cspan class=\"s\"\u003efirebase deploy --only hosting\u003c/span\u003e\n\n      \u003cspan class=\"pi\"\u003e-\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e秘密鍵ファイルを削除\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# if: always()でデプロイが失敗しても必ずこのステップを実行する\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e# 鍵ファイルを仮想マシン上に残さないようにする\u003c/span\u003e\n        \u003cspan class=\"na\"\u003eif\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003ealways()\u003c/span\u003e\n        \u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003erm $HOME/private-key.json\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"552:1-552:34\"\u003e\n\u003cspan id=\"61-ジョブを分ける理由\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#61-%E3%82%B8%E3%83%A7%E3%83%96%E3%82%92%E5%88%86%E3%81%91%E3%82%8B%E7%90%86%E7%94%B1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6.1 ジョブを分ける理由\u003c/h2\u003e\n\u003cp data-sourcepos=\"554:1-556:135\"\u003eビルド・テスト・デプロイを別々の\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003ejobs\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eに分けているのがポイントです。\u003cbr\u003e\n各ジョブは別々の仮想マシン上で動くので、1つにまとめてしまうとテストだけが偶発的に失敗した場合でもビルドからやり直しになってしまうようです。\u003cbr\u003e\nジョブを分けておけば失敗したジョブからだけリトライできるので、時間の節約になるみたいです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"yaml\" data-sourcepos=\"558:1-567:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"na\"\u003ejobs\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n  \u003cspan class=\"na\"\u003etest\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e# buildジョブが成功した後にだけ実行する\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eneeds\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003ebuild\u003c/span\u003e\n\n  \u003cspan class=\"na\"\u003edeploy\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e# buildとtestの両方が成功した後にだけ実行する\u003c/span\u003e\n    \u003cspan class=\"na\"\u003eneeds\u003c/span\u003e\u003cspan class=\"pi\"\u003e:\u003c/span\u003e \u003cspan class=\"pi\"\u003e[\u003c/span\u003e\u003cspan class=\"nv\"\u003ebuild\u003c/span\u003e\u003cspan class=\"pi\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003etest\u003c/span\u003e\u003cspan class=\"pi\"\u003e]\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"569:1-569:58\"\u003e\n\u003cspan id=\"62-アーティファクトで成果物を受け渡す\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#62-%E3%82%A2%E3%83%BC%E3%83%86%E3%82%A3%E3%83%95%E3%82%A1%E3%82%AF%E3%83%88%E3%81%A7%E6%88%90%E6%9E%9C%E7%89%A9%E3%82%92%E5%8F%97%E3%81%91%E6%B8%A1%E3%81%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6.2 アーティファクトで成果物を受け渡す\u003c/h2\u003e\n\u003cp data-sourcepos=\"571:1-572:221\"\u003e各ジョブは別々の仮想マシン上で動くため、ビルドで作った\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003edist\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eフォルダをそのまま次のジョブに渡すことはできないようです。\u003cbr\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003eactions/upload-artifact\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eで保存して\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003eactions/download-artifact\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eで受け取ることで、ジョブをまたいで成果物を渡せるみたいです。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"574:1-574:11\"\u003e\n\u003cspan id=\"まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eまとめ\u003c/h1\u003e\n\u003cp data-sourcepos=\"576:1-577:194\"\u003e今回はReact + TypeScript + VitestとGitHub Actions・Firebaseを使ったCI/CDパイプラインを構築する流れを学びました。\u003cbr\u003e\nプッシュするだけでテストが走り自動でデプロイされる仕組みが作れるとわかり、「CI/CDは難しい」という思い込みが崩れた感覚がありました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"579:1-579:21\"\u003e\n\u003cspan id=\"今回の気づき\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BB%8A%E5%9B%9E%E3%81%AE%E6%B0%97%E3%81%A5%E3%81%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e今回の気づき\u003c/h2\u003e\n\u003cp data-sourcepos=\"581:1-583:201\"\u003e一番印象的だったのは、テストコードが「仕様書」として機能するという考え方です。\u003cbr\u003e\nテストの説明文を読むだけでアプリの仕様が把握できるように書いておくことで、誰かが誤ってコードを変更した場合も自動テストがそれを検知してCIが止まるので、デプロイ前にバグに気づける仕組みが作れるみたいです。\u003cbr\u003e\nまた各ジョブを別々に分けることで、失敗した箇所だけリトライできるという設計の考え方も、パイプラインを実際に組んで初めて実感できました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"585:1-585:33\"\u003e\n\u003cspan id=\"ハマりやすいポイント\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%8F%E3%83%9E%E3%82%8A%E3%82%84%E3%81%99%E3%81%84%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eハマりやすいポイント\u003c/h2\u003e\n\u003cul data-sourcepos=\"587:1-595:0\"\u003e\n\u003cli data-sourcepos=\"587:1-587:139\"\u003eテストファイルでJSXを使う場合は拡張子を\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003e.tsx\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eにしないとエラーになります。\u003c/li\u003e\n\u003cli data-sourcepos=\"588:1-588:138\"\u003eサービスアカウントの鍵ファイルは必ずBase64でエンコードしてからSecretsに登録する必要があります。\u003c/li\u003e\n\u003cli data-sourcepos=\"589:1-595:0\"\u003e\n\u003cfont color=\"salmon\"\u003e\u003cstrong\u003e\u003ccode\u003efirebase deploy --only hosting\u003c/code\u003e\u003c/strong\u003e\u003c/font\u003eを使わないとデプロイジョブ内で再ビルドが走ってしまい、アーティファクトが無駄になるようです。\u003c/li\u003e\n\u003c/ul\u003e\n","body":"# 1. はじめに\n\n今回はYouTubeの動画「【初心者完全版】0からReactでCI/CD構築までできるチュートリアル【GitHub Actions/Firebase/Vitest/TypeScript】」を参考に、React + TypeScript + ViteでシンプルなTodoアプリを作りながら、CI/CDパイプラインを構築するまでの流れを学びました。\n「CI/CDって上級者向けでしょ」と思っていたのですが、個人開発レベルなら思ってたより全然シンプルに始められるみたいです。\n\nhttps://www.youtube.com/watch?v=n0fHY-uX2KY\u0026list=PL89AUoaDo0E9AliMM1xwvTE39UtUeSy3H\n\n具体的には以下の技術スタックを使いました。\n![9954d9b3-259d-4e3d-af97-40958410bf31_r1_c2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/19a69b34-3ce2-4eae-a377-5603ff5a2255.png)\n\n- **フロントエンド**：React + TypeScript + Vite\n- **スタイリング**：Tailwind CSS\n- **自動テスト**：Vitest + Testing Library\n- **デプロイ先**：Firebase Hosting\n- **CI/CDパイプライン**：GitHub Actions\n\n# 2. CI/CDとは\n![9954d9b3-259d-4e3d-af97-40958410b1f31_r1_c2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/64da1e18-3d16-4640-8014-523b2c6409a4.png)\n\n## 2.1 CIとCDの違い\n\n**CI（継続的インテグレーション）**とは、コードをGitHubにプッシュした瞬間に自動でビルドとテストが走る仕組みのようです。\n**CD（継続的デリバリー）**とは、テストが全て通ったら自動でデプロイまで行う仕組みのようです。\n\nこの2つが組み合わさることで、「コードを書く→プッシュ→自動テスト→自動デプロイ→世界に公開」という流れが全て自動化されるみたいです。\n\n## 2.2 CI/CDがない場合の問題\n\nCI/CDがない現場だと、半年間コードを書き続けてまとめてリリースする、いわゆるビッグバンリリースになりがちなようです。\nその結果、手動テストでバグが大量に見つかってリリースが延期になるといった問題が起きやすいと理解しました。\nCI/CDを導入すると細かい単位でデプロイするので、バグが見つかっても影響範囲が狭く済むのが大きなメリットみたいです。\n\n# 3. Todoアプリの実装\n![9954d9b3-259d-4e13d-af97-40958410bf31_r1_c2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/81facde2-b4e0-4038-9f9b-0337db7b0599.png)\n\n今回作ったTodoアプリのコード全体を確認します。\n\n```jsx\nimport { useState } from 'react';\nimport './index.css';\n\n// Todoオブジェクトの型定義（TypeScriptで型安全にするため）\ntype Todo = {\n  id: number;\n  title: string;\n  completed: boolean;\n};\n\nfunction App() {\n  // TodoリストをStateで管理（変更があると画面が自動で再レンダリングされる）\n  const [todos, setTodos] = useState\u003cTodo[]\u003e([]);\n  // 入力フォームの文字列をStateで管理\n  const [title, setTitle] = useState('');\n\n  // 追加ボタンを押した時にTodoを追加する処理\n  const handleAddTodo = () =\u003e {\n    // titleが空の場合は何もしない（空のTodoを追加させない）\n    if (!title) return;\n    // スプレッド構文で新しい配列を作ってStateを更新する\n    // pushで直接変更するとReactが変化を検知できないため必ずこの形にする\n    setTodos([\n      ...todos,\n      { id: todos.length + 1, title, completed: false },\n    ]);\n    // 追加後はフォームをリセット\n    setTitle('');\n  };\n\n  // チェックボックスを押した時にcompletedを反転させる処理\n  const handleToggleTodo = (id: number) =\u003e {\n    // mapは新しい配列を返すのでスプレッド構文なしでStateの更新ができる\n    setTodos(\n      todos.map((todo) =\u003e\n        // IDが一致するTodoだけcompletedを反転し、それ以外はそのまま返す\n        todo.id === id\n          ? { ...todo, completed: !todo.completed }\n          : todo\n      )\n    );\n  };\n\n  // filterでcompletedがtrueのものだけ取り出して件数を数える\n  const completedCount = todos.filter((todo) =\u003e todo.completed).length;\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eTodoアプリ\u003c/h1\u003e\n      \u003cinput\n        type=\"text\"\n        placeholder=\"新しいタスクを入力\"\n        // valueにStateを渡すことでフォームとStateを同期させる\n        value={title}\n        // 文字が入力されるたびにtitleのStateを更新する\n        onChange={(e) =\u003e setTitle(e.target.value)}\n      /\u003e\n      \u003cbutton onClick={handleAddTodo}\u003e追加\u003c/button\u003e\n\n      {/* todosが1件以上あればリストを表示、なければメッセージを表示 */}\n      {todos.length \u003e 0 ? (\n        \u003cul\u003e\n          {todos.map((todo) =\u003e (\n            \u003cli key={todo.id}\u003e\n              \u003cinput\n                type=\"checkbox\"\n                // completedの値でチェック状態を制御する\n                checked={todo.completed}\n                onChange={() =\u003e handleToggleTodo(todo.id)}\n              /\u003e\n              {todo.title}\n            \u003c/li\u003e\n          ))}\n        \u003c/ul\u003e\n      ) : (\n        \u003cp\u003eタスクがありません。新しいタスクを追加してください。\u003c/p\u003e\n      )}\n\n      {/* todosが1件以上ある時だけ完了件数を表示する */}\n      {todos.length \u003e 0 \u0026\u0026 (\n        \u003cp\u003e完了済み {completedCount} / {todos.length}\u003c/p\u003e\n      )}\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n\n# 4. 自動テスト（Vitest + Testing Library）\n![9954d9b3-259d-4e3d-af97-40958410bf311_r2_c2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/4def9de0-d77e-44a0-97dc-ead53aec8bb4.png)\n\nCI/CDを構築するうえで自動テストは欠かせない要素です。\nテストが通ったことを条件にデプロイするので、テストがなければCI/CDは成立しないと理解しました。\n\n## 4.1 パッケージのインストールと役割\n\n```javascript\n// 開発用パッケージとしてインストール（本番ビルドには含まれない）\nnpm install -D vitest @testing-library/react @testing-library/dom @testing-library/jest-dom jsdom\n```\n\nインストールするパッケージの役割はそれぞれ以下のようです。\n\n**vitest**\n\n\u003cfont color=\"salmon\"\u003e**`vitest`**\u003c/font\u003eはViteと相性の良いテストフレームワークで、テスト全体を動かすエンジン部分になります。\n\u003cfont color=\"salmon\"\u003e**`test`**\u003c/font\u003e・\u003cfont color=\"salmon\"\u003e**`describe`**\u003c/font\u003e・\u003cfont color=\"salmon\"\u003e**`expect`**\u003c/font\u003eといったテストの骨格を作る関数が使えるようになります。\n\n```javascript\nimport { test, describe, expect } from 'vitest';\n\n// describe：テストケースをグループにまとめる（どのコンポーネントのテストかを示す）\ndescribe('Appコンポーネント', () =\u003e {\n  // test：1つのテストケースを定義する（第1引数がテストの仕様説明になる）\n  test('タイトルが表示されている', () =\u003e {\n    // expect：実際の値と期待値が一致するかを検証する\n    expect(1 + 1).toBe(2);\n  });\n});\n```\n\n**@testing-library/react**\n\n\u003cfont color=\"salmon\"\u003e**`@testing-library/react`**\u003c/font\u003eはReactコンポーネントを仮想的にレンダリングするためのライブラリです。\n\u003cfont color=\"salmon\"\u003e**`render`**\u003c/font\u003eでコンポーネントを描画し、\u003cfont color=\"salmon\"\u003e**`screen`**\u003c/font\u003eで描画されたHTML要素を取得できます。\n\n```jsx\nimport { render, screen } from '@testing-library/react';\nimport App from '../App';\n\ntest('タイトルが表示されている', () =\u003e {\n  // render：テスト内でAppコンポーネントを仮想的に描画する\n  // ブラウザを開かなくてもHTMLが生成される\n  render(\u003cApp /\u003e);\n\n  // screen.getByRole：描画されたHTMLからロールと名前で要素を取得する\n  // ユーザーが画面を見る目線で要素を特定するのがポイント\n  const heading = screen.getByRole('heading', { name: 'Todoアプリ' });\n});\n```\n\n**@testing-library/dom**\n\n\u003cfont color=\"salmon\"\u003e**`@testing-library/dom`**\u003c/font\u003eは描画されたHTMLに対してユーザー操作を模倣するための関数を提供するライブラリです。\n\u003cfont color=\"salmon\"\u003e**`fireEvent`**\u003c/font\u003eでクリックや入力といったブラウザイベントをテスト内で再現できます。\n\n```jsx\nimport { fireEvent } from '@testing-library/react';\n\n// fireEvent.change：inputに文字が入力されたイベントを発火する\n// 実際にキーボードで入力した時と同じ状態を作り出す\nfireEvent.change(input, { target: { value: 'テストタスク' } });\n\n// fireEvent.click：ボタンがクリックされたイベントを発火する\n// 実際にマウスでクリックした時と同じ状態を作り出す\nfireEvent.click(button);\n```\n\n**@testing-library/jest-dom**\n\n\u003cfont color=\"salmon\"\u003e**`@testing-library/jest-dom`**\u003c/font\u003eはDOMに特化したマッチャーを追加するライブラリです。\nこのライブラリを入れることで\u003cfont color=\"salmon\"\u003e**`toBeInTheDocument()`**\u003c/font\u003eや\u003cfont color=\"salmon\"\u003e**`toBeChecked()`**\u003c/font\u003eといった直感的な検証メソッドが使えるようになるみたいです。\n\n```jsx\n// toBeInTheDocument：その要素が画面上に存在するかを確認する\nexpect(heading).toBeInTheDocument();\n\n// toBeChecked：チェックボックスがチェック済み状態かを確認する\nexpect(checkbox).toBeChecked();\n```\n\n**jsdom**\n\n\u003cfont color=\"salmon\"\u003e**`jsdom`**\u003c/font\u003eはNode.js環境上に仮想のブラウザ環境を作るためのライブラリです。\nテストはブラウザではなくNode.jsで実行されるので、DOMを扱うためにこの仮想環境が必要になるようです。\n\u003cfont color=\"salmon\"\u003e**`vitest.config.ts`**\u003c/font\u003eで\u003cfont color=\"salmon\"\u003e**`environment: 'jsdom'`**\u003c/font\u003eと設定することで有効になります。\n\n## 4.2 設定ファイルの準備\n\n\u003cfont color=\"salmon\"\u003e**`vitest.config.ts`**\u003c/font\u003eに以下の設定を書きます。\n\n```javascript\nimport { defineConfig } from 'vitest/config';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n  plugins: [react()],\n  test: {\n    // jsdomで仮想ブラウザ環境を作りDOMを扱えるようにする\n    environment: 'jsdom',\n    // test・describe・expectをimportなしで使えるようにする\n    globals: true,\n    // 全テストの実行前に必ず読み込まれるセットアップファイルを指定する\n    setupFiles: './vitest.setup.ts',\n  },\n});\n```\n\n\u003cfont color=\"salmon\"\u003e**`vitest.setup.ts`**\u003c/font\u003eに以下を書きます。\n全テストの実行前に自動でインポートされるので、各テストファイルに個別に書く必要がなくなるようです。\n\n```javascript\n// toBeInTheDocumentなどのDOM用マッチャーを全テストで使えるようにする\nimport '@testing-library/jest-dom';\n```\n\n## 4.3 テストコードの全体像\n\n以下がアプリ全体をカバーするテストコードです。\n\n```jsx\nimport { render, screen, fireEvent, within } from '@testing-library/react';\nimport { describe, test, expect } from 'vitest';\nimport App from '../App';\n\n// describe：Appコンポーネントに関するテストをひとまとめにする\ndescribe('App', () =\u003e {\n\n  // test第1引数はアプリの「仕様」を書く\n  // CIでこのテストが落ちた時に「どの仕様が壊れたか」が一目でわかる\n  test('アプリタイトルが表示されている', () =\u003e {\n    // render：仮想ブラウザ上にAppコンポーネントを描画する\n    render(\u003cApp /\u003e);\n    // screen.getByRole：headingロールを持つ「Todoアプリ」という名前の要素を取得する\n    const heading = screen.getByRole('heading', { name: 'Todoアプリ' });\n    // toBeInTheDocument：取得した要素が画面上に存在することを確認する\n    expect(heading).toBeInTheDocument();\n  });\n\n  test('Todoを追加することができる', () =\u003e {\n    render(\u003cApp /\u003e);\n    // screen.getByRole：ユーザーが画面を見る目線で要素を特定する\n    // textboxロールのinputとbuttonロールの追加ボタンを取得する\n    const input = screen.getByRole('textbox', { name: /新しいタスクを入力/ });\n    const addButton = screen.getByRole('button', { name: '追加' });\n\n    // fireEvent.change：inputに「テストタスク」と入力したイベントを発火する\n    fireEvent.change(input, { target: { value: 'テストタスク' } });\n    // fireEvent.click：追加ボタンをクリックしたイベントを発火する\n    fireEvent.click(addButton);\n\n    // screen.getByRole：listロールの要素（ul）を取得する\n    const list = screen.getByRole('list');\n    // within：listの中だけを対象に絞り込んで「テストタスク」を探す\n    // withinを使うことでリスト外の同名テキストに誤反応しなくなる\n    expect(within(list).getByText('テストタスク')).toBeInTheDocument();\n  });\n\n  test('Todoを完了することができる', () =\u003e {\n    render(\u003cApp /\u003e);\n    const input = screen.getByRole('textbox', { name: /新しいタスクを入力/ });\n    const addButton = screen.getByRole('button', { name: '追加' });\n\n    // まずTodoを1件追加してからチェックボックスを操作する\n    fireEvent.change(input, { target: { value: 'テストタスク' } });\n    fireEvent.click(addButton);\n\n    // screen.getAllByRole：checkboxロールを持つ要素を全件配列で取得する\n    const checkboxes = screen.getAllByRole('checkbox');\n    // fireEvent.click：最初のチェックボックスをクリックして完了状態にする\n    fireEvent.click(checkboxes[0]);\n\n    // toBeChecked：チェックボックスがチェック済みになっているかを確認する\n    expect(checkboxes[0]).toBeChecked();\n  });\n\n  test('完了したTodoのカウントが表示されている', () =\u003e {\n    render(\u003cApp /\u003e);\n    const input = screen.getByRole('textbox', { name: /新しいタスクを入力/ });\n    const addButton = screen.getByRole('button', { name: '追加' });\n\n    // 2件追加して1件だけチェックする\n    fireEvent.change(input, { target: { value: 'テストタスク1' } });\n    fireEvent.click(addButton);\n    fireEvent.change(input, { target: { value: 'テストタスク2' } });\n    fireEvent.click(addButton);\n\n    const checkboxes = screen.getAllByRole('checkbox');\n    // 最初の1件だけチェックして「完了済み 1 / 2」になることを確認する\n    fireEvent.click(checkboxes[0]);\n\n    // screen.getByText：画面上に「完了済み 1 / 2」というテキストが存在するか確認する\n    expect(screen.getByText('完了済み 1 / 2')).toBeInTheDocument();\n  });\n\n  test('Todoがない場合はメッセージが表示されている', () =\u003e {\n    // Todoを追加しない状態でレンダリングして初期メッセージを確認する\n    render(\u003cApp /\u003e);\n    expect(\n      screen.getByText('タスクがありません。新しいタスクを追加してください。')\n    ).toBeInTheDocument();\n  });\n\n  test('空のタイトルでTodoは追加されない', () =\u003e {\n    render(\u003cApp /\u003e);\n    const addButton = screen.getByRole('button', { name: '追加' });\n\n    // 何も入力せずに追加ボタンをクリックする\n    fireEvent.click(addButton);\n\n    // 空追加後もメッセージが残ったままであることを確認する\n    // もしtitleの空チェック処理が消えたらここでテストが落ちてCIが止まる\n    expect(\n      screen.getByText('タスクがありません。新しいタスクを追加してください。')\n    ).toBeInTheDocument();\n  });\n});\n```\n\n## 4.4 テストのポイント\n\n\u003cfont color=\"salmon\"\u003e**`getByRole`**\u003c/font\u003eでユーザーが実際に画面を見た時の目線でHTML要素を取得するのがポイントのようです。\n「追加」という文字が書かれたボタンを\u003cfont color=\"salmon\"\u003e**`button`**\u003c/font\u003eロールで取得することで、ユーザーの操作をそのままテストに落とし込めると理解しました。\n\n\u003cfont color=\"salmon\"\u003e**`within`**\u003c/font\u003eを使うと特定の要素の中だけを対象に絞り込んで検索できます。\nリスト全体を対象にするより範囲が狭まるので、より確実なテストが書けるようです。\n\n:::note\nテストの説明文（第1引数の文字列）は「仕様書」として書くとよいみたいです。\nこのテストを読んだだけでアプリの仕様が分かるようにしておくと、CI/CDのパイプラインが落ちた時にどの仕様が壊れたかが一目で分かるようです。\n:::\n\n# 5. Firebase Hostingへのデプロイ\n![9954d9b3-259d-4e3d-af97-409584101bf31_r2_c2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/ba6beb35-d4bf-4f3a-87d3-45e10c3546c7.png)\n\nFirebase Hostingとは、Googleが提供する静的サイトのホスティングサービスです。\nReactアプリをビルドしてできたファイルをFirebaseのサーバーに置くことで、インターネット上に公開できるみたいです。\n今回はまず手動でデプロイの流れを確認してから、CI/CDパイプラインに組み込む流れで進めました。\n\n## 5.1 Firebaseプロジェクトの作成とCLI設定\n\nまずFirebase CLIをグローバルにインストールします。\n\n```javascript\n// グローバルインストール（どのディレクトリからでもfirebaseコマンドが使えるようになる）\nnpm install -g firebase-tools\n```\n\nその後、Firebase CLIでログインし、プロジェクトに設定を追加します。\n\n```javascript\n// Googleアカウントでログインする\nfirebase login\n\n// Hostingの初期化（firebase.jsonと.firebasercが自動生成される）\nfirebase init hosting\n```\n\n:::note\n自分の環境では\u003cfont color=\"salmon\"\u003e**`firebase.json`**\u003c/font\u003eの\u003cfont color=\"salmon\"\u003e**`\"public\"`**\u003c/font\u003eの値が最初から\u003cfont color=\"salmon\"\u003e**`\"dist\"`**\u003c/font\u003eになっていました。\n動画内では異なる値になっていたので、もし\u003cfont color=\"salmon\"\u003e**`\"dist\"`**\u003c/font\u003e以外になっていた場合は手動で修正してください。\n:::\n\n## 5.2 手動デプロイの確認\n\n設定が完了したら以下のコマンドだけでデプロイできます。\n\n```javascript\n// ビルドとデプロイをまとめて実行する\nfirebase deploy\n```\n\nURLが発行され、インターネット上に公開されるのが確認できました。\n思ったよりシンプルにデプロイできるみたいです。\n\n## 5.3 サービスアカウント鍵のエンコード\n\nCI/CD環境からFirebaseにデプロイするには、パイプラインにFirebaseの認証情報を渡す必要があります。\nGCPでサービスアカウントを作成してダウンロードした鍵ファイルをそのまま使うのは危険なので、Base64でエンコードしてからGitHub ActionsのSecretsに登録するみたいです。\n\nエンコードは以下のコマンドで行いました。\n\n```javascript\n// ダウンロードした鍵ファイルをBase64でエンコードしてテキストファイルに保存する\nbase64 -w 0 ~/ダウンロード/サービスアカウントキー.json \u003e encoded_file.txt\n```\n\nエンコードした文字列をGitHub RepositoryのSettings→Secrets→ActionsからSecretsとして登録します。\nパイプライン内では\u003cfont color=\"salmon\"\u003e**`secrets.GOOGLE_APPLICATION_CREDENTIALS`**\u003c/font\u003eとして参照できるようになります。\n\n:::note warn\nサービスアカウントの鍵ファイルはGitにプッシュしてはいけないようです。\n\u003cfont color=\"salmon\"\u003e**`encoded_file.txt`**\u003c/font\u003eも同様にプッシュしないよう\u003cfont color=\"salmon\"\u003e**`.gitignore`**\u003c/font\u003eに追加しておく必要があります。\n:::\n\n# 6. GitHub ActionsでCI/CDパイプラインを構築する\n![99154d9b3-259d-4e3d-af97-40958410bf31_r2_c2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/797bd44b-968e-4f4d-9634-d014dbe05f25.png)\n\n![7f8b95bf-f9e9-4081-8b47-7520d7460bde.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/12d48997-6b70-4b45-b28e-9bcbdfc6ab00.png)\n\nいよいよCI/CDパイプラインの構築です。\n以下のワークフローファイル全体を先に確認してから、各ジョブのポイントを見ていきます。\n\n\u003cfont color=\"salmon\"\u003e**`.github/workflows/pipeline.yml`**\u003c/font\u003eに以下を記述します。\n\n```yaml\nname: todo-app-cicd-pipe\n\n# メインブランチへのプッシュ時にパイプラインを起動する\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build:\n    name: ビルドフェーズ\n    # GitHubが用意するUbuntu環境の仮想マシン上で実行する\n    runs-on: ubuntu-latest\n    steps:\n      - name: コードをチェックアウト\n        # GitHubにプッシュしたコードをこの仮想マシン上にクローンする\n        uses: actions/checkout@v4\n\n      - name: Node.jsをセットアップ\n        # 仮想マシンにNode.jsをインストールする\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          # 2回目以降はキャッシュを使ってインストールを高速化する\n          cache: 'npm'\n\n      - name: 依存関係をインストール\n        # package.jsonを読み取って必要なライブラリを全てインストールする\n        run: npm install\n\n      - name: アプリケーションをビルド\n        # ReactコードをブラウザやFirebaseが読める静的ファイルに変換する\n        # 変換後のファイルはdistフォルダに出力される\n        run: npm run build\n\n      - name: ビルドアーティファクトをアップロード\n        # distフォルダを保存して次のジョブ（別の仮想マシン）に渡せるようにする\n        # ジョブをまたいでファイルを受け渡すにはこのアーティファクト機能が必要\n        uses: actions/upload-artifact@v4\n        with:\n          name: build-files\n          path: dist\n          # 1日後に自動削除する\n          retention-days: 1\n\n  test:\n    name: テストフェーズ\n    # buildジョブが成功してからテストを実行する（失敗したら止まる）\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: コードをチェックアウト\n        uses: actions/checkout@v4\n\n      - name: Node.jsをセットアップ\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'npm'\n\n      - name: 依存関係をインストール\n        run: npm install\n\n      - name: テストを実行\n        # vitestが全テストを実行する（1つでも失敗するとここで止まりデプロイされない）\n        run: npm run test\n\n  deploy:\n    name: デプロイフェーズ\n    # buildとtestの両方が成功した時だけデプロイを実行する\n    needs: [build, test]\n    runs-on: ubuntu-latest\n    steps:\n      - name: コードをチェックアウト\n        uses: actions/checkout@v4\n\n      - name: Node.jsをセットアップ\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'npm'\n\n      - name: ビルドアーティファクトをダウンロード\n        # buildジョブで保存したdistフォルダをこの仮想マシンに取得する\n        uses: actions/download-artifact@v4\n        with:\n          name: build-files\n          path: dist\n\n      - name: 依存関係をインストール\n        run: npm install\n\n      - name: Firebase CLIをインストール\n        # firebaseコマンドをこの仮想マシンでも使えるようにする\n        run: npm install -g firebase-tools\n\n      - name: Googleクレデンシャルを準備\n        run: |\n          # Secretsに登録したBase64エンコード済みの鍵をデコードしてファイルに保存する\n          echo ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} | base64 -d \u003e $HOME/private-key.json\n\n      - name: Firebaseにデプロイ\n        run: |\n          # 鍵ファイルの場所をFirebaseに伝える環境変数を設定する\n          export GOOGLE_APPLICATION_CREDENTIALS=$HOME/private-key.json\n          export FIREBASE_EXPERIMENTS_ENABLED=true\n          # Hostingだけをデプロイする（他のFirebase機能には触らない）\n          firebase deploy --only hosting\n\n      - name: 秘密鍵ファイルを削除\n        # if: always()でデプロイが失敗しても必ずこのステップを実行する\n        # 鍵ファイルを仮想マシン上に残さないようにする\n        if: always()\n        run: rm $HOME/private-key.json\n```\n\n## 6.1 ジョブを分ける理由\n\nビルド・テスト・デプロイを別々の\u003cfont color=\"salmon\"\u003e**`jobs`**\u003c/font\u003eに分けているのがポイントです。\n各ジョブは別々の仮想マシン上で動くので、1つにまとめてしまうとテストだけが偶発的に失敗した場合でもビルドからやり直しになってしまうようです。\nジョブを分けておけば失敗したジョブからだけリトライできるので、時間の節約になるみたいです。\n\n```yaml\njobs:\n  test:\n    # buildジョブが成功した後にだけ実行する\n    needs: build\n\n  deploy:\n    # buildとtestの両方が成功した後にだけ実行する\n    needs: [build, test]\n```\n\n## 6.2 アーティファクトで成果物を受け渡す\n\n各ジョブは別々の仮想マシン上で動くため、ビルドで作った\u003cfont color=\"salmon\"\u003e**`dist`**\u003c/font\u003eフォルダをそのまま次のジョブに渡すことはできないようです。\n\u003cfont color=\"salmon\"\u003e**`actions/upload-artifact`**\u003c/font\u003eで保存して\u003cfont color=\"salmon\"\u003e**`actions/download-artifact`**\u003c/font\u003eで受け取ることで、ジョブをまたいで成果物を渡せるみたいです。\n\n# まとめ\n\n今回はReact + TypeScript + VitestとGitHub Actions・Firebaseを使ったCI/CDパイプラインを構築する流れを学びました。\nプッシュするだけでテストが走り自動でデプロイされる仕組みが作れるとわかり、「CI/CDは難しい」という思い込みが崩れた感覚がありました。\n\n## 今回の気づき\n\n一番印象的だったのは、テストコードが「仕様書」として機能するという考え方です。\nテストの説明文を読むだけでアプリの仕様が把握できるように書いておくことで、誰かが誤ってコードを変更した場合も自動テストがそれを検知してCIが止まるので、デプロイ前にバグに気づける仕組みが作れるみたいです。\nまた各ジョブを別々に分けることで、失敗した箇所だけリトライできるという設計の考え方も、パイプラインを実際に組んで初めて実感できました。\n\n## ハマりやすいポイント\n\n- テストファイルでJSXを使う場合は拡張子を\u003cfont color=\"salmon\"\u003e**`.tsx`**\u003c/font\u003eにしないとエラーになります。\n- サービスアカウントの鍵ファイルは必ずBase64でエンコードしてからSecretsに登録する必要があります。\n- \u003cfont color=\"salmon\"\u003e**`firebase deploy --only hosting`**\u003c/font\u003eを使わないとデプロイジョブ内で再ビルドが走ってしまい、アーティファクトが無駄になるようです。\n\n\n\n\n\n\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:17:47+09:00","group":null,"id":"37c498e5e7bf13e8f479","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"TypeScript","versions":[]},{"name":"Firebase","versions":[]},{"name":"React","versions":[]},{"name":"CICD","versions":[]},{"name":"GitHubActions","versions":[]}],"title":"初心者がReactのTodoアプリでCI/CDを体験してみた【Vitest・Firebase・GitHub Actions】","updated_at":"2026-05-06T11:17:47+09:00","url":"https://qiita.com/teru_dev/items/37c498e5e7bf13e8f479","user":{"description":"WordPressやWeb制作を経験したのち、\r\nJavaScript / React を本格的に学習中 📚\r\n\r\n「自分と同じ初学者の役に立てば」という気持ちで、学んだことをQiitaにまとめています✍️\r\nフロントエンドエンジニアへ向けて一歩ずつ進んでいます🚶","facebook_id":"","followees_count":1,"followers_count":2,"github_login_name":null,"id":"teru_dev","items_count":34,"linkedin_id":"","location":"","name":"","organization":"","permanent_id":4379308,"profile_image_url":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4379308/profile-images/1773468928","team_only":false,"twitter_screen_name":"teruu_dev","website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:14\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h1\u003e\n\u003cp data-sourcepos=\"3:1-3:162\"\u003e多くの言語に実行環境のOS名を取得する機能が備わっていますが、今回はGo言語でOS名を取得する方法を調べてみました。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"5:1-5:11\"\u003e\n\u003cspan id=\"コード\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%B3%E3%83%BC%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eコード\u003c/h1\u003e\n\u003cul data-sourcepos=\"7:1-9:0\"\u003e\n\u003cli data-sourcepos=\"7:1-9:0\"\u003e\n\u003ccode\u003eruntime.GOOS\u003c/code\u003eと書くだけで、実行環境のOS名を取得できます。\n\u003cul data-sourcepos=\"8:3-9:0\"\u003e\n\u003cli data-sourcepos=\"8:3-9:0\"\u003eJavaだと\u003ccode\u003eSystem.getProperty(\"os.name\")\u003c/code\u003eと書くところですが、Go言語は簡潔に書けるのが良いですね。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"go\" data-sourcepos=\"10:1-20:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003eos_name.go\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003epackage\u003c/span\u003e \u003cspan class=\"n\"\u003emain\u003c/span\u003e\n\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n    \u003cspan class=\"s\"\u003e\"fmt\"\u003c/span\u003e\n    \u003cspan class=\"s\"\u003e\"runtime\"\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003efunc\u003c/span\u003e \u003cspan class=\"n\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n    \u003cspan class=\"n\"\u003efmt\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eruntime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGOOS\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003ch1 data-sourcepos=\"22:1-22:14\"\u003e\n\u003cspan id=\"実行結果\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実行結果\u003c/h1\u003e\n\u003cul data-sourcepos=\"24:1-30:0\"\u003e\n\u003cli data-sourcepos=\"24:1-24:49\"\u003e取得したOS名は全て小文字でした。\u003c/li\u003e\n\u003cli data-sourcepos=\"25:1-27:170\"\u003eOSのバージョン番号や、ディストリビューション名は取得できないようです。\n\u003cul data-sourcepos=\"26:3-27:170\"\u003e\n\u003cli data-sourcepos=\"26:3-26:171\"\u003eWindowsの場合、「Windows 11」などのバージョン名（製品名）を取得するには、レジストリを直接参照することになりそうです。\u003c/li\u003e\n\u003cli data-sourcepos=\"27:3-27:170\"\u003eLinuxの場合、ディストリビューション名を取得するには、\u003ccode\u003e/etc/os-release\u003c/code\u003eをファイルとして開いて解析するしかないようです。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"28:1-30:0\"\u003e公式リファレンスを見ると、「windows」と「linux」以外に「darwin」と「freebsd」にも対応しているようです。\n\u003cul data-sourcepos=\"29:3-30:0\"\u003e\n\u003cli data-sourcepos=\"29:3-30:0\"\u003e「darwin」は、macOSなどAppleのOS全般を指しているようです。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"go\" data-sourcepos=\"31:1-33:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003eRocky Linux 9での実行結果\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003elinux\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"go\" data-sourcepos=\"35:1-37:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003eOracle Linux 8での実行結果\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003elinux\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"go\" data-sourcepos=\"39:1-41:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003eWindows 11での実行結果\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewindows\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003ch1 data-sourcepos=\"44:1-44:11\"\u003e\n\u003cspan id=\"参考url\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%8F%82%E8%80%83url\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e参考URL\u003c/h1\u003e\n\u003cul data-sourcepos=\"46:1-46:52\"\u003e\n\u003cli data-sourcepos=\"46:1-46:52\"\u003e\u003ca href=\"https://pkg.go.dev/runtime\" rel=\"nofollow noopener\" target=\"_blank\"\u003eruntime - pkg.go.dev\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n","body":"# はじめに\n\n多くの言語に実行環境のOS名を取得する機能が備わっていますが、今回はGo言語でOS名を取得する方法を調べてみました。\n\n# コード\n\n* `runtime.GOOS`と書くだけで、実行環境のOS名を取得できます。\n  * Javaだと`System.getProperty(\"os.name\")`と書くところですが、Go言語は簡潔に書けるのが良いですね。\n\n```go:os_name.go\npackage main\nimport (\n    \"fmt\"\n    \"runtime\"\n    )\n\nfunc main(){\n    fmt.Println(runtime.GOOS)\n}\n```\n\n# 実行結果\n\n* 取得したOS名は全て小文字でした。\n* OSのバージョン番号や、ディストリビューション名は取得できないようです。\n  * Windowsの場合、「Windows 11」などのバージョン名（製品名）を取得するには、レジストリを直接参照することになりそうです。\n  * Linuxの場合、ディストリビューション名を取得するには、`/etc/os-release`をファイルとして開いて解析するしかないようです。\n* 公式リファレンスを見ると、「windows」と「linux」以外に「darwin」と「freebsd」にも対応しているようです。\n  * 「darwin」は、macOSなどAppleのOS全般を指しているようです。\n\n```go:Rocky Linux 9での実行結果\nlinux\n```\n\n```go:Oracle Linux 8での実行結果\nlinux\n```\n\n```go:Windows 11での実行結果\nwindows\n```\n\n\n# 参考URL\n\n* [runtime - pkg.go.dev](https://pkg.go.dev/runtime)\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:17:40+09:00","group":null,"id":"33ee8ae26ec6b690adbc","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"Go","versions":[]},{"name":"初心者","versions":[]}],"title":"はじめてのGo言語（22. 実行環境のOS名を取得する）","updated_at":"2026-05-06T11:17:40+09:00","url":"https://qiita.com/nkojima/items/33ee8ae26ec6b690adbc","user":{"description":null,"facebook_id":null,"followees_count":12,"followers_count":45,"github_login_name":null,"id":"nkojima","items_count":260,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":312166,"profile_image_url":"https://qiita-image-store.s3.amazonaws.com/0/312166/profile-images/1543412366","team_only":false,"twitter_screen_name":null,"website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:9\"\u003e\n\u003cspan id=\"背景\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%83%8C%E6%99%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e背景\u003c/h2\u003e\n\u003cp data-sourcepos=\"2:1-2:371\"\u003e自分は普段noteというブログアプリで読んだ本や観た映画の批評を書くのが趣味なのですが、「いつも使っているnoteを自分で開発してみれば、アプリの仕組みやバックエンドの勉強になり面白いのでは？」と思い始めました。機能に関してはGeminiを活用しながら制作しました。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"4:1-4:8\"\u003e\n\u003cspan id=\"概要\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%A6%82%E8%A6%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e概要\u003c/h1\u003e\n\u003cp data-sourcepos=\"5:1-9:66\"\u003e\u003ca href=\"https://www.postman.com/jp/\" rel=\"nofollow noopener\" target=\"_blank\"\u003ePostman\u003c/a\u003eで\u003ca href=\"https://note-clone-dgiw.onrender.com/\" rel=\"nofollow noopener\" target=\"_blank\"\u003eurl\u003c/a\u003eをペーストして実装できます。\u003cbr\u003e\n①まず\u003ca href=\"https://note-clone-dgiw.onrender.com/signup\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://note-clone-dgiw.onrender.com/signup\u003c/a\u003e でアカウントネームとパスワードを設定します。\u003cbr\u003e\n②次に\u003ca href=\"https://note-clone-dgiw.onrender.com/login\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://note-clone-dgiw.onrender.com/login\u003c/a\u003e でアカウントネームとパスワードを入力しトークンを入手します。\u003cbr\u003e\n③\u003ca href=\"https://note-clone-dgiw.onrender.com/articles\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://note-clone-dgiw.onrender.com/articles\u003c/a\u003e でトークンを利用し記事のタイトル、内容、画像を入力します。\u003cbr\u003e\n④記事のタイトル、内容、画像が表示されます。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"11:1-11:38\"\u003e\n\u003cspan id=\"技術構成アーキテクチャ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%8A%80%E8%A1%93%E6%A7%8B%E6%88%90%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e技術構成・アーキテクチャ\u003c/h1\u003e\n\u003cp data-sourcepos=\"12:1-19:142\"\u003e言語　Go(Golang)　並行処理に強く、静的型付けによる堅牢なバックエンド開発が可能なため。\u003cbr\u003e\nフレームワーク　Gin　Goで最も人気のある軽量・高速なWebフレームワーク。ルーティングやミドルウェアの記述が直感的であるため。\u003cbr\u003e\nデータベース　PostgreSQL (Render)　リレーショナルデータの管理に適しており、本番環境での信頼性が高いため。\u003cbr\u003e\nORM　GORM　GoオブジェクトとDBテーブルを直感的に紐付け、複雑なSQLを書かずに開発スピードを向上できるため。\u003cbr\u003e\n認証　JWT (JSON Web Token)　ステートレスな認証方式で、サーバー側にセッションを持たせず、スケーラビリティを確保できるため。\u003cbr\u003e\n画像ストレージ\tCloudinary\t画像の保存、最適化、配信を自動化するクラウドサービスであるため。\u003cbr\u003e\nインフラ\tRender.com\tGitHubと連携したCI/CD（自動デプロイ）が可能で、モダンなPaaS環境を提供。\u003cbr\u003e\nAI Gemini 無料で無制限利用ができるため。基本的にGeminiでコードを書き、それを自分でチェックしました。\u003c/p\u003e\n\u003cp data-sourcepos=\"21:1-21:198\"\u003e本システムは、クライアントからのリクエストに対して、認証・ビジネスロジック・外部連携を順次行う「レイヤード」な構造を意識しています。\u003c/p\u003e\n\u003col data-sourcepos=\"23:1-32:0\"\u003e\n\u003cli data-sourcepos=\"23:1-32:0\"\u003eリクエストの流れ\u003cbr\u003e\n　1.クライアント (Postman等): \u003ccode\u003eAuthorization\u003c/code\u003e ヘッダーに JWT を含めて \u003ccode\u003ePOST /articles\u003c/code\u003e を送信。\u003cbr\u003e\n2.Auth Middleware: 送られてきたトークンを検証し、有効であれば \u003ccode\u003euser_id\u003c/code\u003e を抽出して後続の処理へ渡す。\u003cbr\u003e\n3.Controller (Main Logic):\u003cbr\u003e\n・フォームデータからタイトルと内容を取得。\u003cbr\u003e\n・添付された画像ファイルを一時的に受け取る。\u003cbr\u003e\n4.External Service (Cloudinary): 画像ファイルを Cloudinary へアップロードし、永続的な Image URL を受け取る。\u003cbr\u003e\n5.Database (PostgreSQL): 記事のタイトル、内容、Image URL、および投稿した \u003ccode\u003euser_id\u003c/code\u003e を1つのレコードとして保存。\u003cbr\u003e\n6. Response: 保存された記事データを JSON 形式でクライアントへ返却。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 data-sourcepos=\"33:1-33:47\"\u003e\n\u003cspan id=\"実装のポイント詰まった箇所\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E8%A3%85%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E8%A9%B0%E3%81%BE%E3%81%A3%E3%81%9F%E7%AE%87%E6%89%80\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実装のポイント（詰まった箇所）\u003c/h1\u003e\n\u003col data-sourcepos=\"34:1-63:109\"\u003e\n\u003cli data-sourcepos=\"34:1-39:182\"\u003e開発環境と本番環境（PaaS）の「ポート」の差\u003cbr\u003e\n【詰まった箇所】\u003cbr\u003e\nローカルでは \u003ccode\u003e8080\u003c/code\u003e など固定のポートで動いていましたが、Renderでは \u003ccode\u003ePort scan timeout\u003c/code\u003e でデプロイが失敗し続けました。\u003cbr\u003e\n【解決の鍵】\u003cbr\u003e\nPaaS（RenderやHerokuなど）は、起動のたびに空いているポートをアプリに割り当て、その番号を環境変数 \u003ccode\u003ePORT\u003c/code\u003e に注入します。\u003cbr\u003e\n・\u003ccode\u003eos.Getenv(\"PORT\")\u003c/code\u003e を使って動的にポートを受け取る実装に変更したことで、Renderの監視システムと正しく通信できるようになりました。\u003c/li\u003e\n\u003cli data-sourcepos=\"40:1-45:181\"\u003eGORMによる「多対多（Many-to-Many）」のリレーション\u003cbr\u003e\n【詰まった箇所】\u003cbr\u003e\n「いいね機能」の実装において、ユーザーと記事の紐付けをどう管理するかが課題でした。\u003cbr\u003e\n【解決の鍵】\u003cbr\u003e\nGORMの \u003ccode\u003emany2many\u003c/code\u003e タグを活用し、中間テーブル（\u003ccode\u003earticle_likes\u003c/code\u003e）を自動生成させました。\u003cbr\u003e\n・\u003ccode\u003edb.Model(\u0026amp;article).Association(\"LikedBy\").Append(\u0026amp;user)\u003c/code\u003e という、SQLを直接書かないオブジェクト指向な操作で、複雑な関連付けを実現しました。\u003c/li\u003e\n\u003cli data-sourcepos=\"46:1-51:169\"\u003ePostgreSQLにおける「ILIKE」検索\u003cbr\u003e\n【詰まった箇所】\u003cbr\u003e\n当初の検索実装では大文字・小文字の区別や、日本語検索の柔軟性に不安がありました。\u003cbr\u003e\n【解決の鍵】\u003cbr\u003e\nSQLiteなどの単純な \u003ccode\u003eLIKE\u003c/code\u003e ではなく、PostgreSQL特有の \u003ccode\u003eILIKE\u003c/code\u003e （不感文字一致）を採用しました。\u003cbr\u003e\n・これに \u003ccode\u003e%\u003c/code\u003e（ワイルドカード）を組み合わせることで、実用的なキーワード検索機能を backend に組み込むことができました。\u003c/li\u003e\n\u003cli data-sourcepos=\"52:1-57:250\"\u003e起動時のタイムアウトと「SLOW SQL」の壁\u003cbr\u003e\n【詰まった箇所】\u003cbr\u003e\n\u003ccode\u003einformation_schema\u003c/code\u003e への重いクエリによる起動遅延がありました。\u003cbr\u003e\n【解決の鍵】\u003cbr\u003e\nRenderの無料枠という制限の中で、\u003ccode\u003edb.AutoMigrate\u003c/code\u003e が起動のたびに全テーブルをチェックするのが重すぎると判断しました。\u003cbr\u003e\n・一時的にマイグレーションをスキップする、あるいは構成を最適化することで、Renderのヘルスチェック（制限時間）内にポートを開放し、サービスを「Live」に導くことができました。\u003c/li\u003e\n\u003cli data-sourcepos=\"58:1-63:109\"\u003eデータベース移行（PostgreSQLへの完全移行）\u003cbr\u003e\n【詰まった箇所】\u003cbr\u003e\nローカルのファイルベース（SQLite）から、クラウド上のマネージドDB（PostgreSQL）への切り替え。\u003cbr\u003e\n【解決の鍵】\u003cbr\u003e\n環境変数 \u003ccode\u003eDATABASE_URL\u003c/code\u003e を適切に扱い、ドライバを \u003ccode\u003epostgres.Open(dsn)\u003c/code\u003e に変更しました。\u003cbr\u003e\n・インフラ構成をコードから切り離す「12-Factor App」の考え方を実践できました。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 data-sourcepos=\"64:1-64:35\"\u003e\n\u003cspan id=\"結果学び今後の展望\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%B5%90%E6%9E%9C%E5%AD%A6%E3%81%B3%E4%BB%8A%E5%BE%8C%E3%81%AE%E5%B1%95%E6%9C%9B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e結果・学び・今後の展望\u003c/h1\u003e\n\u003cp data-sourcepos=\"65:1-66:84\"\u003e今回の開発を通してGoを通じたバックエンド開発の方法、また詰まった箇所をどう直すかを学ぶことができました。\u003cbr\u003e\n今後はフロントエンド連携、docker化を目指していきたいです。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"68:1-68:17\"\u003e\n\u003cspan id=\"githubリンク\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#github%E3%83%AA%E3%83%B3%E3%82%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eGithubリンク\u003c/h1\u003e\n\u003cp data-sourcepos=\"69:1-69:42\"\u003e\u003ciframe id=\"qiita-embed-content__e7a477106aa28dda5278e5d8d3edfd5b\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__e7a477106aa28dda5278e5d8d3edfd5b\" data-content=\"https%3A%2F%2Fgithub.com%2Fshoheikazami%2Fnote-clone\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n","body":"## 背景\n自分は普段noteというブログアプリで読んだ本や観た映画の批評を書くのが趣味なのですが、「いつも使っているnoteを自分で開発してみれば、アプリの仕組みやバックエンドの勉強になり面白いのでは？」と思い始めました。機能に関してはGeminiを活用しながら制作しました。\n\n# 概要\n[Postman](https://www.postman.com/jp/)で[url](https://note-clone-dgiw.onrender.com/)をペーストして実装できます。\n①まずhttps://note-clone-dgiw.onrender.com/signup でアカウントネームとパスワードを設定します。\n②次にhttps://note-clone-dgiw.onrender.com/login でアカウントネームとパスワードを入力しトークンを入手します。\n③https://note-clone-dgiw.onrender.com/articles でトークンを利用し記事のタイトル、内容、画像を入力します。\n④記事のタイトル、内容、画像が表示されます。\n\n# 技術構成・アーキテクチャ\n言語　Go(Golang)　並行処理に強く、静的型付けによる堅牢なバックエンド開発が可能なため。\nフレームワーク　Gin　Goで最も人気のある軽量・高速なWebフレームワーク。ルーティングやミドルウェアの記述が直感的であるため。\nデータベース　PostgreSQL (Render)　リレーショナルデータの管理に適しており、本番環境での信頼性が高いため。\nORM　GORM　GoオブジェクトとDBテーブルを直感的に紐付け、複雑なSQLを書かずに開発スピードを向上できるため。\n認証　JWT (JSON Web Token)　ステートレスな認証方式で、サーバー側にセッションを持たせず、スケーラビリティを確保できるため。\n画像ストレージ\tCloudinary\t画像の保存、最適化、配信を自動化するクラウドサービスであるため。\nインフラ\tRender.com\tGitHubと連携したCI/CD（自動デプロイ）が可能で、モダンなPaaS環境を提供。\nAI Gemini 無料で無制限利用ができるため。基本的にGeminiでコードを書き、それを自分でチェックしました。\n\n本システムは、クライアントからのリクエストに対して、認証・ビジネスロジック・外部連携を順次行う「レイヤード」な構造を意識しています。\n\n1. リクエストの流れ\n　1.クライアント (Postman等): ```Authorization``` ヘッダーに JWT を含めて `POST /articles` を送信。\n  2.Auth Middleware: 送られてきたトークンを検証し、有効であれば `user_id` を抽出して後続の処理へ渡す。\n  3.Controller (Main Logic):\n    ・フォームデータからタイトルと内容を取得。\n    ・添付された画像ファイルを一時的に受け取る。\n  4.External Service (Cloudinary): 画像ファイルを Cloudinary へアップロードし、永続的な Image URL を受け取る。\n  5.Database (PostgreSQL): 記事のタイトル、内容、Image URL、および投稿した `user_id` を1つのレコードとして保存。\n   6. Response: 保存された記事データを JSON 形式でクライアントへ返却。\n\n# 実装のポイント（詰まった箇所）\n1. 開発環境と本番環境（PaaS）の「ポート」の差\n【詰まった箇所】\nローカルでは `8080` など固定のポートで動いていましたが、Renderでは `Port scan timeout` でデプロイが失敗し続けました。\n【解決の鍵】\nPaaS（RenderやHerokuなど）は、起動のたびに空いているポートをアプリに割り当て、その番号を環境変数 `PORT` に注入します。\n・`os.Getenv(\"PORT\")` を使って動的にポートを受け取る実装に変更したことで、Renderの監視システムと正しく通信できるようになりました。\n2. GORMによる「多対多（Many-to-Many）」のリレーション\n【詰まった箇所】\n「いいね機能」の実装において、ユーザーと記事の紐付けをどう管理するかが課題でした。\n【解決の鍵】\nGORMの `many2many` タグを活用し、中間テーブル（`article_likes`）を自動生成させました。\n・`db.Model(\u0026article).Association(\"LikedBy\").Append(\u0026user)` という、SQLを直接書かないオブジェクト指向な操作で、複雑な関連付けを実現しました。\n3. PostgreSQLにおける「ILIKE」検索\n【詰まった箇所】\n当初の検索実装では大文字・小文字の区別や、日本語検索の柔軟性に不安がありました。\n【解決の鍵】\nSQLiteなどの単純な `LIKE` ではなく、PostgreSQL特有の `ILIKE` （不感文字一致）を採用しました。\n・これに `%`（ワイルドカード）を組み合わせることで、実用的なキーワード検索機能を backend に組み込むことができました。\n4. 起動時のタイムアウトと「SLOW SQL」の壁\n【詰まった箇所】\n`information_schema` への重いクエリによる起動遅延がありました。\n【解決の鍵】\nRenderの無料枠という制限の中で、`db.AutoMigrate` が起動のたびに全テーブルをチェックするのが重すぎると判断しました。\n・一時的にマイグレーションをスキップする、あるいは構成を最適化することで、Renderのヘルスチェック（制限時間）内にポートを開放し、サービスを「Live」に導くことができました。\n5. データベース移行（PostgreSQLへの完全移行）\n【詰まった箇所】\nローカルのファイルベース（SQLite）から、クラウド上のマネージドDB（PostgreSQL）への切り替え。\n【解決の鍵】\n環境変数 `DATABASE_URL` を適切に扱い、ドライバを `postgres.Open(dsn)` に変更しました。\n・インフラ構成をコードから切り離す「12-Factor App」の考え方を実践できました。\n# 結果・学び・今後の展望\n今回の開発を通してGoを通じたバックエンド開発の方法、また詰まった箇所をどう直すかを学ぶことができました。\n今後はフロントエンド連携、docker化を目指していきたいです。\n\n# Githubリンク\nhttps://github.com/shoheikazami/note-clone\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:14:24+09:00","group":null,"id":"50e1aa9ac941893baf82","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"Go","versions":[]},{"name":"バックエンド","versions":[]}],"title":"Go(Gin)でnoteのクローンを開発しました","updated_at":"2026-05-06T11:14:24+09:00","url":"https://qiita.com/shohei_nakamoto/items/50e1aa9ac941893baf82","user":{"description":null,"facebook_id":null,"followees_count":1,"followers_count":0,"github_login_name":null,"id":"shohei_nakamoto","items_count":2,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":4153899,"profile_image_url":"https://secure.gravatar.com/avatar/ab26a9aac95e79848e95c30c95a6bf75","team_only":false,"twitter_screen_name":null,"website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cp data-sourcepos=\"1:1-1:156\"\u003e今回、Claude Code とGeminiに日本語で指示を出すだけでサイゼリヤのメニュー組み合わせアプリを作ってもらいました。\u003c/p\u003e\n\u003cp data-sourcepos=\"3:1-3:34\"\u003e\u003ciframe id=\"qiita-embed-content__37616202c921bd43f308bea05e52b06c\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__37616202c921bd43f308bea05e52b06c\" data-content=\"https%3A%2F%2Fsaizeriyagacha.goris.site%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"5:1-6:114\"\u003e最終的に React + PWA 対応の本格アプリに進化したのですが、面白かったのは 「完璧な指示を出さなくていい」 ということ。\u003cbr\u003e\n出てきたものを見て「もうちょっとこう…」と追加注文するスタイルで進めました。\u003c/p\u003e\n\u003cp data-sourcepos=\"8:1-8:85\"\u003e思い立ってからサイト公開までの所要時間は「1時間」でした。\u003c/p\u003e\n\u003cp data-sourcepos=\"10:1-10:93\"\u003eこの記事では、実際に投げたプロンプトをそのまま全部公開します。\u003c/p\u003e\n\u003cp data-sourcepos=\"12:1-12:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fc9a5dede-2fed-4278-978e-4692b7cb7f23.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=0618352664d20ddd9c82f4a8e8b388ba\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fc9a5dede-2fed-4278-978e-4692b7cb7f23.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=0618352664d20ddd9c82f4a8e8b388ba\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fc9a5dede-2fed-4278-978e-4692b7cb7f23.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=63b2a8057e35a8c699e68f0db0d56e5a 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/c9a5dede-2fed-4278-978e-4692b7cb7f23.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"14:1-14:21\"\u003e\n\u003cspan id=\"使ったツール\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BD%BF%E3%81%A3%E3%81%9F%E3%83%84%E3%83%BC%E3%83%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e使ったツール\u003c/h2\u003e\n\u003cul data-sourcepos=\"15:1-19:0\"\u003e\n\u003cli data-sourcepos=\"15:1-15:58\"\u003eGemini (Google) — メニューデータの抽出担当\u003c/li\u003e\n\u003cli data-sourcepos=\"16:1-16:51\"\u003eClaude Code (Anthropic) — アプリ実装担当\u003c/li\u003e\n\u003cli data-sourcepos=\"17:1-17:25\"\u003eエディタは VS Code\u003c/li\u003e\n\u003cli data-sourcepos=\"18:1-19:0\"\u003e自分で書いたコード行数: 0行\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"20:1-20:29\"\u003e\n\u003cspan id=\"-やりとり全記録\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#-%E3%82%84%E3%82%8A%E3%81%A8%E3%82%8A%E5%85%A8%E8%A8%98%E9%8C%B2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e🎬 やりとり全記録\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"22:1-22:61\"\u003e\n\u003cspan id=\"第0ラウンドデータ準備は-gemini-に丸投げ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%AC%AC0%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%83%87%E3%83%BC%E3%82%BF%E6%BA%96%E5%82%99%E3%81%AF-gemini-%E3%81%AB%E4%B8%B8%E6%8A%95%E3%81%92\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e第0ラウンド：データ準備は Gemini に丸投げ\u003c/h3\u003e\n\u003cp data-sourcepos=\"24:1-25:146\"\u003eアプリを作る前に、まずメニューデータが必要です。\u003cbr\u003e\nサイゼリヤ公式サイトのメニューページ（複数枚の画像）を Gemini にアップロードして、こう指示しました。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"27:1-29:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eメニュー一覧を作成して。json形式で。重複なし。\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"30:1-30:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F9a2bfec5-4011-4858-9fce-b2fedb03de9e.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=e1289076754c10275bde4c3edd08aabe\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F9a2bfec5-4011-4858-9fce-b2fedb03de9e.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=e1289076754c10275bde4c3edd08aabe\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F9a2bfec5-4011-4858-9fce-b2fedb03de9e.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=5cf46da9e535a10cffce1e7ebed76ea6 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/9a2bfec5-4011-4858-9fce-b2fedb03de9e.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"32:1-34:57\"\u003e\u003cstrong\u003e結果:\u003c/strong\u003e\u003cbr\u003e\n画像を解析して、構造化されたJSONをそのまま出力してくれました。\u003cbr\u003e\n100品超のメニューが一発でデータ化完了。\u003c/p\u003e\n\u003cul data-sourcepos=\"36:1-40:0\"\u003e\n\u003cli data-sourcepos=\"36:1-36:22\"\u003eid（注文番号）\u003c/li\u003e\n\u003cli data-sourcepos=\"37:1-37:28\"\u003ecategory（カテゴリ）\u003c/li\u003e\n\u003cli data-sourcepos=\"38:1-38:27\"\u003ename（メニュー名）\u003c/li\u003e\n\u003cli data-sourcepos=\"39:1-40:0\"\u003eprice（値段）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"41:1-41:194\"\u003e💡 サイドメニューによくある「人気の組み合わせ」など重複しがちなデータも、「重複なし」と一言添えただけでうまく整理してくれました。\u003c/p\u003e\n\u003cp data-sourcepos=\"43:1-43:294\"\u003eただ1点だけ問題が。デザートは「すぐに提供」と「食後に提供」で注文番号が違うのですが、最初のJSONでは番号がスラッシュで連結されていました（例: \"id\": \"3206/3906\"）。プログラムで使うには分けたいので追加指示。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"45:1-56:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eデザートは、nameのあとに、（すぐに）（あとで）を付与して、idを分けてください。\n { \"id\": \"3206/3906\", \"category\": \"デザート\", \"name\": \"イタリアンプリン\", \"price\": 250 },\n { \"id\": \"3212/3912\", \"category\": \"デザート\", \"name\": \"プリンとティラミスクラシコの盛合せ\", \"price\": 500 },\n { \"id\": \"3201/3901\", \"category\": \"デザート\", \"name\": \"ティラミスクラシコ\", \"price\": 300 },\n { \"id\": \"3205/3905\", \"category\": \"デザート\", \"name\": \"ミルクジェラート\", \"price\": 250 },\n { \"id\": \"3216/3916\", \"category\": \"デザート\", \"name\": \"チョコレートケーキ＆ミルクジェラート\", \"price\": 500 },\n { \"id\": \"3207/3907\", \"category\": \"デザート\", \"name\": \"チョコレートケーキ\", \"price\": 300 },\n { \"id\": \"3213/3913\", \"category\": \"デザート\", \"name\": \"トリフアイスクリーム\", \"price\": 350 },\n { \"id\": \"3215/3915\", \"category\": \"デザート\", \"name\": \"コーヒーゼリー＆ミルクジェラート\", \"price\": 350 },\n { \"id\": \"3214/3914\", \"category\": \"デザート\", \"name\": \"ジェラート＆シナモンフォッカチオ\", \"price\": 450 },\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"58:1-58:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F2076af58-14b7-46a6-90d2-1fa19ad1d0c4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=789557023e24b99c0385015eeb32d336\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F2076af58-14b7-46a6-90d2-1fa19ad1d0c4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=789557023e24b99c0385015eeb32d336\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F2076af58-14b7-46a6-90d2-1fa19ad1d0c4.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=6b4265675d69630724537ca79edc39e3 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/2076af58-14b7-46a6-90d2-1fa19ad1d0c4.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"60:1-61:48\"\u003eこれで完璧に整形された JSON 配列が完成。\u003cbr\u003e\nClaude Code に渡す準備が整いました。\u003c/p\u003e\n\u003cp data-sourcepos=\"63:1-64:197\"\u003e🤖 ポイント: 画像 → 構造化データの変換は Gemini の得意分野。\u003cbr\u003e\nClaude Code に「画像を見てJSON作って」とお願いするより、最初から Gemini に作らせて Claude Code には実装に専念させる 方が分業として効率的でした。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"66:1-66:41\"\u003e\n\u003cspan id=\"第1ラウンドベースを作る\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%AC%AC1%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%83%99%E3%83%BC%E3%82%B9%E3%82%92%E4%BD%9C%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e第1ラウンド：ベースを作る\u003c/h3\u003e\n\u003cp data-sourcepos=\"68:1-69:85\"\u003eClaude Codeへの最初の指示はこれだけ。\u003cbr\u003e\nGeminiで生成したメニューデータのJSONを一緒に貼り付けました。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"71:1-87:4\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e静的サイトとして、サイゼリヤのメニューからランダムに選択するジェネレーターを生成して。合計金額は800円～1200円になるように。モバイル対応するように。\nメニューデータは以下のとおり。\n\n``` json\n[\n  { \"id\": \"1202\", \"category\": \"サラダ\", \"name\": \"小エビのサラダ\", \"price\": 350 },\n  { \"id\": \"1209\", \"category\": \"サラダ\", \"name\": \"チキンのサラダ\", \"price\": 350 },\n  { \"id\": \"1205\", \"category\": \"サラダ\", \"name\": \"わかめのサラダ\", \"price\": 350 },\n  { \"id\": \"1425\", \"category\": \"サラダ\", \"name\": \"柔らか青豆の温サラダ\", \"price\": 200 },\n  { \"id\": \"1413\", \"category\": \"サラダ\", \"name\": \"キャロットラペ\", \"price\": 200 },\n　\n　（Geminiで生成したjsonを丸っとペーストする）　\n\n]\n```\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"89:1-89:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F441c152c-e0aa-441f-8db1-461c4f705aca.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=8c8b60947ad69592ba687eb7687addde\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F441c152c-e0aa-441f-8db1-461c4f705aca.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=8c8b60947ad69592ba687eb7687addde\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F441c152c-e0aa-441f-8db1-461c4f705aca.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=1f491eb7876adc353040c39dd7b0a73f 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/441c152c-e0aa-441f-8db1-461c4f705aca.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"91:1-93:205\"\u003e\u003cstrong\u003e結果:\u003c/strong\u003e\u003cbr\u003e\nものの数十秒で index.html が完成。1ファイル完結のHTMLで、ボタンを押すと予算内のメニューが出る、ちゃんと動くものが出てきました。\u003cbr\u003e\nカテゴリ除外機能まで勝手につけてくれて、最初の状態で アルコールやトッピングがデフォルト除外になっていたのは「分かってるな」と思いました。\u003c/p\u003e\n\u003cp data-sourcepos=\"95:1-95:129\"\u003e🤖 ポイント: 仕様を細かく指定しなくても、文脈から「こういうUIだろうな」を補完してくれる\u003c/p\u003e\n\u003ch3 data-sourcepos=\"97:1-97:59\"\u003e\n\u003cspan id=\"第2ラウンド見た目をブラッシュアップ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%AC%AC2%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E8%A6%8B%E3%81%9F%E7%9B%AE%E3%82%92%E3%83%96%E3%83%A9%E3%83%83%E3%82%B7%E3%83%A5%E3%82%A2%E3%83%83%E3%83%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e第2ラウンド：見た目をブラッシュアップ\u003c/h3\u003e\n\u003cp data-sourcepos=\"98:1-98:183\"\u003e最初は赤一色のシンプルなデザインだったので、もう少しサイゼっぽさを出したくなり、サイゼリヤ公式のロゴ画像と一緒に追加指示。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"100:1-104:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e・\"合計800円〜1200円のメニューをランダム生成\" は不要。\n・赤と緑のカラーコードを抽出して、サイトのデザインに使用して。現状緑がない。\n・ガチャ感を出したいので、演出を追加して。\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"106:1-106:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F06a8e951-05b4-42ce-a4e1-fdfbc76efad3.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=1cde20765f25c6ab5726ae124978f228\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F06a8e951-05b4-42ce-a4e1-fdfbc76efad3.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=1cde20765f25c6ab5726ae124978f228\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F06a8e951-05b4-42ce-a4e1-fdfbc76efad3.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=6860c9562960ffc078ea6978999a8e6d 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/06a8e951-05b4-42ce-a4e1-fdfbc76efad3.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"108:1-108:11\"\u003e\u003cstrong\u003e結果:\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"110:1-112:98\"\u003eトップ画像の下に余計なサブタイトルがあったので除去。\u003cbr\u003e\nまた画像から #E60012（赤）と #009944（緑）を抽出して CSS 変数化。\u003cbr\u003e\nさらに 演出を細かく指示していないのに、こんな演出が追加されました:\u003c/p\u003e\n\u003cul data-sourcepos=\"114:1-118:0\"\u003e\n\u003cli data-sourcepos=\"114:1-114:67\"\u003eスロットマシン風の高速サイクル → 徐々に減速\u003c/li\u003e\n\u003cli data-sourcepos=\"115:1-115:53\"\u003e確定時にカード背景が黄色フラッシュ\u003c/li\u003e\n\u003cli data-sourcepos=\"116:1-116:34\"\u003e紙吹雪が60個降ってくる\u003c/li\u003e\n\u003cli data-sourcepos=\"117:1-118:0\"\u003e「🎰 抽選中…」バッジがパルスアニメ\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"119:1-119:75\"\u003e「ガチャ感を出したい」だけで全部やってくれました。\u003c/p\u003e\n\u003cp data-sourcepos=\"121:1-121:138\"\u003e🤖 ポイント: 抽象的な要望（\"ガチャ感\"）でも汲み取って具体化してくれる。画像から色抽出も自動。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"123:1-123:41\"\u003e\n\u003cspan id=\"第3ラウンド実用性の改善\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%AC%AC3%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E5%AE%9F%E7%94%A8%E6%80%A7%E3%81%AE%E6%94%B9%E5%96%84\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e第3ラウンド：実用性の改善\u003c/h3\u003e\n\u003cp data-sourcepos=\"124:1-125:51\"\u003e実際に「サイゼで使う場面」を想像したら、肝心なものが目立たないことに気づきました。サイゼでは紙のシートに4桁の番号を書いて注文します。\u003cbr\u003e\n商品名より番号の方が大事なんです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"127:1-129:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eメニュー名（name）の前に、注文番号（id）を表示して。サイゼリヤでは注文する際にこの番号を入力するので、大きめに表示して。\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"131:1-132:225\"\u003e\u003cstrong\u003e結果:\u003c/strong\u003e\u003cbr\u003e\n緑のバッジで番号がドンと表示されるように。「サイゼリヤでは番号を入力する」という理由を一緒に伝えると、サイズ感やデザインの判断もうまくやってくれます。\u003c/p\u003e\n\u003cp data-sourcepos=\"134:1-134:12\"\u003e続けて、\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"135:1-137:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e注文番号とメニュー名は改行して。\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"139:1-139:87\"\u003eこれで番号が独立した行になり、視認性が一気に上がりました。\u003c/p\u003e\n\u003cp data-sourcepos=\"141:1-141:115\"\u003e🤖 ポイント: なぜそうしたいのか（背景）を伝えると、見た目の判断精度が上がる。\u003c/p\u003e\n\u003cp data-sourcepos=\"143:1-143:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fed511a9a-f571-4e61-b9f6-b3b54175f181.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=1e36a521215198a6478a3176f62c1e4e\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fed511a9a-f571-4e61-b9f6-b3b54175f181.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=1e36a521215198a6478a3176f62c1e4e\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fed511a9a-f571-4e61-b9f6-b3b54175f181.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=9fdfcd4973db853f1afae198be3af7f2 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/ed511a9a-f571-4e61-b9f6-b3b54175f181.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch3 data-sourcepos=\"145:1-145:50\"\u003e\n\u003cspan id=\"第4ラウンドreact--pwa-に作り直し\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%AC%AC4%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89react--pwa-%E3%81%AB%E4%BD%9C%E3%82%8A%E7%9B%B4%E3%81%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e第4ラウンド：React + PWA に作り直し\u003c/h3\u003e\n\u003cp data-sourcepos=\"146:1-146:106\"\u003eシングルHTMLで動いていたものを、本格的なアプリに進化させたくなったので…\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"148:1-150:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ereactで作り直して。PWA対応して、インストール可能にして。\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"152:1-152:11\"\u003e\u003cstrong\u003e結果:\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"153:1-158:0\"\u003e\n\u003cli data-sourcepos=\"153:1-153:60\"\u003eVite + React 18 のプロジェクトに丸ごと再構築\u003c/li\u003e\n\u003cli data-sourcepos=\"154:1-154:49\"\u003evite-plugin-pwa で Service Worker 自動生成\u003c/li\u003e\n\u003cli data-sourcepos=\"155:1-155:70\"\u003ebeforeinstallprompt を捕捉する独自インストールボタン\u003c/li\u003e\n\u003cli data-sourcepos=\"156:1-156:127\"\u003eアイコンSVG → PNG (192/512/maskable/apple-touch) を sharp で自動生成するスクリプトまで作ってくれた\u003c/li\u003e\n\u003cli data-sourcepos=\"157:1-158:0\"\u003enpm install → npm run build まで自動実行して、ビルドが通ることを確認してくれた\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"159:1-159:109\"\u003eここまで含めて1回の指示で完了。ホーム画面に追加できるアプリになりました。\u003c/p\u003e\n\u003cp data-sourcepos=\"161:1-161:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F5644891e-abef-417f-acc5-e9c06938e501.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=3300dff7ed3a3420b160d5c09765f609\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F5644891e-abef-417f-acc5-e9c06938e501.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=3300dff7ed3a3420b160d5c09765f609\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F5644891e-abef-417f-acc5-e9c06938e501.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=6ed886cccf784dee3fbfd587deca2644 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/5644891e-abef-417f-acc5-e9c06938e501.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"163:1-163:140\"\u003e🤖 ポイント: 「PWA対応して」だけで、マニフェスト・SW・アイコン生成・インストールUIまで全部やる。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"165:1-165:47\"\u003e\n\u003cspan id=\"第5ラウンドアイコンの微調整\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%AC%AC5%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%81%AE%E5%BE%AE%E8%AA%BF%E6%95%B4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e第5ラウンド：アイコンの微調整\u003c/h3\u003e\n\u003cp data-sourcepos=\"167:1-167:129\"\u003ePWA用に生成された「サ」のアイコンを見たら、文字が円の中央じゃなくて上にズレていました。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"169:1-171:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eアイコンの\"サ\"を中央にして。\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"173:1-173:121\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fc8aca4b9-75a7-45ac-9f62-e675337bee02.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=0cf53c9de0cf0ab5fab6dd729895a9f7\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fc8aca4b9-75a7-45ac-9f62-e675337bee02.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=0cf53c9de0cf0ab5fab6dd729895a9f7\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fc8aca4b9-75a7-45ac-9f62-e675337bee02.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=88af3ce2c0026cb4554d5a913c0e336e 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/c8aca4b9-75a7-45ac-9f62-e675337bee02.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"175:1-176:121\"\u003e\u003cstrong\u003e修正前\u003c/strong\u003e\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F26e3d7e5-cbd0-4f3f-9e50-78ccf81acd71.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=80b42226aa24ce795d0492a3d6537543\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F26e3d7e5-cbd0-4f3f-9e50-78ccf81acd71.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=80b42226aa24ce795d0492a3d6537543\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2F26e3d7e5-cbd0-4f3f-9e50-78ccf81acd71.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=aaa7f04258104c95db3ccabf900ffce6 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/26e3d7e5-cbd0-4f3f-9e50-78ccf81acd71.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"178:1-178:9\"\u003e↓↓↓\u003c/p\u003e\n\u003cp data-sourcepos=\"180:1-181:121\"\u003e\u003cstrong\u003e修正後\u003c/strong\u003e\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fcde0bb95-0cdf-4e7b-b9c2-e3d4acbeb0dc.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=46ccd40ec72c2a0f478f658a5c6b9d20\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fcde0bb95-0cdf-4e7b-b9c2-e3d4acbeb0dc.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=46ccd40ec72c2a0f478f658a5c6b9d20\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F649204%2Fcde0bb95-0cdf-4e7b-b9c2-e3d4acbeb0dc.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=e7cf8fa1b0a10de616f28e0f1ca744d4 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/cde0bb95-0cdf-4e7b-b9c2-e3d4acbeb0dc.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"183:1-183:42\"\u003e完璧に中央揃いになりました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"185:1-185:36\"\u003e\n\u003cspan id=\"やってみて気づいたこと\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6%E6%B0%97%E3%81%A5%E3%81%84%E3%81%9F%E3%81%93%E3%81%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eやってみて気づいたこと\u003c/h2\u003e\n\u003cp data-sourcepos=\"186:1-187:176\"\u003e\u003cstrong\u003e① 「完璧な指示」は必要ない\u003c/strong\u003e\u003cbr\u003e\n最初から仕様書みたいなものを書く必要はなくて、3行くらいの雑な指示 + 出てきたものを見ながら追加注文 で十分形になります。\u003c/p\u003e\n\u003cp data-sourcepos=\"189:1-190:168\"\u003e\u003cstrong\u003e② 背景・理由を添えると精度が上がる\u003c/strong\u003e\u003cbr\u003e\n「番号を大きく」だけより「サイゼでは番号で注文するから大きく」と伝える方が、適切なサイズ・色・配置になりました。\u003c/p\u003e\n\u003cp data-sourcepos=\"192:1-193:156\"\u003e\u003cstrong\u003e③ 画像を貼ると一発で伝わる\u003c/strong\u003e\u003cbr\u003e\n「サの位置が変」と文字で説明するより、スクリーンショットを貼った方が早い。色の指定もロゴ画像を渡すだけ。\u003c/p\u003e\n\u003cp data-sourcepos=\"195:1-196:174\"\u003e\u003cstrong\u003e④ 「とりあえず作って」が成立する\u003c/strong\u003e\u003cbr\u003e\nガチャ演出みたいな抽象的な要望でも、いい感じに具体化してくれます。出てきたものが気に入らなければまた指示すればいい。\u003c/p\u003e\n\u003cp data-sourcepos=\"198:1-199:108\"\u003e\u003cstrong\u003e⑤ 環境構築も任せられる\u003c/strong\u003e\u003cbr\u003e\nnpm install も npm run build も自分で打たない。エラーが出たら勝手に直してくれる。\u003c/p\u003e\n\u003cp data-sourcepos=\"201:1-203:51\"\u003e\u003cstrong\u003e⑥ AIの「得意分野」を分業する\u003c/strong\u003e\u003cbr\u003e\nGemini は画像理解・データ抽出が得意。\u003cbr\u003e\nClaude Code はコード生成・実装が得意。\u003c/p\u003e\n\u003cp data-sourcepos=\"205:1-205:125\"\u003e1つのAIに全部やらせないで、得意なことを得意なAIに任せると精度もスピードも上がります。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"207:1-207:48\"\u003e\n\u003cspan id=\"全部で何回プロンプトを送ったか\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%85%A8%E9%83%A8%E3%81%A7%E4%BD%95%E5%9B%9E%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%82%92%E9%80%81%E3%81%A3%E3%81%9F%E3%81%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e全部で何回プロンプトを送ったか\u003c/h2\u003e\n\u003cul data-sourcepos=\"208:1-210:0\"\u003e\n\u003cli data-sourcepos=\"208:1-208:86\"\u003eGemini への指示: 2回（メニュー画像 → JSON化、デザートID分割）\u003c/li\u003e\n\u003cli data-sourcepos=\"209:1-210:0\"\u003eClaude Code への指示: 6回（実装〜微調整）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"211:1-218:34\"\u003e合計 8回のプロンプト で、\u003cbr\u003e\n✅ メニューデータの構造化（公式画像から）\u003cbr\u003e\n✅ 静的サイトのプロトタイプ\u003cbr\u003e\n✅ ブランドカラー対応\u003cbr\u003e\n✅ ガチャ演出\u003cbr\u003e\n✅ UX改善\u003cbr\u003e\n✅ React + PWA への移行\u003cbr\u003e\n✅ アイコン中央揃え修正\u003c/p\u003e\n\u003cp data-sourcepos=\"220:1-220:71\"\u003eがすべて完成しました。所要時間は だいたい1時間。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"222:1-222:30\"\u003e\n\u003cspan id=\"知識不要で作れる\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%9F%A5%E8%AD%98%E4%B8%8D%E8%A6%81%E3%81%A7%E4%BD%9C%E3%82%8C%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e知識不要で作れる？\u003c/h2\u003e\n\u003cp data-sourcepos=\"223:1-224:135\"\u003e正直に言うと、プログラミングの知識ゼロで開発するのは難しいと思います。もちろん時間をかければ非エンジニアの方でも作り上げることは可能だと思いますが、”何が必要か”を知らないと、何を指示すればよいかが分からないと思います。\u003cbr\u003e\n例えば、私はプログラマなので、開発しようと思ったときには、すでに以下が頭に浮かんでいます。\u003c/p\u003e\n\u003cul data-sourcepos=\"226:1-231:0\"\u003e\n\u003cli data-sourcepos=\"226:1-226:69\"\u003eメニューのデータが必要で、json形式が適している\u003c/li\u003e\n\u003cli data-sourcepos=\"227:1-227:66\"\u003eGeminiは画像解析が得意で、json形式にしてくれる\u003c/li\u003e\n\u003cli data-sourcepos=\"228:1-228:65\"\u003eサイゼリヤ風にするには、カラーコードが必要\u003c/li\u003e\n\u003cli data-sourcepos=\"229:1-229:69\"\u003eClaude CodeはReactが得意なので、間違いにくいだろう\u003c/li\u003e\n\u003cli data-sourcepos=\"230:1-231:0\"\u003e手軽にスマホ対応するには”PWA化”と指示すればよい\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"232:1-232:12\"\u003e\n\u003cspan id=\"まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eまとめ\u003c/h2\u003e\n\u003cp data-sourcepos=\"233:1-234:16\"\u003eClaude Code とGeminiを使うと、作りたいもののイメージさえあれば Webアプリが作れる時代が来ています。\u003cbr\u003e\nポイントは:\u003c/p\u003e\n\u003cul data-sourcepos=\"235:1-240:0\"\u003e\n\u003cli data-sourcepos=\"235:1-235:23\"\u003e最初は雑でいい\u003c/li\u003e\n\u003cli data-sourcepos=\"236:1-236:41\"\u003e出てきたものを見て追加指示\u003c/li\u003e\n\u003cli data-sourcepos=\"237:1-237:29\"\u003e背景や理由を添える\u003c/li\u003e\n\u003cli data-sourcepos=\"238:1-238:20\"\u003e画像で伝える\u003c/li\u003e\n\u003cli data-sourcepos=\"239:1-240:0\"\u003e仕様じゃなく「こうしたい」を伝える\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"241:1-242:96\"\u003e「コードを書く」のではなく「作りたいものを言葉にする」スキルがこれからは大事になりそうです。\u003cbr\u003e\nとはいえ、\u003cstrong\u003eAIに指示を出すためにプログラミングの知識は必要です！\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"244:1-245:39\"\u003e完成したアプリはこちら\u003cbr\u003e\n👉 \u003ca href=\"https://saizeriyagacha.goris.site/\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://saizeriyagacha.goris.site/\u003c/a\u003e\u003c/p\u003e\n","body":"今回、Claude Code とGeminiに日本語で指示を出すだけでサイゼリヤのメニュー組み合わせアプリを作ってもらいました。\n\nhttps://saizeriyagacha.goris.site/\n\n最終的に React + PWA 対応の本格アプリに進化したのですが、面白かったのは 「完璧な指示を出さなくていい」 ということ。\n出てきたものを見て「もうちょっとこう…」と追加注文するスタイルで進めました。\n\n思い立ってからサイト公開までの所要時間は「1時間」でした。\n\nこの記事では、実際に投げたプロンプトをそのまま全部公開します。\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/c9a5dede-2fed-4278-978e-4692b7cb7f23.png)\n\n## 使ったツール\n- Gemini (Google) — メニューデータの抽出担当\n- Claude Code (Anthropic) — アプリ実装担当\n- エディタは VS Code\n- 自分で書いたコード行数: 0行\n\n## 🎬 やりとり全記録\n\n### 第0ラウンド：データ準備は Gemini に丸投げ\n\nアプリを作る前に、まずメニューデータが必要です。\nサイゼリヤ公式サイトのメニューページ（複数枚の画像）を Gemini にアップロードして、こう指示しました。\n\n```\nメニュー一覧を作成して。json形式で。重複なし。\n```\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/9a2bfec5-4011-4858-9fce-b2fedb03de9e.png)\n\n**結果:**\n画像を解析して、構造化されたJSONをそのまま出力してくれました。\n100品超のメニューが一発でデータ化完了。\n\n- id（注文番号）\n- category（カテゴリ）\n- name（メニュー名）\n- price（値段）\n\n💡 サイドメニューによくある「人気の組み合わせ」など重複しがちなデータも、「重複なし」と一言添えただけでうまく整理してくれました。\n\nただ1点だけ問題が。デザートは「すぐに提供」と「食後に提供」で注文番号が違うのですが、最初のJSONでは番号がスラッシュで連結されていました（例: \"id\": \"3206/3906\"）。プログラムで使うには分けたいので追加指示。\n\n```\nデザートは、nameのあとに、（すぐに）（あとで）を付与して、idを分けてください。\n { \"id\": \"3206/3906\", \"category\": \"デザート\", \"name\": \"イタリアンプリン\", \"price\": 250 },\n { \"id\": \"3212/3912\", \"category\": \"デザート\", \"name\": \"プリンとティラミスクラシコの盛合せ\", \"price\": 500 },\n { \"id\": \"3201/3901\", \"category\": \"デザート\", \"name\": \"ティラミスクラシコ\", \"price\": 300 },\n { \"id\": \"3205/3905\", \"category\": \"デザート\", \"name\": \"ミルクジェラート\", \"price\": 250 },\n { \"id\": \"3216/3916\", \"category\": \"デザート\", \"name\": \"チョコレートケーキ＆ミルクジェラート\", \"price\": 500 },\n { \"id\": \"3207/3907\", \"category\": \"デザート\", \"name\": \"チョコレートケーキ\", \"price\": 300 },\n { \"id\": \"3213/3913\", \"category\": \"デザート\", \"name\": \"トリフアイスクリーム\", \"price\": 350 },\n { \"id\": \"3215/3915\", \"category\": \"デザート\", \"name\": \"コーヒーゼリー＆ミルクジェラート\", \"price\": 350 },\n { \"id\": \"3214/3914\", \"category\": \"デザート\", \"name\": \"ジェラート＆シナモンフォッカチオ\", \"price\": 450 },\n```\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/2076af58-14b7-46a6-90d2-1fa19ad1d0c4.png)\n\nこれで完璧に整形された JSON 配列が完成。\nClaude Code に渡す準備が整いました。\n\n🤖 ポイント: 画像 → 構造化データの変換は Gemini の得意分野。\nClaude Code に「画像を見てJSON作って」とお願いするより、最初から Gemini に作らせて Claude Code には実装に専念させる 方が分業として効率的でした。\n\n### 第1ラウンド：ベースを作る\n\nClaude Codeへの最初の指示はこれだけ。\nGeminiで生成したメニューデータのJSONを一緒に貼り付けました。\n\n````\n静的サイトとして、サイゼリヤのメニューからランダムに選択するジェネレーターを生成して。合計金額は800円～1200円になるように。モバイル対応するように。\nメニューデータは以下のとおり。\n\n``` json\n[\n  { \"id\": \"1202\", \"category\": \"サラダ\", \"name\": \"小エビのサラダ\", \"price\": 350 },\n  { \"id\": \"1209\", \"category\": \"サラダ\", \"name\": \"チキンのサラダ\", \"price\": 350 },\n  { \"id\": \"1205\", \"category\": \"サラダ\", \"name\": \"わかめのサラダ\", \"price\": 350 },\n  { \"id\": \"1425\", \"category\": \"サラダ\", \"name\": \"柔らか青豆の温サラダ\", \"price\": 200 },\n  { \"id\": \"1413\", \"category\": \"サラダ\", \"name\": \"キャロットラペ\", \"price\": 200 },\n　\n　（Geminiで生成したjsonを丸っとペーストする）　\n\n]\n```\n````\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/441c152c-e0aa-441f-8db1-461c4f705aca.png)\n\n**結果:**\nものの数十秒で index.html が完成。1ファイル完結のHTMLで、ボタンを押すと予算内のメニューが出る、ちゃんと動くものが出てきました。\nカテゴリ除外機能まで勝手につけてくれて、最初の状態で アルコールやトッピングがデフォルト除外になっていたのは「分かってるな」と思いました。\n\n🤖 ポイント: 仕様を細かく指定しなくても、文脈から「こういうUIだろうな」を補完してくれる\n\n### 第2ラウンド：見た目をブラッシュアップ\n最初は赤一色のシンプルなデザインだったので、もう少しサイゼっぽさを出したくなり、サイゼリヤ公式のロゴ画像と一緒に追加指示。\n\n```\n・\"合計800円〜1200円のメニューをランダム生成\" は不要。\n・赤と緑のカラーコードを抽出して、サイトのデザインに使用して。現状緑がない。\n・ガチャ感を出したいので、演出を追加して。\n```\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/06a8e951-05b4-42ce-a4e1-fdfbc76efad3.png)\n\n**結果:**\n\nトップ画像の下に余計なサブタイトルがあったので除去。\nまた画像から #E60012（赤）と #009944（緑）を抽出して CSS 変数化。\nさらに 演出を細かく指示していないのに、こんな演出が追加されました:\n\n- スロットマシン風の高速サイクル → 徐々に減速\n- 確定時にカード背景が黄色フラッシュ\n- 紙吹雪が60個降ってくる\n- 「🎰 抽選中…」バッジがパルスアニメ\n\n「ガチャ感を出したい」だけで全部やってくれました。\n\n🤖 ポイント: 抽象的な要望（\"ガチャ感\"）でも汲み取って具体化してくれる。画像から色抽出も自動。\n\n### 第3ラウンド：実用性の改善\n実際に「サイゼで使う場面」を想像したら、肝心なものが目立たないことに気づきました。サイゼでは紙のシートに4桁の番号を書いて注文します。\n商品名より番号の方が大事なんです。\n\n```\nメニュー名（name）の前に、注文番号（id）を表示して。サイゼリヤでは注文する際にこの番号を入力するので、大きめに表示して。\n```\n\n**結果:**\n緑のバッジで番号がドンと表示されるように。「サイゼリヤでは番号を入力する」という理由を一緒に伝えると、サイズ感やデザインの判断もうまくやってくれます。\n\n続けて、\n```\n注文番号とメニュー名は改行して。\n```\n\nこれで番号が独立した行になり、視認性が一気に上がりました。\n\n🤖 ポイント: なぜそうしたいのか（背景）を伝えると、見た目の判断精度が上がる。\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/ed511a9a-f571-4e61-b9f6-b3b54175f181.png)\n\n### 第4ラウンド：React + PWA に作り直し\nシングルHTMLで動いていたものを、本格的なアプリに進化させたくなったので…\n\n```\nreactで作り直して。PWA対応して、インストール可能にして。\n```\n\n**結果:**\n- Vite + React 18 のプロジェクトに丸ごと再構築\n- vite-plugin-pwa で Service Worker 自動生成\n- beforeinstallprompt を捕捉する独自インストールボタン\n- アイコンSVG → PNG (192/512/maskable/apple-touch) を sharp で自動生成するスクリプトまで作ってくれた\n- npm install → npm run build まで自動実行して、ビルドが通ることを確認してくれた\n\nここまで含めて1回の指示で完了。ホーム画面に追加できるアプリになりました。\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/5644891e-abef-417f-acc5-e9c06938e501.png)\n\n🤖 ポイント: 「PWA対応して」だけで、マニフェスト・SW・アイコン生成・インストールUIまで全部やる。\n\n### 第5ラウンド：アイコンの微調整\n\nPWA用に生成された「サ」のアイコンを見たら、文字が円の中央じゃなくて上にズレていました。\n\n```\nアイコンの\"サ\"を中央にして。\n```\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/c8aca4b9-75a7-45ac-9f62-e675337bee02.png)\n\n**修正前**\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/26e3d7e5-cbd0-4f3f-9e50-78ccf81acd71.png)\n\n↓↓↓\n\n**修正後**\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649204/cde0bb95-0cdf-4e7b-b9c2-e3d4acbeb0dc.png)\n\n完璧に中央揃いになりました。\n\n## やってみて気づいたこと\n**① 「完璧な指示」は必要ない**\n最初から仕様書みたいなものを書く必要はなくて、3行くらいの雑な指示 + 出てきたものを見ながら追加注文 で十分形になります。\n\n**② 背景・理由を添えると精度が上がる**\n「番号を大きく」だけより「サイゼでは番号で注文するから大きく」と伝える方が、適切なサイズ・色・配置になりました。\n\n**③ 画像を貼ると一発で伝わる**\n「サの位置が変」と文字で説明するより、スクリーンショットを貼った方が早い。色の指定もロゴ画像を渡すだけ。\n\n**④ 「とりあえず作って」が成立する**\nガチャ演出みたいな抽象的な要望でも、いい感じに具体化してくれます。出てきたものが気に入らなければまた指示すればいい。\n\n**⑤ 環境構築も任せられる**\nnpm install も npm run build も自分で打たない。エラーが出たら勝手に直してくれる。\n\n**⑥ AIの「得意分野」を分業する**\nGemini は画像理解・データ抽出が得意。\nClaude Code はコード生成・実装が得意。\n\n1つのAIに全部やらせないで、得意なことを得意なAIに任せると精度もスピードも上がります。\n\n## 全部で何回プロンプトを送ったか\n- Gemini への指示: 2回（メニュー画像 → JSON化、デザートID分割）\n- Claude Code への指示: 6回（実装〜微調整）\n\n合計 8回のプロンプト で、\n✅ メニューデータの構造化（公式画像から）\n✅ 静的サイトのプロトタイプ\n✅ ブランドカラー対応\n✅ ガチャ演出\n✅ UX改善\n✅ React + PWA への移行\n✅ アイコン中央揃え修正\n\nがすべて完成しました。所要時間は だいたい1時間。\n\n## 知識不要で作れる？\n正直に言うと、プログラミングの知識ゼロで開発するのは難しいと思います。もちろん時間をかければ非エンジニアの方でも作り上げることは可能だと思いますが、”何が必要か”を知らないと、何を指示すればよいかが分からないと思います。\n例えば、私はプログラマなので、開発しようと思ったときには、すでに以下が頭に浮かんでいます。\n\n- メニューのデータが必要で、json形式が適している\n- Geminiは画像解析が得意で、json形式にしてくれる\n- サイゼリヤ風にするには、カラーコードが必要\n- Claude CodeはReactが得意なので、間違いにくいだろう\n- 手軽にスマホ対応するには”PWA化”と指示すればよい\n\n## まとめ\nClaude Code とGeminiを使うと、作りたいもののイメージさえあれば Webアプリが作れる時代が来ています。\nポイントは:\n- 最初は雑でいい\n- 出てきたものを見て追加指示\n- 背景や理由を添える\n- 画像で伝える\n- 仕様じゃなく「こうしたい」を伝える\n\n「コードを書く」のではなく「作りたいものを言葉にする」スキルがこれからは大事になりそうです。\nとはいえ、**AIに指示を出すためにプログラミングの知識は必要です！**\n\n完成したアプリはこちら \n👉 https://saizeriyagacha.goris.site/\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:12:18+09:00","group":null,"id":"6c292a1c0d36171efd4f","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"AI","versions":[]},{"name":"アプリ開発","versions":[]},{"name":"個人開発","versions":[]},{"name":"Gemini","versions":[]},{"name":"ClaudeCode","versions":[]}],"title":"Claude CodeとGeminiで「サイゼガチャ」を作らせてみた｜プロンプトだけで完成までの全記録","updated_at":"2026-05-06T11:12:18+09:00","url":"https://qiita.com/gorie-site/items/6c292a1c0d36171efd4f","user":{"description":"こんにちは、ゴリスです。自社開発のパッケージソフトウェア開発会社に勤めるサラリーマンエンジニアです。最近はチームメンバを抱え、マネジメント中心の業務をしています。パッケージソフトの開発現場において、経験したことや実践しているノウハウを紹介します。","facebook_id":"","followees_count":0,"followers_count":0,"github_login_name":null,"id":"gorie-site","items_count":1,"linkedin_id":"","location":"","name":"ゴリス","organization":"","permanent_id":649204,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/649204/0daa4256f6282dea41a8bc73f2c275e9b6b00999/large.png?1777979858","team_only":false,"twitter_screen_name":null,"website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:133\"\u003e\n\u003cspan id=\"インフラエンジニアが100日でpythonを覚える7日目-csvから抽出した結果を別csvファイルに書き出す\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%8C100%E6%97%A5%E3%81%A7python%E3%82%92%E8%A6%9A%E3%81%88%E3%82%8B7%E6%97%A5%E7%9B%AE-csv%E3%81%8B%E3%82%89%E6%8A%BD%E5%87%BA%E3%81%97%E3%81%9F%E7%B5%90%E6%9E%9C%E3%82%92%E5%88%A5csv%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eインフラエンジニアが100日でPythonを覚える：7日目 CSVから抽出した結果を別CSVファイルに書き出す\u003c/h1\u003e\n\u003ch2 data-sourcepos=\"3:1-3:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"5:1-5:141\"\u003eインフラ現場では、サーバ一覧、監視対象一覧、作業対象一覧などをCSVで管理することがよくあります。\u003c/p\u003e\n\u003cp data-sourcepos=\"7:1-7:130\"\u003e6日目までは、PythonでCSVを読み込み、条件に合うサーバを画面に表示するところまで学びました。\u003c/p\u003e\n\u003cp data-sourcepos=\"9:1-9:167\"\u003e7日目では、さらに一歩進めて、\u003cstrong\u003e条件に合うサーバだけを抽出し、その結果を別のCSVファイルに書き出す\u003c/strong\u003e方法を学びます。\u003c/p\u003e\n\u003cp data-sourcepos=\"11:1-11:93\"\u003eたとえば、以下のような作業をPythonで自動化できるようになります。\u003c/p\u003e\n\u003cul data-sourcepos=\"13:1-17:0\"\u003e\n\u003cli data-sourcepos=\"13:1-13:65\"\u003eサーバ一覧CSVから稼働中サーバだけを抽出する\u003c/li\u003e\n\u003cli data-sourcepos=\"14:1-14:59\"\u003eWebサーバだけの作業対象リストを作成する\u003c/li\u003e\n\u003cli data-sourcepos=\"15:1-15:43\"\u003eDBサーバだけを別CSVに出力する\u003c/li\u003e\n\u003cli data-sourcepos=\"16:1-17:0\"\u003e抽出結果をExcelや別ツールで確認できる形にする\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"18:1-18:78\"\u003e運用保守でも、設計・構築でも使える基本的な処理です。\u003c/p\u003e\n\u003chr data-sourcepos=\"20:1-21:0\"\u003e\n\u003ch2 data-sourcepos=\"22:1-22:21\"\u003e\n\u003cspan id=\"本日のゴール\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%9C%AC%E6%97%A5%E3%81%AE%E3%82%B4%E3%83%BC%E3%83%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e本日のゴール\u003c/h2\u003e\n\u003cp data-sourcepos=\"24:1-24:36\"\u003e本日のゴールは以下です。\u003c/p\u003e\n\u003cul data-sourcepos=\"26:1-30:0\"\u003e\n\u003cli data-sourcepos=\"26:1-26:32\"\u003eCSVファイルを読み込む\u003c/li\u003e\n\u003cli data-sourcepos=\"27:1-27:44\"\u003e条件に合うサーバだけ抽出する\u003c/li\u003e\n\u003cli data-sourcepos=\"28:1-28:56\"\u003e抽出結果を新しいCSVファイルに書き出す\u003c/li\u003e\n\u003cli data-sourcepos=\"29:1-30:0\"\u003ePowerShellで出力結果を確認する\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"31:1-31:48\"\u003e今回は、以下の条件で抽出します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"33:1-37:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003erole が web\nかつ\nstatus が running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"39:1-39:94\"\u003eつまり、\u003cstrong\u003e稼働中のWebサーバだけを別CSVに出力する\u003c/strong\u003e処理を作ります。\u003c/p\u003e\n\u003chr data-sourcepos=\"41:1-42:0\"\u003e\n\u003ch2 data-sourcepos=\"43:1-43:15\"\u003e\n\u003cspan id=\"前提知識\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%89%8D%E6%8F%90%E7%9F%A5%E8%AD%98\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e前提知識\u003c/h2\u003e\n\u003cp data-sourcepos=\"45:1-45:66\"\u003eこの記事では、以下を学習済みとして進めます。\u003c/p\u003e\n\u003cul data-sourcepos=\"47:1-55:0\"\u003e\n\u003cli data-sourcepos=\"47:1-47:11\"\u003e\u003ccode\u003eprint()\u003c/code\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"48:1-48:8\"\u003e変数\u003c/li\u003e\n\u003cli data-sourcepos=\"49:1-49:8\"\u003e\u003ccode\u003elist\u003c/code\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"50:1-50:7\"\u003e\u003ccode\u003efor\u003c/code\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"51:1-51:6\"\u003e\u003ccode\u003eif\u003c/code\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"52:1-52:8\"\u003e\u003ccode\u003edict\u003c/code\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"53:1-53:17\"\u003eCSV読み込み\u003c/li\u003e\n\u003cli data-sourcepos=\"54:1-55:0\"\u003e\u003ccode\u003ecsv.DictReader\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"56:1-56:39\"\u003e環境は以下を前提にします。\u003c/p\u003e\n\u003cul data-sourcepos=\"58:1-62:0\"\u003e\n\u003cli data-sourcepos=\"58:1-58:9\"\u003eWindows\u003c/li\u003e\n\u003cli data-sourcepos=\"59:1-59:12\"\u003ePowerShell\u003c/li\u003e\n\u003cli data-sourcepos=\"60:1-60:32\"\u003ePythonインストール済み\u003c/li\u003e\n\u003cli data-sourcepos=\"61:1-62:0\"\u003e作業フォルダでPythonファイルを実行できる状態\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr data-sourcepos=\"63:1-64:0\"\u003e\n\u003ch2 data-sourcepos=\"65:1-65:33\"\u003e\n\u003cspan id=\"講義コード解説\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%AC%9B%E7%BE%A9%E3%82%B3%E3%83%BC%E3%83%89%E8%A7%A3%E8%AA%AC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e講義（コード＋解説）\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"67:1-67:37\"\u003e\n\u003cspan id=\"作業フォルダを作成する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BD%9C%E6%A5%AD%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e作業フォルダを作成する\u003c/h3\u003e\n\u003cp data-sourcepos=\"69:1-69:61\"\u003ePowerShellを開き、作業フォルダを作成します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"71:1-74:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003emkdir\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003eC:\\python-100days\\day7\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003cspan class=\"n\"\u003ecd\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003eC:\\python-100days\\day7\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch3 data-sourcepos=\"76:1-76:43\"\u003e\n\u003cspan id=\"入力用csvファイルを作成する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%85%A5%E5%8A%9B%E7%94%A8csv%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e入力用CSVファイルを作成する\u003c/h3\u003e\n\u003cp data-sourcepos=\"78:1-78:66\"\u003eまず、元データとなる \u003ccode\u003eservers.csv\u003c/code\u003e を作成します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"80:1-82:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003enotepad\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003eservers.csv\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"84:1-84:51\"\u003e以下の内容を貼り付けて保存します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"csv\" data-sourcepos=\"86:1-92:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\nweb01,192.168.1.10,web,running\ndb01,192.168.1.20,db,running\nweb02,192.168.1.11,web,stopped\nbackup01,192.168.1.30,backup,running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"94:1-94:51\"\u003e今回のCSVには、以下の列があります。\u003c/p\u003e\n\u003ctable data-sourcepos=\"96:1-101:25\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"96:1-96:19\"\u003e\n\u003cth data-sourcepos=\"96:2-96:9\"\u003e列名\u003c/th\u003e\n\u003cth data-sourcepos=\"96:11-96:18\"\u003e意味\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"98:1-98:23\"\u003e\n\u003ctd data-sourcepos=\"98:2-98:7\"\u003ename\u003c/td\u003e\n\u003ctd data-sourcepos=\"98:9-98:22\"\u003eサーバ名\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"99:1-99:23\"\u003e\n\u003ctd data-sourcepos=\"99:2-99:5\"\u003eip\u003c/td\u003e\n\u003ctd data-sourcepos=\"99:7-99:22\"\u003eIPアドレス\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"100:1-100:29\"\u003e\n\u003ctd data-sourcepos=\"100:2-100:7\"\u003erole\u003c/td\u003e\n\u003ctd data-sourcepos=\"100:9-100:28\"\u003eサーバの役割\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"101:1-101:25\"\u003e\n\u003ctd data-sourcepos=\"101:2-101:9\"\u003estatus\u003c/td\u003e\n\u003ctd data-sourcepos=\"101:11-101:24\"\u003e稼働状態\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr data-sourcepos=\"103:1-104:0\"\u003e\n\u003ch3 data-sourcepos=\"105:1-105:37\"\u003e\n\u003cspan id=\"pythonファイルを作成する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#python%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003ePythonファイルを作成する\u003c/h3\u003e\n\u003cp data-sourcepos=\"107:1-107:48\"\u003e次に、Pythonファイルを作成します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"109:1-111:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003enotepad\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003eexport_web_running.py\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"113:1-113:36\"\u003e以下のコードを書きます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"115:1-144:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e# 入力ファイルと出力ファイルの名前を変数に入れる\n\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eservers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb_running_servers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e# CSVファイルを読み込む\n\u003c/span\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003er\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e# 条件に合うサーバを入れるための空リスト\n\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e# role が web かつ status が running のサーバだけ抽出する\n\u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n        \u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e# 抽出結果を新しいCSVファイルに書き出す\n\u003c/span\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ew\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enewline\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteheader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e抽出結果をCSVファイルに出力しました。\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e出力ファイル:\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"146:1-147:0\"\u003e\n\u003ch3 data-sourcepos=\"148:1-148:25\"\u003e\n\u003cspan id=\"コードの全体像\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%85%A8%E4%BD%93%E5%83%8F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eコードの全体像\u003c/h3\u003e\n\u003cp data-sourcepos=\"150:1-150:45\"\u003e今回の処理は、以下の流れです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"152:1-162:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eCSVを読み込む\n↓\n条件に合うサーバだけ抽出する\n↓\n抽出結果をリストに入れる\n↓\n新しいCSVファイルに書き出す\n↓\n結果を確認する\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"164:1-165:78\"\u003eいきなり全体を理解しようとすると難しく感じます。\u003cbr\u003e\nまずは、処理をブロックごとに分けて見るのが大切です。\u003c/p\u003e\n\u003chr data-sourcepos=\"167:1-168:0\"\u003e\n\u003ch3 data-sourcepos=\"169:1-169:31\"\u003e\n\u003cspan id=\"csvを扱うための準備\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#csv%E3%82%92%E6%89%B1%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E6%BA%96%E5%82%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCSVを扱うための準備\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"171:1-173:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"175:1-175:84\"\u003ePythonでCSVを扱うために、\u003ccode\u003ecsv\u003c/code\u003e モジュールを読み込んでいます。\u003c/p\u003e\n\u003cp data-sourcepos=\"177:1-177:81\"\u003e\u003ccode\u003ecsv\u003c/code\u003e はPython標準機能なので、追加インストールは不要です。\u003c/p\u003e\n\u003chr data-sourcepos=\"179:1-180:0\"\u003e\n\u003ch3 data-sourcepos=\"181:1-181:61\"\u003e\n\u003cspan id=\"入力ファイル名と出力ファイル名を決める\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%85%A5%E5%8A%9B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D%E3%81%A8%E5%87%BA%E5%8A%9B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D%E3%82%92%E6%B1%BA%E3%82%81%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e入力ファイル名と出力ファイル名を決める\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"183:1-186:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eservers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb_running_servers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"188:1-188:73\"\u003e\u003ccode\u003einput_file\u003c/code\u003e には読み込むCSVファイル名を入れています。\u003c/p\u003e\n\u003cp data-sourcepos=\"190:1-190:83\"\u003e\u003ccode\u003eoutput_file\u003c/code\u003e には新しく作成するCSVファイル名を入れています。\u003c/p\u003e\n\u003cp data-sourcepos=\"192:1-192:87\"\u003eファイル名を変数にしておくと、後から変更しやすくなります。\u003c/p\u003e\n\u003chr data-sourcepos=\"194:1-195:0\"\u003e\n\u003ch3 data-sourcepos=\"196:1-196:22\"\u003e\n\u003cspan id=\"csvを読み込む\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#csv%E3%82%92%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%80\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCSVを読み込む\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"198:1-201:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003er\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"203:1-203:57\"\u003eここでは \u003ccode\u003eservers.csv\u003c/code\u003e を読み込んでいます。\u003c/p\u003e\n\u003cp data-sourcepos=\"205:1-205:39\"\u003e\u003ccode\u003e\"r\"\u003c/code\u003e は読み込みモードです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"207:1-209:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"211:1-211:71\"\u003e\u003ccode\u003eDictReader\u003c/code\u003e を使うと、CSVの1行を辞書として扱えます。\u003c/p\u003e\n\u003cp data-sourcepos=\"213:1-213:36\"\u003eたとえば、CSVのこの行は、\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"csv\" data-sourcepos=\"215:1-217:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eweb01,192.168.1.10,web,running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"219:1-219:66\"\u003ePythonでは、だいたい以下のような形で扱えます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"221:1-228:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb01\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e192.168.1.10\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n    \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"230:1-230:69\"\u003eそのため、以下のように列名で値を取り出せます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"232:1-237:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"239:1-239:70\"\u003eまた、今回は以下のように \u003ccode\u003elist()\u003c/code\u003e を使っています。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"241:1-243:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"245:1-245:72\"\u003eこれにより、CSVの内容をリストとして保持できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"247:1-248:135\"\u003e前回学んだ通り、\u003ccode\u003ecsv.DictReader(file)\u003c/code\u003e は一度読み進めると再利用しにくいです。\u003cbr\u003e\nそのため、何度も確認したり処理を分けたりする場合は、リストに変換しておくと扱いやすいです。\u003c/p\u003e\n\u003chr data-sourcepos=\"250:1-251:0\"\u003e\n\u003ch3 data-sourcepos=\"252:1-252:49\"\u003e\n\u003cspan id=\"抽出結果を入れる空リストを作る\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%8A%BD%E5%87%BA%E7%B5%90%E6%9E%9C%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B%E7%A9%BA%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BD%9C%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e抽出結果を入れる空リストを作る\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"254:1-256:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"258:1-258:69\"\u003e条件に合ったサーバを入れるための空リストです。\u003c/p\u003e\n\u003cp data-sourcepos=\"260:1-260:21\"\u003e最初は空です。\u003c/p\u003e\n\u003cp data-sourcepos=\"262:1-262:105\"\u003eこのあと、条件に合うサーバを見つけたら、このリストに追加していきます。\u003c/p\u003e\n\u003chr data-sourcepos=\"264:1-265:0\"\u003e\n\u003ch3 data-sourcepos=\"266:1-266:46\"\u003e\n\u003cspan id=\"条件に合うサーバだけ抽出する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%9D%A1%E4%BB%B6%E3%81%AB%E5%90%88%E3%81%86%E3%82%B5%E3%83%BC%E3%83%90%E3%81%A0%E3%81%91%E6%8A%BD%E5%87%BA%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e条件に合うサーバだけ抽出する\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"268:1-272:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n        \u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"274:1-274:33\"\u003eここが今回の中心です。\u003c/p\u003e\n\u003cp data-sourcepos=\"276:1-276:24\"\u003e意味は以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"278:1-282:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eservers の中から1件ずつ server として取り出す\nrole が web で、かつ status が running なら\nその server を target_servers に追加する\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"284:1-284:24\"\u003e条件はここです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"286:1-288:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"290:1-290:66\"\u003e\u003ccode\u003eand\u003c/code\u003e は「両方の条件を満たす」という意味です。\u003c/p\u003e\n\u003cp data-sourcepos=\"292:1-292:81\"\u003eつまり、以下の両方を満たすサーバだけが対象になります。\u003c/p\u003e\n\u003cul data-sourcepos=\"294:1-296:0\"\u003e\n\u003cli data-sourcepos=\"294:1-294:18\"\u003e\n\u003ccode\u003erole\u003c/code\u003e が \u003ccode\u003eweb\u003c/code\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"295:1-296:0\"\u003e\n\u003ccode\u003estatus\u003c/code\u003e が \u003ccode\u003erunning\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"297:1-297:66\"\u003e今回のCSVでは、条件に合うのは \u003ccode\u003eweb01\u003c/code\u003e だけです。\u003c/p\u003e\n\u003chr data-sourcepos=\"299:1-300:0\"\u003e\n\u003ch3 data-sourcepos=\"301:1-301:38\"\u003e\n\u003cspan id=\"append-でリストに追加する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#append-%E3%81%A7%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eappend でリストに追加する\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"303:1-305:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"307:1-307:65\"\u003e\u003ccode\u003eappend()\u003c/code\u003e は、リストに要素を追加する命令です。\u003c/p\u003e\n\u003cp data-sourcepos=\"309:1-309:102\"\u003e今回の場合は、条件に合ったサーバ情報を \u003ccode\u003etarget_servers\u003c/code\u003e に追加しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"311:1-311:83\"\u003e最終的に、\u003ccode\u003etarget_servers\u003c/code\u003e には以下のようなデータが入ります。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"313:1-317:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb01\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e192.168.1.10\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"319:1-320:0\"\u003e\n\u003ch3 data-sourcepos=\"321:1-321:43\"\u003e\n\u003cspan id=\"新しいcsvファイルを書き出す\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%96%B0%E3%81%97%E3%81%84csv%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e新しいCSVファイルを書き出す\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"323:1-325:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ew\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enewline\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"327:1-327:63\"\u003eここでは、出力用CSVファイルを開いています。\u003c/p\u003e\n\u003cp data-sourcepos=\"329:1-329:39\"\u003e\u003ccode\u003e\"w\"\u003c/code\u003e は書き込みモードです。\u003c/p\u003e\n\u003cp data-sourcepos=\"331:1-331:105\"\u003e注意点として、同じ名前のファイルがすでに存在する場合は上書きされます。\u003c/p\u003e\n\u003cp data-sourcepos=\"333:1-334:96\"\u003e\u003ccode\u003enewline=\"\"\u003c/code\u003e は、Windows環境でCSVを書き出すときに付けておくとよい指定です。\u003cbr\u003e\n環境によっては、これがないとCSVの行間に空行が入ることがあります。\u003c/p\u003e\n\u003chr data-sourcepos=\"336:1-337:0\"\u003e\n\u003ch3 data-sourcepos=\"338:1-338:31\"\u003e\n\u003cspan id=\"csvの列名を指定する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#csv%E3%81%AE%E5%88%97%E5%90%8D%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCSVの列名を指定する\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"340:1-342:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"344:1-344:51\"\u003e出力するCSVの列名を指定しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"346:1-346:51\"\u003e今回のCSVは以下の列を持っています。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"348:1-353:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename\nip\nrole\nstatus\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"355:1-355:63\"\u003eそのため、Python側でも同じ列名を指定します。\u003c/p\u003e\n\u003chr data-sourcepos=\"357:1-358:0\"\u003e\n\u003ch3 data-sourcepos=\"359:1-359:24\"\u003e\n\u003cspan id=\"dictwriter-を使う\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#dictwriter-%E3%82%92%E4%BD%BF%E3%81%86\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eDictWriter を使う\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"361:1-363:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"365:1-365:83\"\u003e\u003ccode\u003ecsv.DictWriter\u003c/code\u003e は、辞書データをCSVに書き出すための機能です。\u003c/p\u003e\n\u003cp data-sourcepos=\"367:1-367:57\"\u003e読み込みでは \u003ccode\u003ecsv.DictReader\u003c/code\u003e を使いました。\u003c/p\u003e\n\u003cp data-sourcepos=\"369:1-369:54\"\u003e書き込みでは \u003ccode\u003ecsv.DictWriter\u003c/code\u003e を使います。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"371:1-374:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eCSV読み込み：csv.DictReader\nCSV書き込み：csv.DictWriter\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"376:1-376:45\"\u003eこのセットで覚えるとよいです。\u003c/p\u003e\n\u003chr data-sourcepos=\"378:1-379:0\"\u003e\n\u003ch3 data-sourcepos=\"380:1-380:31\"\u003e\n\u003cspan id=\"ヘッダーを書き出す\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%98%E3%83%83%E3%83%80%E3%83%BC%E3%82%92%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eヘッダーを書き出す\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"382:1-384:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteheader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"386:1-386:52\"\u003eCSVの1行目に列名を書き出しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"388:1-388:36\"\u003eつまり、以下の部分です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"csv\" data-sourcepos=\"390:1-392:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"394:1-394:63\"\u003eこれを書かないと、出力CSVに列名が出ません。\u003c/p\u003e\n\u003cp data-sourcepos=\"396:1-396:170\"\u003e実務では、あとからExcelで確認したり、別システムへ渡したりすることが多いため、ヘッダーは基本的に出した方がよいです。\u003c/p\u003e\n\u003chr data-sourcepos=\"398:1-399:0\"\u003e\n\u003ch3 data-sourcepos=\"400:1-400:31\"\u003e\n\u003cspan id=\"抽出結果を書き出す\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%8A%BD%E5%87%BA%E7%B5%90%E6%9E%9C%E3%82%92%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%99\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e抽出結果を書き出す\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"402:1-404:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"406:1-406:80\"\u003e\u003ccode\u003etarget_servers\u003c/code\u003e に入っているデータをCSVに書き出しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"408:1-408:101\"\u003e1行だけ書く場合は \u003ccode\u003ewriterow()\u003c/code\u003e、複数行を書く場合は \u003ccode\u003ewriterows()\u003c/code\u003e を使います。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"410:1-413:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"415:1-415:116\"\u003e今回はリストに入った複数行のデータを書き出す想定なので、\u003ccode\u003ewriterows()\u003c/code\u003e を使います。\u003c/p\u003e\n\u003chr data-sourcepos=\"417:1-418:0\"\u003e\n\u003ch2 data-sourcepos=\"419:1-419:37\"\u003e\n\u003cspan id=\"実行コマンドpowershell\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E8%A1%8C%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89powershell\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実行コマンド（PowerShell）\u003c/h2\u003e\n\u003cp data-sourcepos=\"421:1-421:39\"\u003ePythonファイルを実行します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"423:1-425:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003epython\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003eexport_web_running.py\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"427:1-427:51\"\u003e作成されたCSVファイルを確認します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"429:1-431:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kr\"\u003etype\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eweb_running_servers.csv\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"433:1-433:72\"\u003eメモ帳で確認したい場合は、以下でも確認できます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"435:1-437:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003enotepad\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003eweb_running_servers.csv\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"439:1-439:93\"\u003e作業フォルダ内のファイル一覧を確認する場合は、以下を使います。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"441:1-443:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"445:1-446:0\"\u003e\n\u003ch2 data-sourcepos=\"447:1-447:15\"\u003e\n\u003cspan id=\"実行結果\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実行結果\u003c/h2\u003e\n\u003cp data-sourcepos=\"449:1-449:78\"\u003ePythonファイルを実行すると、以下のように表示されます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"451:1-454:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e抽出結果をCSVファイルに出力しました。\n出力ファイル: web_running_servers.csv\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"456:1-456:91\"\u003e\u003ccode\u003etype web_running_servers.csv\u003c/code\u003e を実行すると、以下のように表示されます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"458:1-461:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\nweb01,192.168.1.10,web,running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"463:1-463:98\"\u003e\u003ccode\u003eservers.csv\u003c/code\u003e の中から、以下の条件に合うサーバだけが出力されています。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"465:1-469:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003erole が web\nかつ\nstatus が running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"471:1-471:72\"\u003e今回のデータでは、条件に合うのは \u003ccode\u003eweb01\u003c/code\u003e だけです。\u003c/p\u003e\n\u003chr data-sourcepos=\"473:1-474:0\"\u003e\n\u003ch2 data-sourcepos=\"475:1-475:45\"\u003e\n\u003cspan id=\"解説初心者向け実務目線\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%A7%A3%E8%AA%AC%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%E5%AE%9F%E5%8B%99%E7%9B%AE%E7%B7%9A\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e解説（初心者向け＋実務目線）\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"477:1-477:67\"\u003e\n\u003cspan id=\"1-今回の処理は作業対象リスト作成に近い\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E4%BB%8A%E5%9B%9E%E3%81%AE%E5%87%A6%E7%90%86%E3%81%AF%E4%BD%9C%E6%A5%AD%E5%AF%BE%E8%B1%A1%E3%83%AA%E3%82%B9%E3%83%88%E4%BD%9C%E6%88%90%E3%81%AB%E8%BF%91%E3%81%84\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. 今回の処理は「作業対象リスト作成」に近い\u003c/h3\u003e\n\u003cp data-sourcepos=\"479:1-479:99\"\u003eインフラ現場では、作業前に対象サーバを絞り込むことがよくあります。\u003c/p\u003e\n\u003cp data-sourcepos=\"481:1-481:48\"\u003eたとえば、以下のような場面です。\u003c/p\u003e\n\u003cul data-sourcepos=\"483:1-488:0\"\u003e\n\u003cli data-sourcepos=\"483:1-483:47\"\u003e稼働中のサーバだけを対象にする\u003c/li\u003e\n\u003cli data-sourcepos=\"484:1-484:38\"\u003eWebサーバだけを対象にする\u003c/li\u003e\n\u003cli data-sourcepos=\"485:1-485:37\"\u003eDBサーバだけを対象にする\u003c/li\u003e\n\u003cli data-sourcepos=\"486:1-486:38\"\u003e停止中のサーバを除外する\u003c/li\u003e\n\u003cli data-sourcepos=\"487:1-488:0\"\u003eメンテナンス対象だけを別ファイルにまとめる\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"489:1-489:66\"\u003e今回のPython処理は、これらの作業の基本形です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"491:1-495:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e一覧を読む\n条件で絞る\n結果を別ファイルに出す\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"497:1-497:57\"\u003eこの流れは、運用自動化でよく使います。\u003c/p\u003e\n\u003chr data-sourcepos=\"499:1-500:0\"\u003e\n\u003ch3 data-sourcepos=\"501:1-501:61\"\u003e\n\u003cspan id=\"2-読み込みと書き込みは別の処理\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%A8%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF%E3%81%AF%E5%88%A5%E3%81%AE%E5%87%A6%E7%90%86\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. 「読み込み」と「書き込み」は別の処理\u003c/h3\u003e\n\u003cp data-sourcepos=\"503:1-503:36\"\u003eCSVを読む処理は以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"505:1-508:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003er\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"510:1-510:36\"\u003eCSVを書く処理は以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"512:1-517:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ew\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enewline\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteheader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"519:1-519:81\"\u003e最初は長く見えますが、役割を分けると理解しやすいです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"521:1-524:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003er：read、読み込み\nw：write、書き込み\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"526:1-527:0\"\u003e\n\u003ch3 data-sourcepos=\"528:1-528:55\"\u003e\n\u003cspan id=\"3-条件を変えるだけで別のcsvを作れる\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-%E6%9D%A1%E4%BB%B6%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E3%81%A0%E3%81%91%E3%81%A7%E5%88%A5%E3%81%AEcsv%E3%82%92%E4%BD%9C%E3%82%8C%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. 条件を変えるだけで別のCSVを作れる\u003c/h3\u003e\n\u003cp data-sourcepos=\"530:1-530:36\"\u003e今回の条件は以下でした。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"532:1-534:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"536:1-536:66\"\u003eこの条件を変えるだけで、別の抽出ができます。\u003c/p\u003e\n\u003cp data-sourcepos=\"538:1-538:60\"\u003e稼働中サーバだけ抽出するなら、以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"540:1-542:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"544:1-544:53\"\u003eDBサーバだけ抽出するなら、以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"546:1-548:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003edb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"550:1-550:54\"\u003eWebサーバだけ抽出するなら、以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"552:1-554:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"556:1-556:114\"\u003eつまり、基本の形を覚えれば、条件を変えていろいろな作業対象リストを作れます。\u003c/p\u003e\n\u003chr data-sourcepos=\"558:1-559:0\"\u003e\n\u003ch3 data-sourcepos=\"560:1-560:67\"\u003e\n\u003cspan id=\"4-ファイルは作成されるが中身が空になる場合\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AF%E4%BD%9C%E6%88%90%E3%81%95%E3%82%8C%E3%82%8B%E3%81%8C%E4%B8%AD%E8%BA%AB%E3%81%8C%E7%A9%BA%E3%81%AB%E3%81%AA%E3%82%8B%E5%A0%B4%E5%90%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. ファイルは作成されるが中身が空になる場合\u003c/h3\u003e\n\u003cp data-sourcepos=\"562:1-562:96\"\u003eファイルは作成されたのに、データ行が表示されないことがあります。\u003c/p\u003e\n\u003cp data-sourcepos=\"564:1-564:72\"\u003eたとえば、出力結果が以下のようになるケースです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"566:1-568:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"570:1-570:120\"\u003eこの場合、CSVファイル自体は作成されていますが、条件に一致したデータがありません。\u003c/p\u003e\n\u003cp data-sourcepos=\"572:1-572:36\"\u003eよくある原因は以下です。\u003c/p\u003e\n\u003cul data-sourcepos=\"574:1-579:0\"\u003e\n\u003cli data-sourcepos=\"574:1-574:29\"\u003e条件が間違っている\u003c/li\u003e\n\u003cli data-sourcepos=\"575:1-575:29\"\u003eCSVの値が想定と違う\u003c/li\u003e\n\u003cli data-sourcepos=\"576:1-576:29\"\u003e大文字小文字が違う\u003c/li\u003e\n\u003cli data-sourcepos=\"577:1-577:23\"\u003eCSVの列名が違う\u003c/li\u003e\n\u003cli data-sourcepos=\"578:1-579:0\"\u003e見ている出力ファイル名が違う\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"580:1-580:57\"\u003eたとえば、Pythonでは以下は一致しません。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"582:1-584:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eRunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"586:1-586:42\"\u003e大文字小文字が違うためです。\u003c/p\u003e\n\u003cp data-sourcepos=\"588:1-588:63\"\u003eまた、コードで以下のように書いている場合、\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"590:1-592:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"594:1-594:69\"\u003eCSVのヘッダーも以下になっている必要があります。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"csv\" data-sourcepos=\"596:1-598:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"600:1-600:108\"\u003eもしCSV側が以下のようになっていると、列名が違うため正しく処理できません。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"csv\" data-sourcepos=\"602:1-604:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,state\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003chr data-sourcepos=\"606:1-607:0\"\u003e\n\u003ch3 data-sourcepos=\"608:1-608:41\"\u003e\n\u003cspan id=\"5-原因調査には-print-が便利\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#5-%E5%8E%9F%E5%9B%A0%E8%AA%BF%E6%9F%BB%E3%81%AB%E3%81%AF-print-%E3%81%8C%E4%BE%BF%E5%88%A9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5. 原因調査には print が便利\u003c/h3\u003e\n\u003cp data-sourcepos=\"610:1-610:98\"\u003e動きがわからないときは、途中に \u003ccode\u003eprint()\u003c/code\u003e を入れると確認しやすいです。\u003c/p\u003e\n\u003cp data-sourcepos=\"612:1-612:81\"\u003eたとえば、CSVが正しく読めているか確認するには以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"614:1-616:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"618:1-618:52\"\u003e1件ずつ中身を確認するには以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"620:1-623:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"625:1-625:48\"\u003e抽出件数を確認するには以下です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"627:1-629:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e抽出件数:\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nf\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"631:1-631:84\"\u003e実務でも、まずは小さく確認しながら進めることが大切です。\u003c/p\u003e\n\u003chr data-sourcepos=\"633:1-634:0\"\u003e\n\u003ch2 data-sourcepos=\"635:1-635:15\"\u003e\n\u003cspan id=\"練習問題\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%B7%B4%E7%BF%92%E5%95%8F%E9%A1%8C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e練習問題\u003c/h2\u003e\n\u003cp data-sourcepos=\"637:1-637:36\"\u003e以下のCSVを前提にします。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"csv\" data-sourcepos=\"639:1-645:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\nweb01,192.168.1.10,web,running\ndb01,192.168.1.20,db,running\nweb02,192.168.1.11,web,stopped\nbackup01,192.168.1.30,backup,running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch3 data-sourcepos=\"647:1-647:11\"\u003e\n\u003cspan id=\"問題1\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%95%8F%E9%A1%8C1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e問題1\u003c/h3\u003e\n\u003cp data-sourcepos=\"649:1-649:97\"\u003e\u003ccode\u003estatus\u003c/code\u003e が \u003ccode\u003erunning\u003c/code\u003e のサーバだけを \u003ccode\u003erunning_servers.csv\u003c/code\u003e に出力してください。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"651:1-651:11\"\u003e\n\u003cspan id=\"問題2\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%95%8F%E9%A1%8C2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e問題2\u003c/h3\u003e\n\u003cp data-sourcepos=\"653:1-653:85\"\u003e\u003ccode\u003erole\u003c/code\u003e が \u003ccode\u003edb\u003c/code\u003e のサーバだけを \u003ccode\u003edb_servers.csv\u003c/code\u003e に出力してください。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"655:1-655:11\"\u003e\n\u003cspan id=\"問題3\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%95%8F%E9%A1%8C3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e問題3\u003c/h3\u003e\n\u003cp data-sourcepos=\"657:1-657:87\"\u003e\u003ccode\u003erole\u003c/code\u003e が \u003ccode\u003eweb\u003c/code\u003e のサーバだけを \u003ccode\u003eweb_servers.csv\u003c/code\u003e に出力してください。\u003c/p\u003e\n\u003chr data-sourcepos=\"659:1-660:0\"\u003e\n\u003ch3 data-sourcepos=\"661:1-661:10\"\u003e\n\u003cspan id=\"解答\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%A7%A3%E7%AD%94\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e解答\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003e問題1の解答を表示\u003c/summary\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"666:1-690:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eservers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning_servers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003er\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n        \u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ew\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enewline\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteheader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erunning のサーバをCSVファイルに出力しました。\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e出力ファイル:\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"692:1-692:27\"\u003e実行コマンドです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"694:1-697:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003epython\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003epractice1.py\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003cspan class=\"kr\"\u003etype\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erunning_servers.csv\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"699:1-699:21\"\u003e想定結果です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"701:1-706:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\nweb01,192.168.1.10,web,running\ndb01,192.168.1.20,db,running\nbackup01,192.168.1.30,backup,running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e問題2の解答を表示\u003c/summary\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"713:1-737:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eservers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003edb_servers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003er\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003edb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n        \u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ew\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enewline\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteheader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eDBサーバをCSVファイルに出力しました。\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e出力ファイル:\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"739:1-739:27\"\u003e実行コマンドです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"741:1-744:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003epython\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003epractice2.py\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003cspan class=\"kr\"\u003etype\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb_servers.csv\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"746:1-746:21\"\u003e想定結果です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"748:1-751:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\ndb01,192.168.1.20,db,running\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003e問題3の解答を表示\u003c/summary\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"python\" data-sourcepos=\"758:1-782:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eservers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb_servers.csv\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003er\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eservers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eweb\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n        \u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ew\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eencoding\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eutf-8\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enewline\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n    \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003ename\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eip\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003erole\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003estatus\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecsv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eDictWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003efieldnames\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteheader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n    \u003cspan class=\"n\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriterows\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etarget_servers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003eWebサーバをCSVファイルに出力しました。\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"s\"\u003e出力ファイル:\u003c/span\u003e\u003cspan class=\"sh\"\u003e\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoutput_file\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"784:1-784:27\"\u003e実行コマンドです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"powershell\" data-sourcepos=\"786:1-789:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"n\"\u003epython\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003epractice3.py\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003cspan class=\"kr\"\u003etype\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eweb_servers.csv\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"791:1-791:21\"\u003e想定結果です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"793:1-797:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003ename,ip,role,status\nweb01,192.168.1.10,web,running\nweb02,192.168.1.11,web,stopped\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003c/details\u003e\n\u003chr data-sourcepos=\"801:1-802:0\"\u003e\n\u003ch2 data-sourcepos=\"803:1-803:12\"\u003e\n\u003cspan id=\"まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eまとめ\u003c/h2\u003e\n\u003cp data-sourcepos=\"805:1-805:127\"\u003e7日目では、CSVから条件に合うサーバを抽出し、別のCSVファイルに書き出す方法を学びました。\u003c/p\u003e\n\u003cp data-sourcepos=\"807:1-807:45\"\u003e今回の重要ポイントは以下です。\u003c/p\u003e\n\u003cul data-sourcepos=\"809:1-816:0\"\u003e\n\u003cli data-sourcepos=\"809:1-809:58\"\u003e\n\u003ccode\u003ecsv.DictReader\u003c/code\u003e でCSVを辞書として読み込める\u003c/li\u003e\n\u003cli data-sourcepos=\"810:1-810:82\"\u003e\n\u003ccode\u003elist(csv.DictReader(file))\u003c/code\u003e にすると、CSVの内容を再利用しやすい\u003c/li\u003e\n\u003cli data-sourcepos=\"811:1-811:55\"\u003e\n\u003ccode\u003eif\u003c/code\u003e で条件に合うサーバだけ抽出できる\u003c/li\u003e\n\u003cli data-sourcepos=\"812:1-812:58\"\u003e\n\u003ccode\u003eappend()\u003c/code\u003e で抽出結果をリストに追加できる\u003c/li\u003e\n\u003cli data-sourcepos=\"813:1-813:61\"\u003e\n\u003ccode\u003ecsv.DictWriter\u003c/code\u003e で辞書データをCSVに書き出せる\u003c/li\u003e\n\u003cli data-sourcepos=\"814:1-814:64\"\u003e\n\u003ccode\u003ewriter.writeheader()\u003c/code\u003e でCSVのヘッダーを書き出せる\u003c/li\u003e\n\u003cli data-sourcepos=\"815:1-816:0\"\u003e\n\u003ccode\u003ewriter.writerows()\u003c/code\u003e で複数行をまとめて書き出せる\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"817:1-817:87\"\u003e今回の処理は、インフラ運用でよくある以下の作業に近いです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"819:1-824:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eサーバ一覧を読む\n作業対象だけ抽出する\n作業対象リストをCSVで作成する\n結果を確認する\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"826:1-826:73\"\u003e7日目あたりから、コードは少し難しく見えてきます。\u003c/p\u003e\n\u003cp data-sourcepos=\"828:1-828:75\"\u003eただし、やっていることは実務の作業手順と同じです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"830:1-835:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e読む\n絞る\n出す\n確認する\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"837:1-837:84\"\u003eこの流れを意識すると、コード全体を理解しやすくなります。\u003c/p\u003e\n\u003cp data-sourcepos=\"839:1-839:145\"\u003e次回は、今回のようなCSV抽出・出力処理をもう少し扱いやすくするために、\u003cstrong\u003e関数化\u003c/strong\u003eへ進むと実務的です。\u003c/p\u003e\n","body":"# インフラエンジニアが100日でPythonを覚える：7日目 CSVから抽出した結果を別CSVファイルに書き出す\n\n## はじめに\n\nインフラ現場では、サーバ一覧、監視対象一覧、作業対象一覧などをCSVで管理することがよくあります。\n\n6日目までは、PythonでCSVを読み込み、条件に合うサーバを画面に表示するところまで学びました。\n\n7日目では、さらに一歩進めて、**条件に合うサーバだけを抽出し、その結果を別のCSVファイルに書き出す**方法を学びます。\n\nたとえば、以下のような作業をPythonで自動化できるようになります。\n\n- サーバ一覧CSVから稼働中サーバだけを抽出する\n- Webサーバだけの作業対象リストを作成する\n- DBサーバだけを別CSVに出力する\n- 抽出結果をExcelや別ツールで確認できる形にする\n\n運用保守でも、設計・構築でも使える基本的な処理です。\n\n---\n\n## 本日のゴール\n\n本日のゴールは以下です。\n\n- CSVファイルを読み込む\n- 条件に合うサーバだけ抽出する\n- 抽出結果を新しいCSVファイルに書き出す\n- PowerShellで出力結果を確認する\n\n今回は、以下の条件で抽出します。\n\n```text\nrole が web\nかつ\nstatus が running\n```\n\nつまり、**稼働中のWebサーバだけを別CSVに出力する**処理を作ります。\n\n---\n\n## 前提知識\n\nこの記事では、以下を学習済みとして進めます。\n\n- `print()`\n- 変数\n- `list`\n- `for`\n- `if`\n- `dict`\n- CSV読み込み\n- `csv.DictReader`\n\n環境は以下を前提にします。\n\n- Windows\n- PowerShell\n- Pythonインストール済み\n- 作業フォルダでPythonファイルを実行できる状態\n\n---\n\n## 講義（コード＋解説）\n\n### 作業フォルダを作成する\n\nPowerShellを開き、作業フォルダを作成します。\n\n```powershell\nmkdir C:\\python-100days\\day7\ncd C:\\python-100days\\day7\n```\n\n### 入力用CSVファイルを作成する\n\nまず、元データとなる `servers.csv` を作成します。\n\n```powershell\nnotepad servers.csv\n```\n\n以下の内容を貼り付けて保存します。\n\n```csv\nname,ip,role,status\nweb01,192.168.1.10,web,running\ndb01,192.168.1.20,db,running\nweb02,192.168.1.11,web,stopped\nbackup01,192.168.1.30,backup,running\n```\n\n今回のCSVには、以下の列があります。\n\n| 列名 | 意味 |\n|---|---|\n| name | サーバ名 |\n| ip | IPアドレス |\n| role | サーバの役割 |\n| status | 稼働状態 |\n\n---\n\n### Pythonファイルを作成する\n\n次に、Pythonファイルを作成します。\n\n```powershell\nnotepad export_web_running.py\n```\n\n以下のコードを書きます。\n\n```python\nimport csv\n\n# 入力ファイルと出力ファイルの名前を変数に入れる\ninput_file = \"servers.csv\"\noutput_file = \"web_running_servers.csv\"\n\n# CSVファイルを読み込む\nwith open(input_file, \"r\", encoding=\"utf-8\") as file:\n    servers = list(csv.DictReader(file))\n\n# 条件に合うサーバを入れるための空リスト\ntarget_servers = []\n\n# role が web かつ status が running のサーバだけ抽出する\nfor server in servers:\n    if server[\"role\"] == \"web\" and server[\"status\"] == \"running\":\n        target_servers.append(server)\n\n# 抽出結果を新しいCSVファイルに書き出す\nwith open(output_file, \"w\", encoding=\"utf-8\", newline=\"\") as file:\n    fieldnames = [\"name\", \"ip\", \"role\", \"status\"]\n    writer = csv.DictWriter(file, fieldnames=fieldnames)\n\n    writer.writeheader()\n    writer.writerows(target_servers)\n\nprint(\"抽出結果をCSVファイルに出力しました。\")\nprint(\"出力ファイル:\", output_file)\n```\n\n---\n\n### コードの全体像\n\n今回の処理は、以下の流れです。\n\n```text\nCSVを読み込む\n↓\n条件に合うサーバだけ抽出する\n↓\n抽出結果をリストに入れる\n↓\n新しいCSVファイルに書き出す\n↓\n結果を確認する\n```\n\nいきなり全体を理解しようとすると難しく感じます。\nまずは、処理をブロックごとに分けて見るのが大切です。\n\n---\n\n### CSVを扱うための準備\n\n```python\nimport csv\n```\n\nPythonでCSVを扱うために、`csv` モジュールを読み込んでいます。\n\n`csv` はPython標準機能なので、追加インストールは不要です。\n\n---\n\n### 入力ファイル名と出力ファイル名を決める\n\n```python\ninput_file = \"servers.csv\"\noutput_file = \"web_running_servers.csv\"\n```\n\n`input_file` には読み込むCSVファイル名を入れています。\n\n`output_file` には新しく作成するCSVファイル名を入れています。\n\nファイル名を変数にしておくと、後から変更しやすくなります。\n\n---\n\n### CSVを読み込む\n\n```python\nwith open(input_file, \"r\", encoding=\"utf-8\") as file:\n    servers = list(csv.DictReader(file))\n```\n\nここでは `servers.csv` を読み込んでいます。\n\n`\"r\"` は読み込みモードです。\n\n```python\ncsv.DictReader(file)\n```\n\n`DictReader` を使うと、CSVの1行を辞書として扱えます。\n\nたとえば、CSVのこの行は、\n\n```csv\nweb01,192.168.1.10,web,running\n```\n\nPythonでは、だいたい以下のような形で扱えます。\n\n```python\n{\n    \"name\": \"web01\",\n    \"ip\": \"192.168.1.10\",\n    \"role\": \"web\",\n    \"status\": \"running\"\n}\n```\n\nそのため、以下のように列名で値を取り出せます。\n\n```python\nserver[\"name\"]\nserver[\"ip\"]\nserver[\"role\"]\nserver[\"status\"]\n```\n\nまた、今回は以下のように `list()` を使っています。\n\n```python\nservers = list(csv.DictReader(file))\n```\n\nこれにより、CSVの内容をリストとして保持できます。\n\n前回学んだ通り、`csv.DictReader(file)` は一度読み進めると再利用しにくいです。\nそのため、何度も確認したり処理を分けたりする場合は、リストに変換しておくと扱いやすいです。\n\n---\n\n### 抽出結果を入れる空リストを作る\n\n```python\ntarget_servers = []\n```\n\n条件に合ったサーバを入れるための空リストです。\n\n最初は空です。\n\nこのあと、条件に合うサーバを見つけたら、このリストに追加していきます。\n\n---\n\n### 条件に合うサーバだけ抽出する\n\n```python\nfor server in servers:\n    if server[\"role\"] == \"web\" and server[\"status\"] == \"running\":\n        target_servers.append(server)\n```\n\nここが今回の中心です。\n\n意味は以下です。\n\n```text\nservers の中から1件ずつ server として取り出す\nrole が web で、かつ status が running なら\nその server を target_servers に追加する\n```\n\n条件はここです。\n\n```python\nserver[\"role\"] == \"web\" and server[\"status\"] == \"running\"\n```\n\n`and` は「両方の条件を満たす」という意味です。\n\nつまり、以下の両方を満たすサーバだけが対象になります。\n\n- `role` が `web`\n- `status` が `running`\n\n今回のCSVでは、条件に合うのは `web01` だけです。\n\n---\n\n### append でリストに追加する\n\n```python\ntarget_servers.append(server)\n```\n\n`append()` は、リストに要素を追加する命令です。\n\n今回の場合は、条件に合ったサーバ情報を `target_servers` に追加しています。\n\n最終的に、`target_servers` には以下のようなデータが入ります。\n\n```python\n[\n    {\"name\": \"web01\", \"ip\": \"192.168.1.10\", \"role\": \"web\", \"status\": \"running\"}\n]\n```\n\n---\n\n### 新しいCSVファイルを書き出す\n\n```python\nwith open(output_file, \"w\", encoding=\"utf-8\", newline=\"\") as file:\n```\n\nここでは、出力用CSVファイルを開いています。\n\n`\"w\"` は書き込みモードです。\n\n注意点として、同じ名前のファイルがすでに存在する場合は上書きされます。\n\n`newline=\"\"` は、Windows環境でCSVを書き出すときに付けておくとよい指定です。\n環境によっては、これがないとCSVの行間に空行が入ることがあります。\n\n---\n\n### CSVの列名を指定する\n\n```python\nfieldnames = [\"name\", \"ip\", \"role\", \"status\"]\n```\n\n出力するCSVの列名を指定しています。\n\n今回のCSVは以下の列を持っています。\n\n```text\nname\nip\nrole\nstatus\n```\n\nそのため、Python側でも同じ列名を指定します。\n\n---\n\n### DictWriter を使う\n\n```python\nwriter = csv.DictWriter(file, fieldnames=fieldnames)\n```\n\n`csv.DictWriter` は、辞書データをCSVに書き出すための機能です。\n\n読み込みでは `csv.DictReader` を使いました。\n\n書き込みでは `csv.DictWriter` を使います。\n\n```text\nCSV読み込み：csv.DictReader\nCSV書き込み：csv.DictWriter\n```\n\nこのセットで覚えるとよいです。\n\n---\n\n### ヘッダーを書き出す\n\n```python\nwriter.writeheader()\n```\n\nCSVの1行目に列名を書き出しています。\n\nつまり、以下の部分です。\n\n```csv\nname,ip,role,status\n```\n\nこれを書かないと、出力CSVに列名が出ません。\n\n実務では、あとからExcelで確認したり、別システムへ渡したりすることが多いため、ヘッダーは基本的に出した方がよいです。\n\n---\n\n### 抽出結果を書き出す\n\n```python\nwriter.writerows(target_servers)\n```\n\n`target_servers` に入っているデータをCSVに書き出しています。\n\n1行だけ書く場合は `writerow()`、複数行を書く場合は `writerows()` を使います。\n\n```python\nwriter.writerow()\nwriter.writerows()\n```\n\n今回はリストに入った複数行のデータを書き出す想定なので、`writerows()` を使います。\n\n---\n\n## 実行コマンド（PowerShell）\n\nPythonファイルを実行します。\n\n```powershell\npython export_web_running.py\n```\n\n作成されたCSVファイルを確認します。\n\n```powershell\ntype web_running_servers.csv\n```\n\nメモ帳で確認したい場合は、以下でも確認できます。\n\n```powershell\nnotepad web_running_servers.csv\n```\n\n作業フォルダ内のファイル一覧を確認する場合は、以下を使います。\n\n```powershell\ndir\n```\n\n---\n\n## 実行結果\n\nPythonファイルを実行すると、以下のように表示されます。\n\n```text\n抽出結果をCSVファイルに出力しました。\n出力ファイル: web_running_servers.csv\n```\n\n`type web_running_servers.csv` を実行すると、以下のように表示されます。\n\n```text\nname,ip,role,status\nweb01,192.168.1.10,web,running\n```\n\n`servers.csv` の中から、以下の条件に合うサーバだけが出力されています。\n\n```text\nrole が web\nかつ\nstatus が running\n```\n\n今回のデータでは、条件に合うのは `web01` だけです。\n\n---\n\n## 解説（初心者向け＋実務目線）\n\n### 1. 今回の処理は「作業対象リスト作成」に近い\n\nインフラ現場では、作業前に対象サーバを絞り込むことがよくあります。\n\nたとえば、以下のような場面です。\n\n- 稼働中のサーバだけを対象にする\n- Webサーバだけを対象にする\n- DBサーバだけを対象にする\n- 停止中のサーバを除外する\n- メンテナンス対象だけを別ファイルにまとめる\n\n今回のPython処理は、これらの作業の基本形です。\n\n```text\n一覧を読む\n条件で絞る\n結果を別ファイルに出す\n```\n\nこの流れは、運用自動化でよく使います。\n\n---\n\n### 2. 「読み込み」と「書き込み」は別の処理\n\nCSVを読む処理は以下です。\n\n```python\nwith open(input_file, \"r\", encoding=\"utf-8\") as file:\n    servers = list(csv.DictReader(file))\n```\n\nCSVを書く処理は以下です。\n\n```python\nwith open(output_file, \"w\", encoding=\"utf-8\", newline=\"\") as file:\n    writer = csv.DictWriter(file, fieldnames=fieldnames)\n    writer.writeheader()\n    writer.writerows(target_servers)\n```\n\n最初は長く見えますが、役割を分けると理解しやすいです。\n\n```text\nr：read、読み込み\nw：write、書き込み\n```\n\n---\n\n### 3. 条件を変えるだけで別のCSVを作れる\n\n今回の条件は以下でした。\n\n```python\nif server[\"role\"] == \"web\" and server[\"status\"] == \"running\":\n```\n\nこの条件を変えるだけで、別の抽出ができます。\n\n稼働中サーバだけ抽出するなら、以下です。\n\n```python\nif server[\"status\"] == \"running\":\n```\n\nDBサーバだけ抽出するなら、以下です。\n\n```python\nif server[\"role\"] == \"db\":\n```\n\nWebサーバだけ抽出するなら、以下です。\n\n```python\nif server[\"role\"] == \"web\":\n```\n\nつまり、基本の形を覚えれば、条件を変えていろいろな作業対象リストを作れます。\n\n---\n\n### 4. ファイルは作成されるが中身が空になる場合\n\nファイルは作成されたのに、データ行が表示されないことがあります。\n\nたとえば、出力結果が以下のようになるケースです。\n\n```text\nname,ip,role,status\n```\n\nこの場合、CSVファイル自体は作成されていますが、条件に一致したデータがありません。\n\nよくある原因は以下です。\n\n- 条件が間違っている\n- CSVの値が想定と違う\n- 大文字小文字が違う\n- CSVの列名が違う\n- 見ている出力ファイル名が違う\n\nたとえば、Pythonでは以下は一致しません。\n\n```python\n\"running\" == \"Running\"\n```\n\n大文字小文字が違うためです。\n\nまた、コードで以下のように書いている場合、\n\n```python\nserver[\"status\"]\n```\n\nCSVのヘッダーも以下になっている必要があります。\n\n```csv\nname,ip,role,status\n```\n\nもしCSV側が以下のようになっていると、列名が違うため正しく処理できません。\n\n```csv\nname,ip,role,state\n```\n\n---\n\n### 5. 原因調査には print が便利\n\n動きがわからないときは、途中に `print()` を入れると確認しやすいです。\n\nたとえば、CSVが正しく読めているか確認するには以下です。\n\n```python\nprint(servers)\n```\n\n1件ずつ中身を確認するには以下です。\n\n```python\nfor server in servers:\n    print(server)\n```\n\n抽出件数を確認するには以下です。\n\n```python\nprint(\"抽出件数:\", len(target_servers))\n```\n\n実務でも、まずは小さく確認しながら進めることが大切です。\n\n---\n\n## 練習問題\n\n以下のCSVを前提にします。\n\n```csv\nname,ip,role,status\nweb01,192.168.1.10,web,running\ndb01,192.168.1.20,db,running\nweb02,192.168.1.11,web,stopped\nbackup01,192.168.1.30,backup,running\n```\n\n### 問題1\n\n`status` が `running` のサーバだけを `running_servers.csv` に出力してください。\n\n### 問題2\n\n`role` が `db` のサーバだけを `db_servers.csv` に出力してください。\n\n### 問題3\n\n`role` が `web` のサーバだけを `web_servers.csv` に出力してください。\n\n---\n\n### 解答\n\n\u003cdetails\u003e\n\u003csummary\u003e問題1の解答を表示\u003c/summary\u003e\n\n```python\nimport csv\n\ninput_file = \"servers.csv\"\noutput_file = \"running_servers.csv\"\n\nwith open(input_file, \"r\", encoding=\"utf-8\") as file:\n    servers = list(csv.DictReader(file))\n\ntarget_servers = []\n\nfor server in servers:\n    if server[\"status\"] == \"running\":\n        target_servers.append(server)\n\nwith open(output_file, \"w\", encoding=\"utf-8\", newline=\"\") as file:\n    fieldnames = [\"name\", \"ip\", \"role\", \"status\"]\n    writer = csv.DictWriter(file, fieldnames=fieldnames)\n\n    writer.writeheader()\n    writer.writerows(target_servers)\n\nprint(\"running のサーバをCSVファイルに出力しました。\")\nprint(\"出力ファイル:\", output_file)\n```\n\n実行コマンドです。\n\n```powershell\npython practice1.py\ntype running_servers.csv\n```\n\n想定結果です。\n\n```text\nname,ip,role,status\nweb01,192.168.1.10,web,running\ndb01,192.168.1.20,db,running\nbackup01,192.168.1.30,backup,running\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e問題2の解答を表示\u003c/summary\u003e\n\n```python\nimport csv\n\ninput_file = \"servers.csv\"\noutput_file = \"db_servers.csv\"\n\nwith open(input_file, \"r\", encoding=\"utf-8\") as file:\n    servers = list(csv.DictReader(file))\n\ntarget_servers = []\n\nfor server in servers:\n    if server[\"role\"] == \"db\":\n        target_servers.append(server)\n\nwith open(output_file, \"w\", encoding=\"utf-8\", newline=\"\") as file:\n    fieldnames = [\"name\", \"ip\", \"role\", \"status\"]\n    writer = csv.DictWriter(file, fieldnames=fieldnames)\n\n    writer.writeheader()\n    writer.writerows(target_servers)\n\nprint(\"DBサーバをCSVファイルに出力しました。\")\nprint(\"出力ファイル:\", output_file)\n```\n\n実行コマンドです。\n\n```powershell\npython practice2.py\ntype db_servers.csv\n```\n\n想定結果です。\n\n```text\nname,ip,role,status\ndb01,192.168.1.20,db,running\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e問題3の解答を表示\u003c/summary\u003e\n\n```python\nimport csv\n\ninput_file = \"servers.csv\"\noutput_file = \"web_servers.csv\"\n\nwith open(input_file, \"r\", encoding=\"utf-8\") as file:\n    servers = list(csv.DictReader(file))\n\ntarget_servers = []\n\nfor server in servers:\n    if server[\"role\"] == \"web\":\n        target_servers.append(server)\n\nwith open(output_file, \"w\", encoding=\"utf-8\", newline=\"\") as file:\n    fieldnames = [\"name\", \"ip\", \"role\", \"status\"]\n    writer = csv.DictWriter(file, fieldnames=fieldnames)\n\n    writer.writeheader()\n    writer.writerows(target_servers)\n\nprint(\"WebサーバをCSVファイルに出力しました。\")\nprint(\"出力ファイル:\", output_file)\n```\n\n実行コマンドです。\n\n```powershell\npython practice3.py\ntype web_servers.csv\n```\n\n想定結果です。\n\n```text\nname,ip,role,status\nweb01,192.168.1.10,web,running\nweb02,192.168.1.11,web,stopped\n```\n\n\u003c/details\u003e\n\n---\n\n## まとめ\n\n7日目では、CSVから条件に合うサーバを抽出し、別のCSVファイルに書き出す方法を学びました。\n\n今回の重要ポイントは以下です。\n\n- `csv.DictReader` でCSVを辞書として読み込める\n- `list(csv.DictReader(file))` にすると、CSVの内容を再利用しやすい\n- `if` で条件に合うサーバだけ抽出できる\n- `append()` で抽出結果をリストに追加できる\n- `csv.DictWriter` で辞書データをCSVに書き出せる\n- `writer.writeheader()` でCSVのヘッダーを書き出せる\n- `writer.writerows()` で複数行をまとめて書き出せる\n\n今回の処理は、インフラ運用でよくある以下の作業に近いです。\n\n```text\nサーバ一覧を読む\n作業対象だけ抽出する\n作業対象リストをCSVで作成する\n結果を確認する\n```\n\n7日目あたりから、コードは少し難しく見えてきます。\n\nただし、やっていることは実務の作業手順と同じです。\n\n```text\n読む\n絞る\n出す\n確認する\n```\n\nこの流れを意識すると、コード全体を理解しやすくなります。\n\n次回は、今回のようなCSV抽出・出力処理をもう少し扱いやすくするために、**関数化**へ進むと実務的です。\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T11:01:06+09:00","group":null,"id":"4059d6f71dc466995f89","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"Python","versions":[]},{"name":"インフラエンジニア","versions":[]}],"title":"インフラエンジニアが100日でPythonを覚える：7日目 CSVから抽出した結果を別CSVファイルに書き出す","updated_at":"2026-05-06T11:01:06+09:00","url":"https://qiita.com/kona1024/items/4059d6f71dc466995f89","user":{"description":"","facebook_id":"","followees_count":0,"followers_count":1,"github_login_name":null,"id":"kona1024","items_count":7,"linkedin_id":"","location":"","name":"こなさん","organization":"","permanent_id":362350,"profile_image_url":"https://lh6.googleusercontent.com/-rV8KnjPtYWU/AAAAAAAAAAI/AAAAAAAAAAA/ACevoQNT9iW2999m-2OIaXtziWw-EkAN9w/mo/photo.jpg?sz=50","team_only":false,"twitter_screen_name":null,"website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cdiv data-sourcepos=\"1:1-3:3\" class=\"note info\"\u003e\n\u003cspan class=\"fa fa-fw fa-check-circle\"\u003e\u003c/span\u003e\u003cdiv\u003e\n\u003cp data-sourcepos=\"2:1-2:125\"\u003eこの記事は \u003ca href=\"https://itauditcompass.com/posts/2026-04-26-1121/\" rel=\"nofollow noopener\" target=\"_blank\"\u003eIT Audit Compass\u003c/a\u003e に掲載した記事の転載です。\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003ch2 data-sourcepos=\"5:1-5:62\"\u003e\n\u003cspan id=\"はじめにit監査資格の重要性が高まる背景\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%ABit%E7%9B%A3%E6%9F%BB%E8%B3%87%E6%A0%BC%E3%81%AE%E9%87%8D%E8%A6%81%E6%80%A7%E3%81%8C%E9%AB%98%E3%81%BE%E3%82%8B%E8%83%8C%E6%99%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに：IT監査資格の重要性が高まる背景\u003c/h2\u003e\n\u003cp data-sourcepos=\"7:1-7:321\"\u003eデジタルトランスフォーメーション（DX）の加速に伴い、企業のIT環境は急速に複雑化しています。クラウドサービスの普及、リモートワークの定着、そしてサイバー攻撃の高度化により、IT監査の重要性はかつてないほど高まっています。\u003c/p\u003e\n\u003cp data-sourcepos=\"9:1-9:437\"\u003e金融庁の調査によると、2024年度における上場企業のITガバナンス関連の指摘事項は前年比で約15%増加しており、IT統制の整備・運用に対する要求水準は年々厳格化しています。また、改正個人情報保護法やGDPR（EU一般データ保護規則）への対応など、コンプライアンス面でも専門知識を持つ人材へのニーズが急増しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"11:1-11:521\"\u003eこのような状況下で、IT監査やセキュリティの専門性を客観的に証明できる資格の取得は、キャリアアップにおいて大きなアドバンテージとなります。本記事では、IT監査分野で特に評価の高い3つの資格「CISA（公認情報システム監査人）」「CISSP（情報システムセキュリティプロフェッショナル認定）」「CIA（公認内部監査人）」について、実務担当者の視点から徹底比較していきます。\u003c/p\u003e\n\u003cp data-sourcepos=\"13:1-13:180\"\u003eそれぞれの資格の特徴、難易度、取得にかかる費用、そしてキャリアへの影響まで、資格選択に必要な情報を網羅的にお伝えします。\u003c/p\u003e\n\u003chr data-sourcepos=\"15:1-16:0\"\u003e\n\u003ch2 data-sourcepos=\"17:1-17:45\"\u003e\n\u003cspan id=\"1-各資格の基本情報と位置づけ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E5%90%84%E8%B3%87%E6%A0%BC%E3%81%AE%E5%9F%BA%E6%9C%AC%E6%83%85%E5%A0%B1%E3%81%A8%E4%BD%8D%E7%BD%AE%E3%81%A5%E3%81%91\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. 各資格の基本情報と位置づけ\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"19:1-19:57\"\u003e\n\u003cspan id=\"cisacertified-information-systems-auditorとは\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cisacertified-information-systems-auditor%E3%81%A8%E3%81%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCISA（Certified Information Systems Auditor）とは\u003c/h3\u003e\n\u003cp data-sourcepos=\"21:1-21:307\"\u003eCISAは、ISACA（Information Systems Audit and Control Association）が認定する情報システム監査の国際資格です。1978年から認定が開始され、2024年時点で世界180カ国以上で約17万人が保有する、IT監査分野では最も認知度の高い資格といえます。\u003c/p\u003e\n\u003cp data-sourcepos=\"23:1-23:25\"\u003e\u003cstrong\u003e主な対象領域：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"24:1-29:0\"\u003e\n\u003cli data-sourcepos=\"24:1-24:41\"\u003e情報システムの監査プロセス\u003c/li\u003e\n\u003cli data-sourcepos=\"25:1-25:28\"\u003eITガバナンスと管理\u003c/li\u003e\n\u003cli data-sourcepos=\"26:1-26:47\"\u003e情報システムの取得・開発・導入\u003c/li\u003e\n\u003cli data-sourcepos=\"27:1-27:62\"\u003e情報システムの運用とビジネスレジリエンス\u003c/li\u003e\n\u003cli data-sourcepos=\"28:1-29:0\"\u003e情報資産の保護\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"30:1-30:225\"\u003eCISAは「監査」という観点からITシステムを評価・検証する専門性を証明する資格です。内部監査部門、監査法人、コンサルティングファームで特に重宝されています。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"32:1-32:72\"\u003e\n\u003cspan id=\"cisspcertified-information-systems-security-professionalとは\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cisspcertified-information-systems-security-professional%E3%81%A8%E3%81%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCISSP（Certified Information Systems Security Professional）とは\u003c/h3\u003e\n\u003cp data-sourcepos=\"34:1-34:330\"\u003eCISSPは、(ISC)²（International Information System Security Certification Consortium）が認定する情報セキュリティの国際資格です。1994年に創設され、現在では世界で約16万人以上が保有する、セキュリティ分野における最高峰の資格として位置づけられています。\u003c/p\u003e\n\u003cp data-sourcepos=\"36:1-36:50\"\u003e\u003cstrong\u003e主な対象領域（8つのドメイン）：\u003c/strong\u003e\u003c/p\u003e\n\u003col data-sourcepos=\"37:1-45:0\"\u003e\n\u003cli data-sourcepos=\"37:1-37:51\"\u003eセキュリティとリスクマネジメント\u003c/li\u003e\n\u003cli data-sourcepos=\"38:1-38:30\"\u003e資産のセキュリティ\u003c/li\u003e\n\u003cli data-sourcepos=\"39:1-39:69\"\u003eセキュリティアーキテクチャとエンジニアリング\u003c/li\u003e\n\u003cli data-sourcepos=\"40:1-40:48\"\u003e通信とネットワークセキュリティ\u003c/li\u003e\n\u003cli data-sourcepos=\"41:1-41:48\"\u003eアイデンティティとアクセス管理\u003c/li\u003e\n\u003cli data-sourcepos=\"42:1-42:42\"\u003eセキュリティの評価とテスト\u003c/li\u003e\n\u003cli data-sourcepos=\"43:1-43:42\"\u003eセキュリティオペレーション\u003c/li\u003e\n\u003cli data-sourcepos=\"44:1-45:0\"\u003eソフトウェア開発セキュリティ\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"46:1-46:231\"\u003eCISSPはセキュリティを「設計・構築・運用」する立場からの専門性を証明します。CISO（最高情報セキュリティ責任者）やセキュリティマネージャーを目指す方に最適です。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"48:1-48:45\"\u003e\n\u003cspan id=\"ciacertified-internal-auditorとは\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#ciacertified-internal-auditor%E3%81%A8%E3%81%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCIA（Certified Internal Auditor）とは\u003c/h3\u003e\n\u003cp data-sourcepos=\"50:1-50:334\"\u003eCIAは、IIA（The Institute of Internal Auditors：内部監査人協会）が認定する内部監査の国際資格です。1974年から認定が開始され、世界190カ国以上で約20万人が保有しています。内部監査分野では唯一のグローバルスタンダードとして広く認知されています。\u003c/p\u003e\n\u003cp data-sourcepos=\"52:1-52:47\"\u003e\u003cstrong\u003e主な対象領域（3パート構成）：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"53:1-56:0\"\u003e\n\u003cli data-sourcepos=\"53:1-53:36\"\u003eパート1：内部監査の基礎\u003c/li\u003e\n\u003cli data-sourcepos=\"54:1-54:36\"\u003eパート2：内部監査の実務\u003c/li\u003e\n\u003cli data-sourcepos=\"55:1-56:0\"\u003eパート3：内部監査に関連するビジネス知識\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"57:1-57:239\"\u003eCIAはIT分野に限定されず、財務、業務、コンプライアンスなど幅広い内部監査の専門性を証明します。経営層に近いポジションでガバナンス全体を俯瞰したい方に向いています。\u003c/p\u003e\n\u003chr data-sourcepos=\"59:1-60:0\"\u003e\n\u003ch2 data-sourcepos=\"61:1-61:39\"\u003e\n\u003cspan id=\"2-受験要件と難易度の比較\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-%E5%8F%97%E9%A8%93%E8%A6%81%E4%BB%B6%E3%81%A8%E9%9B%A3%E6%98%93%E5%BA%A6%E3%81%AE%E6%AF%94%E8%BC%83\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. 受験要件と難易度の比較\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"63:1-63:25\"\u003e\n\u003cspan id=\"受験資格の違い\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%8F%97%E9%A8%93%E8%B3%87%E6%A0%BC%E3%81%AE%E9%81%95%E3%81%84\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e受験資格の違い\u003c/h3\u003e\n\u003cp data-sourcepos=\"65:1-65:54\"\u003e各資格の受験要件は以下のとおりです。\u003c/p\u003e\n\u003cp data-sourcepos=\"67:1-67:11\"\u003e\u003cstrong\u003eCISA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"68:1-72:0\"\u003e\n\u003cli data-sourcepos=\"68:1-68:38\"\u003e試験自体は誰でも受験可能\u003c/li\u003e\n\u003cli data-sourcepos=\"69:1-69:114\"\u003e認定には情報システム監査・セキュリティ・統制分野で最低5年間の実務経験が必要\u003c/li\u003e\n\u003cli data-sourcepos=\"70:1-70:69\"\u003e学歴や他資格により最大3年間まで経験を代替可能\u003c/li\u003e\n\u003cli data-sourcepos=\"71:1-72:0\"\u003e例：4年制大学卒業で2年間、修士号で1年間の代替が可能\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"73:1-73:12\"\u003e\u003cstrong\u003eCISSP：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"74:1-77:0\"\u003e\n\u003cli data-sourcepos=\"74:1-74:77\"\u003e8つのドメインのうち2つ以上で計5年間の実務経験が必要\u003c/li\u003e\n\u003cli data-sourcepos=\"75:1-75:52\"\u003e4年制大学の学位で1年間の代替が可能\u003c/li\u003e\n\u003cli data-sourcepos=\"76:1-77:0\"\u003e経験不足の場合は「Associate of (ISC)²」として仮認定を受け、6年以内に経験を積むことも可能\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"78:1-78:10\"\u003e\u003cstrong\u003eCIA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"79:1-82:0\"\u003e\n\u003cli data-sourcepos=\"79:1-79:38\"\u003e試験自体は誰でも受験可能\u003c/li\u003e\n\u003cli data-sourcepos=\"80:1-80:81\"\u003e認定には内部監査または関連分野で2年間の実務経験が必要\u003c/li\u003e\n\u003cli data-sourcepos=\"81:1-82:0\"\u003e修士号保有者は1年間に短縮\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"83:1-83:28\"\u003e\n\u003cspan id=\"試験形式と合格率\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%A9%A6%E9%A8%93%E5%BD%A2%E5%BC%8F%E3%81%A8%E5%90%88%E6%A0%BC%E7%8E%87\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e試験形式と合格率\u003c/h3\u003e\n\u003cp data-sourcepos=\"85:1-85:11\"\u003e\u003cstrong\u003eCISA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"86:1-91:0\"\u003e\n\u003cli data-sourcepos=\"86:1-86:30\"\u003e出題数：150問（4択）\u003c/li\u003e\n\u003cli data-sourcepos=\"87:1-87:24\"\u003e試験時間：4時間\u003c/li\u003e\n\u003cli data-sourcepos=\"88:1-88:72\"\u003e合格ライン：450点/800点満点（スケールスコア方式）\u003c/li\u003e\n\u003cli data-sourcepos=\"89:1-89:62\"\u003e合格率：約50%前後（公式発表なし、推定値）\u003c/li\u003e\n\u003cli data-sourcepos=\"90:1-91:0\"\u003e試験言語：日本語選択可能\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"92:1-92:12\"\u003e\u003cstrong\u003eCISSP：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"93:1-98:0\"\u003e\n\u003cli data-sourcepos=\"93:1-93:56\"\u003e出題数：100〜150問（CAT方式により可変）\u003c/li\u003e\n\u003cli data-sourcepos=\"94:1-94:24\"\u003e試験時間：3時間\u003c/li\u003e\n\u003cli data-sourcepos=\"95:1-95:40\"\u003e合格ライン：700点/1000点満点\u003c/li\u003e\n\u003cli data-sourcepos=\"96:1-96:40\"\u003e合格率：約25〜30%（推定値）\u003c/li\u003e\n\u003cli data-sourcepos=\"97:1-98:0\"\u003e試験言語：日本語選択可能\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"99:1-99:10\"\u003e\u003cstrong\u003eCIA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"100:1-105:0\"\u003e\n\u003cli data-sourcepos=\"100:1-100:86\"\u003e出題数：パート1（125問）、パート2（100問）、パート3（100問）\u003c/li\u003e\n\u003cli data-sourcepos=\"101:1-101:42\"\u003e試験時間：各パート2〜2.5時間\u003c/li\u003e\n\u003cli data-sourcepos=\"102:1-102:57\"\u003e合格ライン：600点/750点満点（各パート）\u003c/li\u003e\n\u003cli data-sourcepos=\"103:1-103:55\"\u003e合格率：パートにより40〜50%（推定値）\u003c/li\u003e\n\u003cli data-sourcepos=\"104:1-105:0\"\u003e試験言語：日本語選択可能\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"106:1-106:25\"\u003e\n\u003cspan id=\"難易度の実感値\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%9B%A3%E6%98%93%E5%BA%A6%E3%81%AE%E5%AE%9F%E6%84%9F%E5%80%A4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e難易度の実感値\u003c/h3\u003e\n\u003cp data-sourcepos=\"108:1-108:90\"\u003e実務経験者へのヒアリングに基づく難易度感は以下のとおりです。\u003c/p\u003e\n\u003ctable data-sourcepos=\"110:1-114:52\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"110:1-110:76\"\u003e\n\u003cth data-sourcepos=\"110:2-110:9\"\u003e資格\u003c/th\u003e\n\u003cth data-sourcepos=\"110:11-110:30\"\u003e学習時間目安\u003c/th\u003e\n\u003cth data-sourcepos=\"110:32-110:42\"\u003e難易度\u003c/th\u003e\n\u003cth data-sourcepos=\"110:44-110:75\"\u003e日本語受験のしやすさ\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"112:1-112:53\"\u003e\n\u003ctd data-sourcepos=\"112:2-112:7\"\u003eCISA\u003c/td\u003e\n\u003ctd data-sourcepos=\"112:9-112:25\"\u003e200〜400時間\u003c/td\u003e\n\u003ctd data-sourcepos=\"112:27-112:43\"\u003e★★★☆☆\u003c/td\u003e\n\u003ctd data-sourcepos=\"112:45-112:52\"\u003e良好\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"113:1-113:54\"\u003e\n\u003ctd data-sourcepos=\"113:2-113:8\"\u003eCISSP\u003c/td\u003e\n\u003ctd data-sourcepos=\"113:10-113:26\"\u003e400〜600時間\u003c/td\u003e\n\u003ctd data-sourcepos=\"113:28-113:44\"\u003e★★★★★\u003c/td\u003e\n\u003ctd data-sourcepos=\"113:46-113:53\"\u003e良好\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"114:1-114:52\"\u003e\n\u003ctd data-sourcepos=\"114:2-114:6\"\u003eCIA\u003c/td\u003e\n\u003ctd data-sourcepos=\"114:8-114:24\"\u003e300〜500時間\u003c/td\u003e\n\u003ctd data-sourcepos=\"114:26-114:42\"\u003e★★★★☆\u003c/td\u003e\n\u003ctd data-sourcepos=\"114:44-114:51\"\u003e良好\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"116:1-116:389\"\u003eCISSPは8つのドメインにわたる広範な知識が求められ、特に暗号技術やネットワークセキュリティの深い理解が必要なため、最も難易度が高いとされています。CISAは監査実務経験があれば比較的取り組みやすく、CIAは3パートに分かれているため計画的に攻略しやすいという特徴があります。\u003c/p\u003e\n\u003chr data-sourcepos=\"118:1-119:0\"\u003e\n\u003ch2 data-sourcepos=\"120:1-120:45\"\u003e\n\u003cspan id=\"3-取得費用と維持コストの詳細\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-%E5%8F%96%E5%BE%97%E8%B2%BB%E7%94%A8%E3%81%A8%E7%B6%AD%E6%8C%81%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AE%E8%A9%B3%E7%B4%B0\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. 取得費用と維持コストの詳細\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"122:1-122:34\"\u003e\n\u003cspan id=\"初期取得にかかる費用\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%88%9D%E6%9C%9F%E5%8F%96%E5%BE%97%E3%81%AB%E3%81%8B%E3%81%8B%E3%82%8B%E8%B2%BB%E7%94%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e初期取得にかかる費用\u003c/h3\u003e\n\u003cp data-sourcepos=\"124:1-124:11\"\u003e\u003cstrong\u003eCISA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"125:1-130:0\"\u003e\n\u003cli data-sourcepos=\"125:1-125:66\"\u003e受験料：575ドル（ISACA会員）/ 760ドル（非会員）\u003c/li\u003e\n\u003cli data-sourcepos=\"126:1-126:76\"\u003eISACA年会費：135ドル（日本支部会費は別途10,000円程度）\u003c/li\u003e\n\u003cli data-sourcepos=\"127:1-127:61\"\u003e教材費：3〜5万円（公式問題集、参考書等）\u003c/li\u003e\n\u003cli data-sourcepos=\"128:1-128:72\"\u003e講座受講費：15〜30万円（任意、各種資格スクール）\u003c/li\u003e\n\u003cli data-sourcepos=\"129:1-130:0\"\u003e\u003cstrong\u003e合計目安：20〜50万円\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"131:1-131:12\"\u003e\u003cstrong\u003eCISSP：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"132:1-136:0\"\u003e\n\u003cli data-sourcepos=\"132:1-132:40\"\u003e受験料：749ドル（約11万円）\u003c/li\u003e\n\u003cli data-sourcepos=\"133:1-133:70\"\u003e教材費：5〜8万円（公式ガイドブック、問題集等）\u003c/li\u003e\n\u003cli data-sourcepos=\"134:1-134:97\"\u003e講座受講費：20〜50万円（任意、5日間のブートキャンプ形式が一般的）\u003c/li\u003e\n\u003cli data-sourcepos=\"135:1-136:0\"\u003e\u003cstrong\u003e合計目安：30〜70万円\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"137:1-137:10\"\u003e\u003cstrong\u003eCIA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"138:1-144:0\"\u003e\n\u003cli data-sourcepos=\"138:1-138:76\"\u003e受験料：各パート395ドル（IIA会員）/ 525ドル（非会員）\u003c/li\u003e\n\u003cli data-sourcepos=\"139:1-139:23\"\u003e登録料：230ドル\u003c/li\u003e\n\u003cli data-sourcepos=\"140:1-140:59\"\u003eIIA年会費：245ドル（日本支部会費は別途）\u003c/li\u003e\n\u003cli data-sourcepos=\"141:1-141:26\"\u003e教材費：5〜10万円\u003c/li\u003e\n\u003cli data-sourcepos=\"142:1-142:45\"\u003e講座受講費：15〜40万円（任意）\u003c/li\u003e\n\u003cli data-sourcepos=\"143:1-144:0\"\u003e\u003cstrong\u003e合計目安：30〜60万円\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"145:1-145:43\"\u003e\n\u003cspan id=\"資格維持にかかる年間コスト\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%B3%87%E6%A0%BC%E7%B6%AD%E6%8C%81%E3%81%AB%E3%81%8B%E3%81%8B%E3%82%8B%E5%B9%B4%E9%96%93%E3%82%B3%E3%82%B9%E3%83%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e資格維持にかかる年間コスト\u003c/h3\u003e\n\u003cp data-sourcepos=\"147:1-147:108\"\u003eすべての資格には継続教育（CPE：Continuing Professional Education）の要件があります。\u003c/p\u003e\n\u003cp data-sourcepos=\"149:1-149:11\"\u003e\u003cstrong\u003eCISA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"150:1-154:0\"\u003e\n\u003cli data-sourcepos=\"150:1-150:70\"\u003e年間維持料：45ドル（ISACA会員）/ 85ドル（非会員）\u003c/li\u003e\n\u003cli data-sourcepos=\"151:1-151:62\"\u003eCPE要件：年間20時間以上、3年間で120時間以上\u003c/li\u003e\n\u003cli data-sourcepos=\"152:1-152:83\"\u003eCPE取得コスト：無料〜数万円（セミナー参加、書籍購読等）\u003c/li\u003e\n\u003cli data-sourcepos=\"153:1-154:0\"\u003e\u003cstrong\u003e年間合計目安：1〜5万円\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"155:1-155:12\"\u003e\u003cstrong\u003eCISSP：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"156:1-159:0\"\u003e\n\u003cli data-sourcepos=\"156:1-156:29\"\u003e年間維持料：125ドル\u003c/li\u003e\n\u003cli data-sourcepos=\"157:1-157:62\"\u003eCPE要件：年間40時間以上、3年間で120時間以上\u003c/li\u003e\n\u003cli data-sourcepos=\"158:1-159:0\"\u003e\u003cstrong\u003e年間合計目安：2〜7万円\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"160:1-160:10\"\u003e\u003cstrong\u003eCIA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"161:1-164:0\"\u003e\n\u003cli data-sourcepos=\"161:1-161:44\"\u003e年間維持料：会員は会費に含む\u003c/li\u003e\n\u003cli data-sourcepos=\"162:1-162:61\"\u003eCPE要件：年間20時間以上、2年間で40時間以上\u003c/li\u003e\n\u003cli data-sourcepos=\"163:1-164:0\"\u003e\u003cstrong\u003e年間合計目安：1〜5万円\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"165:1-165:40\"\u003e\n\u003cspan id=\"企業による費用負担の実態\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E4%BC%81%E6%A5%AD%E3%81%AB%E3%82%88%E3%82%8B%E8%B2%BB%E7%94%A8%E8%B2%A0%E6%8B%85%E3%81%AE%E5%AE%9F%E6%85%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e企業による費用負担の実態\u003c/h3\u003e\n\u003cp data-sourcepos=\"167:1-167:113\"\u003e多くの企業では、IT監査やセキュリティ関連の資格取得を支援する制度があります。\u003c/p\u003e\n\u003cp data-sourcepos=\"169:1-169:27\"\u003e一般的な支援内容：\u003c/p\u003e\n\u003cul data-sourcepos=\"170:1-174:0\"\u003e\n\u003cli data-sourcepos=\"170:1-170:80\"\u003e受験料の全額または一部補助（合格時のみの企業も多い）\u003c/li\u003e\n\u003cli data-sourcepos=\"171:1-171:63\"\u003e講座受講費の補助（上限10〜30万円が一般的）\u003c/li\u003e\n\u003cli data-sourcepos=\"172:1-172:41\"\u003e合格一時金（3〜10万円程度）\u003c/li\u003e\n\u003cli data-sourcepos=\"173:1-174:0\"\u003e資格手当（月額5,000〜30,000円）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"175:1-175:218\"\u003e大手監査法人やコンサルティングファームでは、CISA・CISSP取得者に月額1〜3万円程度の資格手当が支給されるケースが多く、維持コストを十分にカバーできます。\u003c/p\u003e\n\u003chr data-sourcepos=\"177:1-178:0\"\u003e\n\u003ch2 data-sourcepos=\"179:1-179:45\"\u003e\n\u003cspan id=\"4-キャリアパスと年収への影響\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E3%82%AD%E3%83%A3%E3%83%AA%E3%82%A2%E3%83%91%E3%82%B9%E3%81%A8%E5%B9%B4%E5%8F%8E%E3%81%B8%E3%81%AE%E5%BD%B1%E9%9F%BF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. キャリアパスと年収への影響\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"181:1-181:37\"\u003e\n\u003cspan id=\"各資格のキャリアマップ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%90%84%E8%B3%87%E6%A0%BC%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%AA%E3%82%A2%E3%83%9E%E3%83%83%E3%83%97\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e各資格のキャリアマップ\u003c/h3\u003e\n\u003cp data-sourcepos=\"183:1-183:53\"\u003e\u003cstrong\u003eCISA保有者の典型的なキャリアパス：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"184:1-192:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eIT監査スタッフ（1〜3年目）\n    ↓\nIT監査シニア/主任（4〜7年目）\n    ↓\nIT監査マネージャー（8〜12年目）\n    ↓\nIT監査部長/CAE（13年目〜）\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"194:1-194:24\"\u003e活躍フィールド：\u003c/p\u003e\n\u003cul data-sourcepos=\"195:1-199:0\"\u003e\n\u003cli data-sourcepos=\"195:1-195:31\"\u003e監査法人のIT監査部門\u003c/li\u003e\n\u003cli data-sourcepos=\"196:1-196:49\"\u003e企業の内部監査部門（IT監査担当）\u003c/li\u003e\n\u003cli data-sourcepos=\"197:1-197:35\"\u003eシステムリスク管理部門\u003c/li\u003e\n\u003cli data-sourcepos=\"198:1-199:0\"\u003eITコンサルティングファーム\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"200:1-200:54\"\u003e\u003cstrong\u003eCISSP保有者の典型的なキャリアパス：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"201:1-209:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eセキュリティエンジニア（1〜3年目）\n    ↓\nセキュリティアーキテクト（4〜7年目）\n    ↓\nセキュリティマネージャー（8〜12年目）\n    ↓\nCISO/情報セキュリティ責任者（13年目〜）\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"211:1-211:24\"\u003e活躍フィールド：\u003c/p\u003e\n\u003cul data-sourcepos=\"212:1-216:0\"\u003e\n\u003cli data-sourcepos=\"212:1-212:35\"\u003e企業のセキュリティ部門\u003c/li\u003e\n\u003cli data-sourcepos=\"213:1-213:32\"\u003eセキュリティベンダー\u003c/li\u003e\n\u003cli data-sourcepos=\"214:1-214:11\"\u003eSOC/CSIRT\u003c/li\u003e\n\u003cli data-sourcepos=\"215:1-216:0\"\u003eセキュリティコンサルティング\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"217:1-217:52\"\u003e\u003cstrong\u003eCIA保有者の典型的なキャリアパス：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"218:1-226:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e内部監査スタッフ（1〜3年目）\n    ↓\n内部監査シニア（4〜7年目）\n    ↓\n内部監査マネージャー（8〜12年目）\n    ↓\nCAE/監査役（13年目〜）\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"228:1-228:24\"\u003e活躍フィールド：\u003c/p\u003e\n\u003cul data-sourcepos=\"229:1-233:0\"\u003e\n\u003cli data-sourcepos=\"229:1-229:29\"\u003e企業の内部監査部門\u003c/li\u003e\n\u003cli data-sourcepos=\"230:1-230:44\"\u003e監査法人のアドバイザリー部門\u003c/li\u003e\n\u003cli data-sourcepos=\"231:1-231:35\"\u003eリスクマネジメント部門\u003c/li\u003e\n\u003cli data-sourcepos=\"232:1-233:0\"\u003e取締役会事務局\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"234:1-234:25\"\u003e\n\u003cspan id=\"年収比較データ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%B9%B4%E5%8F%8E%E6%AF%94%E8%BC%83%E3%83%87%E3%83%BC%E3%82%BF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e年収比較データ\u003c/h3\u003e\n\u003cp data-sourcepos=\"236:1-236:112\"\u003e日本国内における各資格保有者の年収レンジ（2024年の求人データ等からの推計）：\u003c/p\u003e\n\u003ctable data-sourcepos=\"238:1-243:79\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"238:1-238:37\"\u003e\n\u003cth data-sourcepos=\"238:2-238:15\"\u003e経験年数\u003c/th\u003e\n\u003cth data-sourcepos=\"238:17-238:22\"\u003eCISA\u003c/th\u003e\n\u003cth data-sourcepos=\"238:24-238:30\"\u003eCISSP\u003c/th\u003e\n\u003cth data-sourcepos=\"238:32-238:36\"\u003eCIA\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"240:1-240:66\"\u003e\n\u003ctd data-sourcepos=\"240:2-240:11\"\u003e3〜5年\u003c/td\u003e\n\u003ctd data-sourcepos=\"240:13-240:29\"\u003e500〜700万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"240:31-240:47\"\u003e550〜800万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"240:49-240:65\"\u003e500〜700万円\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"241:1-241:73\"\u003e\n\u003ctd data-sourcepos=\"241:2-241:12\"\u003e5〜10年\u003c/td\u003e\n\u003ctd data-sourcepos=\"241:14-241:32\"\u003e700〜1,000万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"241:34-241:52\"\u003e800〜1,200万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"241:54-241:72\"\u003e700〜1,000万円\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"242:1-242:77\"\u003e\n\u003ctd data-sourcepos=\"242:2-242:14\"\u003e10年以上\u003c/td\u003e\n\u003ctd data-sourcepos=\"242:16-242:34\"\u003e900〜1,500万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"242:36-242:56\"\u003e1,000〜1,800万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"242:58-242:76\"\u003e900〜1,400万円\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"243:1-243:79\"\u003e\n\u003ctd data-sourcepos=\"243:2-243:12\"\u003e管理職\u003c/td\u003e\n\u003ctd data-sourcepos=\"243:14-243:34\"\u003e1,200〜2,000万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"243:36-243:56\"\u003e1,500〜2,500万円\u003c/td\u003e\n\u003ctd data-sourcepos=\"243:58-243:78\"\u003e1,200〜2,000万円\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"245:1-245:283\"\u003eCISSPは技術的な専門性が高く、需給バランスからも高年収になりやすい傾向があります。特に外資系企業やセキュリティベンダーでは、CISSP保有を必須条件とするポジションも多く、年収プレミアムが顕著です。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"247:1-247:28\"\u003e\n\u003cspan id=\"転職市場での評価\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%BB%A2%E8%81%B7%E5%B8%82%E5%A0%B4%E3%81%A7%E3%81%AE%E8%A9%95%E4%BE%A1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e転職市場での評価\u003c/h3\u003e\n\u003cp data-sourcepos=\"249:1-249:155\"\u003e大手転職エージェントの調査によると、IT監査・セキュリティ関連職種の求人において、以下の傾向が見られます。\u003c/p\u003e\n\u003cul data-sourcepos=\"251:1-254:0\"\u003e\n\u003cli data-sourcepos=\"251:1-251:102\"\u003eCISA：監査法人・コンサル系求人の約60%で「歓迎要件」または「必須要件」\u003c/li\u003e\n\u003cli data-sourcepos=\"252:1-252:103\"\u003eCISSP：セキュリティ専門職求人の約70%で「歓迎要件」、約30%で「必須要件」\u003c/li\u003e\n\u003cli data-sourcepos=\"253:1-254:0\"\u003eCIA：内部監査部門求人の約50%で「歓迎要件」または「必須要件」\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"255:1-255:118\"\u003e特にBig4（四大監査法人）のIT監査部門では、CISAの保有が事実上の標準となっています。\u003c/p\u003e\n\u003chr data-sourcepos=\"257:1-258:0\"\u003e\n\u003ch2 data-sourcepos=\"259:1-259:45\"\u003e\n\u003cspan id=\"5-学習方法と効率的な合格戦略\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#5-%E5%AD%A6%E7%BF%92%E6%96%B9%E6%B3%95%E3%81%A8%E5%8A%B9%E7%8E%87%E7%9A%84%E3%81%AA%E5%90%88%E6%A0%BC%E6%88%A6%E7%95%A5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5. 学習方法と効率的な合格戦略\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"261:1-261:38\"\u003e\n\u003cspan id=\"cisa合格のための学習戦略\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cisa%E5%90%88%E6%A0%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E5%AD%A6%E7%BF%92%E6%88%A6%E7%95%A5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCISA合格のための学習戦略\u003c/h3\u003e\n\u003cp data-sourcepos=\"263:1-263:37\"\u003e\u003cstrong\u003e推奨する学習ステップ：\u003c/strong\u003e\u003c/p\u003e\n\u003col data-sourcepos=\"265:1-279:0\"\u003e\n\u003cli data-sourcepos=\"265:1-269:0\"\u003e\n\u003cp data-sourcepos=\"265:4-265:36\"\u003e\u003cstrong\u003e基礎固め（1〜2ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"266:4-269:0\"\u003e\n\u003cli data-sourcepos=\"266:4-266:52\"\u003eISACA公式レビューマニュアルを通読\u003c/li\u003e\n\u003cli data-sourcepos=\"267:4-267:53\"\u003e各ドメインの概念と相互関係を理解\u003c/li\u003e\n\u003cli data-sourcepos=\"268:4-269:0\"\u003e不明な用語はその都度調べてノート化\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"270:1-274:0\"\u003e\n\u003cp data-sourcepos=\"270:4-270:45\"\u003e\u003cstrong\u003e問題演習中心期（2〜3ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"271:4-274:0\"\u003e\n\u003cli data-sourcepos=\"271:4-271:54\"\u003eISACA公式問題集（1,000問以上）を周回\u003c/li\u003e\n\u003cli data-sourcepos=\"272:4-272:71\"\u003e間違えた問題はドメインごとに分類して弱点把握\u003c/li\u003e\n\u003cli data-sourcepos=\"273:4-274:0\"\u003e正答率80%以上を目標に\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"275:1-279:0\"\u003e\n\u003cp data-sourcepos=\"275:4-275:36\"\u003e\u003cstrong\u003e仕上げ期（2〜4週間）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"276:4-279:0\"\u003e\n\u003cli data-sourcepos=\"276:4-276:41\"\u003e模擬試験で時間配分を確認\u003c/li\u003e\n\u003cli data-sourcepos=\"277:4-277:38\"\u003e弱点ドメインの集中復習\u003c/li\u003e\n\u003cli data-sourcepos=\"278:4-279:0\"\u003eISACAの倫理規程・監査基準の暗記\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"280:1-280:25\"\u003e\u003cstrong\u003e効果的な教材：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"281:1-284:0\"\u003e\n\u003cli data-sourcepos=\"281:1-281:38\"\u003eISACA CISA Review Manual（必須）\u003c/li\u003e\n\u003cli data-sourcepos=\"282:1-282:74\"\u003eISACA CISA Review Questions, Answers \u0026amp; Explanations Database（必須）\u003c/li\u003e\n\u003cli data-sourcepos=\"283:1-284:0\"\u003eHemang Doshi著「CISA Review Questions」（補助）\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"285:1-285:39\"\u003e\n\u003cspan id=\"cissp合格のための学習戦略\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cissp%E5%90%88%E6%A0%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E5%AD%A6%E7%BF%92%E6%88%A6%E7%95%A5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCISSP合格のための学習戦略\u003c/h3\u003e\n\u003cp data-sourcepos=\"287:1-287:37\"\u003e\u003cstrong\u003e推奨する学習ステップ：\u003c/strong\u003e\u003c/p\u003e\n\u003col data-sourcepos=\"289:1-308:0\"\u003e\n\u003cli data-sourcepos=\"289:1-293:0\"\u003e\n\u003cp data-sourcepos=\"289:4-289:45\"\u003e\u003cstrong\u003e全体像把握（2週間〜1ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"290:4-293:0\"\u003e\n\u003cli data-sourcepos=\"290:4-290:42\"\u003e(ISC)² Official Study Guideを一読\u003c/li\u003e\n\u003cli data-sourcepos=\"291:4-291:45\"\u003e8ドメインの範囲と深さを確認\u003c/li\u003e\n\u003cli data-sourcepos=\"292:4-293:0\"\u003e自身の強み・弱みを把握\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"294:1-298:0\"\u003e\n\u003cp data-sourcepos=\"294:4-294:48\"\u003e\u003cstrong\u003eドメイン別深堀り（3〜4ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"295:4-298:0\"\u003e\n\u003cli data-sourcepos=\"295:4-295:47\"\u003e弱いドメインから優先的に学習\u003c/li\u003e\n\u003cli data-sourcepos=\"296:4-296:62\"\u003e暗号技術、ネットワークは特に時間を確保\u003c/li\u003e\n\u003cli data-sourcepos=\"297:4-298:0\"\u003e英語のリソース（NIST文書等）も参照\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"299:1-303:0\"\u003e\n\u003cp data-sourcepos=\"299:4-299:39\"\u003e\u003cstrong\u003e問題演習期（2〜3ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"300:4-303:0\"\u003e\n\u003cli data-sourcepos=\"300:4-300:43\"\u003eOfficial Practice Testsを徹底活用\u003c/li\u003e\n\u003cli data-sourcepos=\"301:4-301:59\"\u003eCAT方式に慣れるため、時間を測って演習\u003c/li\u003e\n\u003cli data-sourcepos=\"302:4-303:0\"\u003e各ドメイン70%以上の正答率を目標に\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"304:1-308:0\"\u003e\n\u003cp data-sourcepos=\"304:4-304:36\"\u003e\u003cstrong\u003e総仕上げ（2〜4週間）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"305:4-308:0\"\u003e\n\u003cli data-sourcepos=\"305:4-305:50\"\u003e11th Hour CISSPで重要ポイント総復習\u003c/li\u003e\n\u003cli data-sourcepos=\"306:4-306:35\"\u003e模擬試験を複数回実施\u003c/li\u003e\n\u003cli data-sourcepos=\"307:4-308:0\"\u003e試験当日のコンディション調整\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"309:1-309:25\"\u003e\u003cstrong\u003e効果的な教材：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"310:1-314:0\"\u003e\n\u003cli data-sourcepos=\"310:1-310:48\"\u003e(ISC)² CISSP Official Study Guide（必須）\u003c/li\u003e\n\u003cli data-sourcepos=\"311:1-311:51\"\u003e(ISC)² CISSP Official Practice Tests（必須）\u003c/li\u003e\n\u003cli data-sourcepos=\"312:1-312:102\"\u003eSybex社「CISSP (ISC)² Certified Information Systems Security Professional Official Study Guide」\u003c/li\u003e\n\u003cli data-sourcepos=\"313:1-314:0\"\u003e「11th Hour CISSP」（直前対策用）\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"315:1-315:37\"\u003e\n\u003cspan id=\"cia合格のための学習戦略\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cia%E5%90%88%E6%A0%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E5%AD%A6%E7%BF%92%E6%88%A6%E7%95%A5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCIA合格のための学習戦略\u003c/h3\u003e\n\u003cp data-sourcepos=\"317:1-317:37\"\u003e\u003cstrong\u003e推奨する学習ステップ：\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"319:1-319:95\"\u003eCIAは3パートあるため、1パートずつ着実にクリアする戦略が有効です。\u003c/p\u003e\n\u003col data-sourcepos=\"321:1-335:0\"\u003e\n\u003cli data-sourcepos=\"321:1-325:0\"\u003e\n\u003cp data-sourcepos=\"321:4-321:40\"\u003e\u003cstrong\u003eパート1対策（2〜3ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"322:4-325:0\"\u003e\n\u003cli data-sourcepos=\"322:4-322:59\"\u003e内部監査の基礎概念、IIA基準を徹底理解\u003c/li\u003e\n\u003cli data-sourcepos=\"323:4-323:26\"\u003e倫理規程の暗記\u003c/li\u003e\n\u003cli data-sourcepos=\"324:4-325:0\"\u003e問題演習で出題パターンを把握\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"326:1-330:0\"\u003e\n\u003cp data-sourcepos=\"326:4-326:40\"\u003e\u003cstrong\u003eパート2対策（2〜3ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"327:4-330:0\"\u003e\n\u003cli data-sourcepos=\"327:4-327:62\"\u003e実務プロセス（計画、実施、報告）の理解\u003c/li\u003e\n\u003cli data-sourcepos=\"328:4-328:46\"\u003e不正検出、ITリスクの知識強化\u003c/li\u003e\n\u003cli data-sourcepos=\"329:4-330:0\"\u003eケーススタディ形式の問題に慣れる\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"331:1-335:0\"\u003e\n\u003cp data-sourcepos=\"331:4-331:40\"\u003e\u003cstrong\u003eパート3対策（2〜3ヶ月）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"332:4-335:0\"\u003e\n\u003cli data-sourcepos=\"332:4-332:41\"\u003e財務会計、管理会計の基礎\u003c/li\u003e\n\u003cli data-sourcepos=\"333:4-333:46\"\u003eビジネスプロセス、IT基礎知識\u003c/li\u003e\n\u003cli data-sourcepos=\"334:4-335:0\"\u003e最も範囲が広いため計画的に\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"336:1-336:25\"\u003e\u003cstrong\u003e効果的な教材：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"337:1-340:0\"\u003e\n\u003cli data-sourcepos=\"337:1-337:54\"\u003eIIA公式学習システム（CIA Learning System）\u003c/li\u003e\n\u003cli data-sourcepos=\"338:1-338:36\"\u003eGleim CIA Review（定評あり）\u003c/li\u003e\n\u003cli data-sourcepos=\"339:1-340:0\"\u003eWiley CIA Exam Review\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"341:1-341:43\"\u003e\n\u003cspan id=\"複数資格の効率的な取得順序\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%A4%87%E6%95%B0%E8%B3%87%E6%A0%BC%E3%81%AE%E5%8A%B9%E7%8E%87%E7%9A%84%E3%81%AA%E5%8F%96%E5%BE%97%E9%A0%86%E5%BA%8F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e複数資格の効率的な取得順序\u003c/h3\u003e\n\u003cp data-sourcepos=\"343:1-343:78\"\u003eキャリアプランに応じて、以下の取得順序を推奨します。\u003c/p\u003e\n\u003cp data-sourcepos=\"345:1-345:39\"\u003e\u003cstrong\u003eIT監査専門を目指す場合：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"346:1-348:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eCISA → CIA → CISSP（余力があれば）\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"350:1-350:49\"\u003e\u003cstrong\u003eセキュリティ専門を目指す場合：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"351:1-353:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eCISSP → CISA → CIA（余力があれば）\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"355:1-355:43\"\u003e\u003cstrong\u003e内部監査全般を目指す場合：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"text\" data-sourcepos=\"356:1-358:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003eCIA → CISA → CISSP（IT監査も担当するなら）\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"360:1-360:136\"\u003eCISA と CIA は監査という観点で知識が重複するため、片方を取得すると他方の学習効率が上がります。\u003c/p\u003e\n\u003chr data-sourcepos=\"362:1-363:0\"\u003e\n\u003ch2 data-sourcepos=\"364:1-364:39\"\u003e\n\u003cspan id=\"6-実務での活用シーン比較\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#6-%E5%AE%9F%E5%8B%99%E3%81%A7%E3%81%AE%E6%B4%BB%E7%94%A8%E3%82%B7%E3%83%BC%E3%83%B3%E6%AF%94%E8%BC%83\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6. 実務での活用シーン比較\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"366:1-366:32\"\u003e\n\u003cspan id=\"cisa知識が活きる場面\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cisa%E7%9F%A5%E8%AD%98%E3%81%8C%E6%B4%BB%E3%81%8D%E3%82%8B%E5%A0%B4%E9%9D%A2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCISA知識が活きる場面\u003c/h3\u003e\n\u003cp data-sourcepos=\"368:1-369:245\"\u003e\u003cstrong\u003e1. ITシステムの評価・監査\u003c/strong\u003e\u003cbr\u003e\n金融機関のシステム監査で、勘定系システムの変更管理プロセスを評価する際、CISA で学んだ「変更管理のコントロール目標」「承認プロセスの設計ポイント」が直接活用できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"371:1-371:16\"\u003e\u003cstrong\u003e具体例：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"372:1-376:0\"\u003e\n\u003cli data-sourcepos=\"372:1-372:44\"\u003eSOC1/SOC2報告書の作成・レビュー\u003c/li\u003e\n\u003cli data-sourcepos=\"373:1-373:36\"\u003eJ-SOX（IT全般統制）の評価\u003c/li\u003e\n\u003cli data-sourcepos=\"374:1-374:47\"\u003eシステム開発プロジェクトの監査\u003c/li\u003e\n\u003cli data-sourcepos=\"375:1-376:0\"\u003eベンダー管理の第三者評価\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"377:1-378:147\"\u003e\u003cstrong\u003e2. ITガバナンス体制の構築支援\u003c/strong\u003e\u003cbr\u003e\n経営層向けにITガバナンスフレームワーク（COBIT等）を導入する際、CISA の知識が体系的な提案に役立ちます。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"380:1-380:33\"\u003e\n\u003cspan id=\"cissp知識が活きる場面\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cissp%E7%9F%A5%E8%AD%98%E3%81%8C%E6%B4%BB%E3%81%8D%E3%82%8B%E5%A0%B4%E9%9D%A2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCISSP知識が活きる場面\u003c/h3\u003e\n\u003cp data-sourcepos=\"382:1-383:196\"\u003e\u003cstrong\u003e1. セキュリティアーキテクチャの設計\u003c/strong\u003e\u003cbr\u003e\n新システム構築時に、認証基盤、暗号化方式、ネットワークセグメンテーションを設計する際、CISSP の8ドメインの知識が総合的に活用できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"385:1-385:16\"\u003e\u003cstrong\u003e具体例：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"386:1-390:0\"\u003e\n\u003cli data-sourcepos=\"386:1-386:50\"\u003eゼロトラストアーキテクチャの設計\u003c/li\u003e\n\u003cli data-sourcepos=\"387:1-387:47\"\u003eクラウドセキュリティ戦略の策定\u003c/li\u003e\n\u003cli data-sourcepos=\"388:1-388:50\"\u003eインシデントレスポンス計画の作成\u003c/li\u003e\n\u003cli data-sourcepos=\"389:1-390:0\"\u003eセキュリティ教育プログラムの企画\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"391:1-392:165\"\u003e\u003cstrong\u003e2. 経営層へのセキュリティ投資説明\u003c/strong\u003e\u003cbr\u003e\nセキュリティ対策の費用対効果を経営層に説明する際、CISSP で学んだリスクマネジメントフレームワークを活用できます。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"394:1-394:31\"\u003e\n\u003cspan id=\"cia知識が活きる場面\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#cia%E7%9F%A5%E8%AD%98%E3%81%8C%E6%B4%BB%E3%81%8D%E3%82%8B%E5%A0%B4%E9%9D%A2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eCIA知識が活きる場面\u003c/h3\u003e\n\u003cp data-sourcepos=\"396:1-397:182\"\u003e\u003cstrong\u003e1. 全社的リスクマネジメントへの貢献\u003c/strong\u003e\u003cbr\u003e\n内部監査の立場から、ITリスクだけでなく、財務リスク、オペレーショナルリスク、コンプライアンスリスクを統合的に評価できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"399:1-399:16\"\u003e\u003cstrong\u003e具体例：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"400:1-404:0\"\u003e\n\u003cli data-sourcepos=\"400:1-400:62\"\u003e監査計画の策定（リスクベースアプローチ）\u003c/li\u003e\n\u003cli data-sourcepos=\"401:1-401:47\"\u003e不正調査（フォレンジック含む）\u003c/li\u003e\n\u003cli data-sourcepos=\"402:1-402:41\"\u003e経営層・監査委員会への報告\u003c/li\u003e\n\u003cli data-sourcepos=\"403:1-404:0\"\u003e内部統制の有効性評価\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"405:1-406:168\"\u003e\u003cstrong\u003e2. 監査部門のマネジメント\u003c/strong\u003e\u003cbr\u003e\nCAE（Chief Audit Executive）として監査部門を統括する際、IIA 基準に準拠した品質管理体制の構築に CIA の知識が必須となります。\u003c/p\u003e\n\u003chr data-sourcepos=\"408:1-409:0\"\u003e\n\u003ch2 data-sourcepos=\"410:1-410:30\"\u003e\n\u003cspan id=\"7-最新動向と将来性\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#7-%E6%9C%80%E6%96%B0%E5%8B%95%E5%90%91%E3%81%A8%E5%B0%86%E6%9D%A5%E6%80%A7\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e7. 最新動向と将来性\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"412:1-412:45\"\u003e\n\u003cspan id=\"2024年2025年の試験制度変更\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2024%E5%B9%B42025%E5%B9%B4%E3%81%AE%E8%A9%A6%E9%A8%93%E5%88%B6%E5%BA%A6%E5%A4%89%E6%9B%B4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2024年〜2025年の試験・制度変更\u003c/h3\u003e\n\u003cp data-sourcepos=\"414:1-414:11\"\u003e\u003cstrong\u003eCISA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"415:1-418:0\"\u003e\n\u003cli data-sourcepos=\"415:1-415:119\"\u003e2024年にドメイン構成が5領域に再編（旧：5領域から変更なし、ただし内容の更新あり）\u003c/li\u003e\n\u003cli data-sourcepos=\"416:1-416:73\"\u003eクラウド監査、AI/ML監査に関する出題比率が増加傾向\u003c/li\u003e\n\u003cli data-sourcepos=\"417:1-418:0\"\u003e日本語試験の品質向上が進行中\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"419:1-419:12\"\u003e\u003cstrong\u003eCISSP：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"420:1-423:0\"\u003e\n\u003cli data-sourcepos=\"420:1-420:90\"\u003e2024年4月に試験内容が改訂（AIセキュリティ、DevSecOps関連を強化）\u003c/li\u003e\n\u003cli data-sourcepos=\"421:1-421:30\"\u003e8ドメイン構成は維持\u003c/li\u003e\n\u003cli data-sourcepos=\"422:1-423:0\"\u003eCAT方式の継続（最短100問、最長150問）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"424:1-424:10\"\u003e\u003cstrong\u003eCIA：\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"425:1-428:0\"\u003e\n\u003cli data-sourcepos=\"425:1-425:58\"\u003e2024年に新3パート試験体制が完全移行完了\u003c/li\u003e\n\u003cli data-sourcepos=\"426:1-426:56\"\u003eIT監査に関する出題がパート2で増加傾向\u003c/li\u003e\n\u003cli data-sourcepos=\"427:1-428:0\"\u003eデータアナリティクス監査の知識がより重視される方向\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"429:1-429:28\"\u003e\n\u003cspan id=\"需要予測と将来性\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%9C%80%E8%A6%81%E4%BA%88%E6%B8%AC%E3%81%A8%E5%B0%86%E6%9D%A5%E6%80%A7\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e需要予測と将来性\u003c/h3\u003e\n\u003cp data-sourcepos=\"431:1-432:253\"\u003e\u003cstrong\u003eIT監査人材の需給ギャップ：\u003c/strong\u003e\u003cbr\u003e\n経済産業省の調査によると、2025年時点でIT人材は約36万人不足すると予測されています。その中でもIT監査・セキュリティ人材は特に不足が深刻で、以下の要因から需要増が見込まれます。\u003c/p\u003e\n\u003cul data-sourcepos=\"434:1-438:0\"\u003e\n\u003cli data-sourcepos=\"434:1-434:65\"\u003eサイバーセキュリティ経営ガイドラインの浸透\u003c/li\u003e\n\u003cli data-sourcepos=\"435:1-435:74\"\u003eクラウドサービスの利用拡大に伴う第三者保証ニーズ\u003c/li\u003e\n\u003cli data-sourcepos=\"436:1-436:71\"\u003eプライバシー規制の強化（個人情報保護法改正等）\u003c/li\u003e\n\u003cli data-sourcepos=\"437:1-438:0\"\u003eESG経営の観点からのITガバナンス強化\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"439:1-439:34\"\u003e\u003cstrong\u003e各資格の将来性評価：\u003c/strong\u003e\u003c/p\u003e\n\u003ctable data-sourcepos=\"441:1-445:61\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"441:1-441:98\"\u003e\n\u003cth data-sourcepos=\"441:2-441:9\"\u003e資格\u003c/th\u003e\n\u003cth data-sourcepos=\"441:11-441:38\"\u003e短期需要（1〜3年）\u003c/th\u003e\n\u003cth data-sourcepos=\"441:40-441:67\"\u003e中期需要（3〜5年）\u003c/th\u003e\n\u003cth data-sourcepos=\"441:69-441:97\"\u003e長期需要（5〜10年）\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"443:1-443:62\"\u003e\n\u003ctd data-sourcepos=\"443:2-443:7\"\u003eCISA\u003c/td\u003e\n\u003ctd data-sourcepos=\"443:9-443:25\"\u003e★★★★★\u003c/td\u003e\n\u003ctd data-sourcepos=\"443:27-443:43\"\u003e★★★★★\u003c/td\u003e\n\u003ctd data-sourcepos=\"443:45-443:61\"\u003e★★★★★\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"444:1-444:63\"\u003e\n\u003ctd data-sourcepos=\"444:2-444:8\"\u003eCISSP\u003c/td\u003e\n\u003ctd data-sourcepos=\"444:10-444:26\"\u003e★★★★★\u003c/td\u003e\n\u003ctd data-sourcepos=\"444:28-444:44\"\u003e★★★★★\u003c/td\u003e\n\u003ctd data-sourcepos=\"444:46-444:62\"\u003e★★★★☆\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"445:1-445:61\"\u003e\n\u003ctd data-sourcepos=\"445:2-445:6\"\u003eCIA\u003c/td\u003e\n\u003ctd data-sourcepos=\"445:8-445:24\"\u003e★★★★☆\u003c/td\u003e\n\u003ctd data-sourcepos=\"445:26-445:42\"\u003e★★★★☆\u003c/td\u003e\n\u003ctd data-sourcepos=\"445:44-445:60\"\u003e★★★★☆\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"447:1-447:393\"\u003eCISAとCISSPは引き続き高い需要が見込まれます。特にCISAは、DX推進に伴うシステム監査ニーズの増加から、さらなる需要拡大が予想されます。CISSPはAI/MLセキュリティなど新領域への適応が鍵となります。CIAは内部監査のデジタル化が進む中で、IT知識と組み合わせた活用が重要となるでしょう。\u003c/p\u003e\n\u003chr data-sourcepos=\"449:1-450:0\"\u003e\n\u003ch2 data-sourcepos=\"451:1-451:57\"\u003e\n\u003cspan id=\"8-資格選択のための判断フレームワーク\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#8-%E8%B3%87%E6%A0%BC%E9%81%B8%E6%8A%9E%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E5%88%A4%E6%96%AD%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e8. 資格選択のための判断フレームワーク\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"453:1-453:67\"\u003e\n\u003cspan id=\"自分に合った資格を選ぶためのチェックリスト\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%87%AA%E5%88%86%E3%81%AB%E5%90%88%E3%81%A3%E3%81%9F%E8%B3%87%E6%A0%BC%E3%82%92%E9%81%B8%E3%81%B6%E3%81%9F%E3%82%81%E3%81%AE%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%88\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e自分に合った資格を選ぶためのチェックリスト\u003c/h3\u003e\n\u003cp data-sourcepos=\"455:1-455:75\"\u003e以下の質問に答えて、最適な資格を判断してください。\u003c/p\u003e\n\u003cp data-sourcepos=\"457:1-457:31\"\u003e\u003cstrong\u003e【現在の業務内容】\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"458:1-461:0\"\u003e\n\u003cli data-sourcepos=\"458:1-458:60\"\u003e□ IT システムの監査・評価が主業務 → CISA\u003c/li\u003e\n\u003cli data-sourcepos=\"459:1-459:70\"\u003e□ セキュリティ対策の企画・実装が主業務 → CISSP\u003c/li\u003e\n\u003cli data-sourcepos=\"460:1-461:0\"\u003e□ 財務・業務監査が主業務でIT監査も担当 → CIA（+CISA）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"462:1-462:28\"\u003e\u003cstrong\u003e【キャリア目標】\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"463:1-467:0\"\u003e\n\u003cli data-sourcepos=\"463:1-463:74\"\u003e□ 監査法人のIT監査部門でパートナーを目指す → CISA\u003c/li\u003e\n\u003cli data-sourcepos=\"464:1-464:75\"\u003e□ 事業会社のCISO/セキュリティ責任者を目指す → CISSP\u003c/li\u003e\n\u003cli data-sourcepos=\"465:1-465:66\"\u003e□ 事業会社のCAE/内部監査責任者を目指す → CIA\u003c/li\u003e\n\u003cli data-sourcepos=\"466:1-467:0\"\u003e□ ITコンサルタントとして独立したい → CISA + CISSP\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"468:1-468:28\"\u003e\u003cstrong\u003e【学習リソース】\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"469:1-472:0\"\u003e\n\u003cli data-sourcepos=\"469:1-469:60\"\u003e□ 学習時間を200〜300時間確保できる → CISA\u003c/li\u003e\n\u003cli data-sourcepos=\"470:1-470:75\"\u003e□ 学習時間を400時間以上確保できる → CISSP または CIA\u003c/li\u003e\n\u003cli data-sourcepos=\"471:1-472:0\"\u003e□ 短期集中より長期分散で学びたい → CIA（3パート分割）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"473:1-473:16\"\u003e\u003cstrong\u003e【予算】\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"474:1-477:0\"\u003e\n\u003cli data-sourcepos=\"474:1-474:56\"\u003e□ 費用を抑えたい（30万円以下） → CISA\u003c/li\u003e\n\u003cli data-sourcepos=\"475:1-475:60\"\u003e□ ある程度投資できる（30〜50万円） → CIA\u003c/li\u003e\n\u003cli data-sourcepos=\"476:1-477:0\"\u003e□ キャリアのため十分に投資できる → CISSP\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 data-sourcepos=\"478:1-478:67\"\u003e\n\u003cspan id=\"ダブルライセンストリプルライセンスの価値\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%80%E3%83%96%E3%83%AB%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%97%E3%83%AB%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9%E3%81%AE%E4%BE%A1%E5%80%A4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eダブルライセンス・トリプルライセンスの価値\u003c/h3\u003e\n\u003cp data-sourcepos=\"480:1-480:102\"\u003e複数資格を保有することで、専門性と守備範囲の両方をアピールできます。\u003c/p\u003e\n\u003cp data-sourcepos=\"482:1-482:34\"\u003e\u003cstrong\u003e効果的な組み合わせ：\u003c/strong\u003e\u003c/p\u003e\n\u003col data-sourcepos=\"484:1-498:0\"\u003e\n\u003cli data-sourcepos=\"484:1-488:0\"\u003e\n\u003cp data-sourcepos=\"484:4-484:17\"\u003e\u003cstrong\u003eCISA + CIA\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"485:4-488:0\"\u003e\n\u003cli data-sourcepos=\"485:4-485:49\"\u003e内部監査とIT監査の両面をカバー\u003c/li\u003e\n\u003cli data-sourcepos=\"486:4-486:71\"\u003e企業の内部監査部門で最も評価される組み合わせ\u003c/li\u003e\n\u003cli data-sourcepos=\"487:4-488:0\"\u003e学習内容の重複が多く効率的\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"489:1-493:0\"\u003e\n\u003cp data-sourcepos=\"489:4-489:19\"\u003e\u003cstrong\u003eCISA + CISSP\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"490:4-493:0\"\u003e\n\u003cli data-sourcepos=\"490:4-490:77\"\u003e監査（評価）とセキュリティ（構築）の両面をカバー\u003c/li\u003e\n\u003cli data-sourcepos=\"491:4-491:53\"\u003eセキュリティコンサルタントに最適\u003c/li\u003e\n\u003cli data-sourcepos=\"492:4-493:0\"\u003e年収プレミアムも高い傾向\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"494:1-498:0\"\u003e\n\u003cp data-sourcepos=\"494:4-494:43\"\u003e\u003cstrong\u003eCISA + CIA + CISSP（トリプル）\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"495:4-498:0\"\u003e\n\u003cli data-sourcepos=\"495:4-495:71\"\u003e最強の組み合わせだが取得・維持コストも大きい\u003c/li\u003e\n\u003cli data-sourcepos=\"496:4-496:57\"\u003e部門長/役員クラスを目指す場合に有効\u003c/li\u003e\n\u003cli data-sourcepos=\"497:4-498:0\"\u003e段階的に取得していくのが現実的\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 data-sourcepos=\"499:1-499:15\"\u003e\n\u003cspan id=\"関連記事\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%96%A2%E9%80%A3%E8%A8%98%E4%BA%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e関連記事\u003c/h2\u003e\n\u003cp data-sourcepos=\"500:1-501:52\"\u003eIT監査・情報セキュリティの実務に役立つ情報を発信しています。\u003cbr\u003e\n👉 \u003ca href=\"https://itauditcompass.com/\" rel=\"nofollow noopener\" target=\"_blank\"\u003eIT Audit Compass\u003c/a\u003e\u003c/p\u003e\n","body":":::note info\nこの記事は [IT Audit Compass](https://itauditcompass.com/posts/2026-04-26-1121/) に掲載した記事の転載です。\n:::\n\n## はじめに：IT監査資格の重要性が高まる背景\n\nデジタルトランスフォーメーション（DX）の加速に伴い、企業のIT環境は急速に複雑化しています。クラウドサービスの普及、リモートワークの定着、そしてサイバー攻撃の高度化により、IT監査の重要性はかつてないほど高まっています。\n\n金融庁の調査によると、2024年度における上場企業のITガバナンス関連の指摘事項は前年比で約15%増加しており、IT統制の整備・運用に対する要求水準は年々厳格化しています。また、改正個人情報保護法やGDPR（EU一般データ保護規則）への対応など、コンプライアンス面でも専門知識を持つ人材へのニーズが急増しています。\n\nこのような状況下で、IT監査やセキュリティの専門性を客観的に証明できる資格の取得は、キャリアアップにおいて大きなアドバンテージとなります。本記事では、IT監査分野で特に評価の高い3つの資格「CISA（公認情報システム監査人）」「CISSP（情報システムセキュリティプロフェッショナル認定）」「CIA（公認内部監査人）」について、実務担当者の視点から徹底比較していきます。\n\nそれぞれの資格の特徴、難易度、取得にかかる費用、そしてキャリアへの影響まで、資格選択に必要な情報を網羅的にお伝えします。\n\n---\n\n## 1. 各資格の基本情報と位置づけ\n\n### CISA（Certified Information Systems Auditor）とは\n\nCISAは、ISACA（Information Systems Audit and Control Association）が認定する情報システム監査の国際資格です。1978年から認定が開始され、2024年時点で世界180カ国以上で約17万人が保有する、IT監査分野では最も認知度の高い資格といえます。\n\n**主な対象領域：**\n- 情報システムの監査プロセス\n- ITガバナンスと管理\n- 情報システムの取得・開発・導入\n- 情報システムの運用とビジネスレジリエンス\n- 情報資産の保護\n\nCISAは「監査」という観点からITシステムを評価・検証する専門性を証明する資格です。内部監査部門、監査法人、コンサルティングファームで特に重宝されています。\n\n### CISSP（Certified Information Systems Security Professional）とは\n\nCISSPは、(ISC)²（International Information System Security Certification Consortium）が認定する情報セキュリティの国際資格です。1994年に創設され、現在では世界で約16万人以上が保有する、セキュリティ分野における最高峰の資格として位置づけられています。\n\n**主な対象領域（8つのドメイン）：**\n1. セキュリティとリスクマネジメント\n2. 資産のセキュリティ\n3. セキュリティアーキテクチャとエンジニアリング\n4. 通信とネットワークセキュリティ\n5. アイデンティティとアクセス管理\n6. セキュリティの評価とテスト\n7. セキュリティオペレーション\n8. ソフトウェア開発セキュリティ\n\nCISSPはセキュリティを「設計・構築・運用」する立場からの専門性を証明します。CISO（最高情報セキュリティ責任者）やセキュリティマネージャーを目指す方に最適です。\n\n### CIA（Certified Internal Auditor）とは\n\nCIAは、IIA（The Institute of Internal Auditors：内部監査人協会）が認定する内部監査の国際資格です。1974年から認定が開始され、世界190カ国以上で約20万人が保有しています。内部監査分野では唯一のグローバルスタンダードとして広く認知されています。\n\n**主な対象領域（3パート構成）：**\n- パート1：内部監査の基礎\n- パート2：内部監査の実務\n- パート3：内部監査に関連するビジネス知識\n\nCIAはIT分野に限定されず、財務、業務、コンプライアンスなど幅広い内部監査の専門性を証明します。経営層に近いポジションでガバナンス全体を俯瞰したい方に向いています。\n\n---\n\n## 2. 受験要件と難易度の比較\n\n### 受験資格の違い\n\n各資格の受験要件は以下のとおりです。\n\n**CISA：**\n- 試験自体は誰でも受験可能\n- 認定には情報システム監査・セキュリティ・統制分野で最低5年間の実務経験が必要\n- 学歴や他資格により最大3年間まで経験を代替可能\n- 例：4年制大学卒業で2年間、修士号で1年間の代替が可能\n\n**CISSP：**\n- 8つのドメインのうち2つ以上で計5年間の実務経験が必要\n- 4年制大学の学位で1年間の代替が可能\n- 経験不足の場合は「Associate of (ISC)²」として仮認定を受け、6年以内に経験を積むことも可能\n\n**CIA：**\n- 試験自体は誰でも受験可能\n- 認定には内部監査または関連分野で2年間の実務経験が必要\n- 修士号保有者は1年間に短縮\n\n### 試験形式と合格率\n\n**CISA：**\n- 出題数：150問（4択）\n- 試験時間：4時間\n- 合格ライン：450点/800点満点（スケールスコア方式）\n- 合格率：約50%前後（公式発表なし、推定値）\n- 試験言語：日本語選択可能\n\n**CISSP：**\n- 出題数：100〜150問（CAT方式により可変）\n- 試験時間：3時間\n- 合格ライン：700点/1000点満点\n- 合格率：約25〜30%（推定値）\n- 試験言語：日本語選択可能\n\n**CIA：**\n- 出題数：パート1（125問）、パート2（100問）、パート3（100問）\n- 試験時間：各パート2〜2.5時間\n- 合格ライン：600点/750点満点（各パート）\n- 合格率：パートにより40〜50%（推定値）\n- 試験言語：日本語選択可能\n\n### 難易度の実感値\n\n実務経験者へのヒアリングに基づく難易度感は以下のとおりです。\n\n| 資格 | 学習時間目安 | 難易度 | 日本語受験のしやすさ |\n|------|-------------|--------|-------------------|\n| CISA | 200〜400時間 | ★★★☆☆ | 良好 |\n| CISSP | 400〜600時間 | ★★★★★ | 良好 |\n| CIA | 300〜500時間 | ★★★★☆ | 良好 |\n\nCISSPは8つのドメインにわたる広範な知識が求められ、特に暗号技術やネットワークセキュリティの深い理解が必要なため、最も難易度が高いとされています。CISAは監査実務経験があれば比較的取り組みやすく、CIAは3パートに分かれているため計画的に攻略しやすいという特徴があります。\n\n---\n\n## 3. 取得費用と維持コストの詳細\n\n### 初期取得にかかる費用\n\n**CISA：**\n- 受験料：575ドル（ISACA会員）/ 760ドル（非会員）\n- ISACA年会費：135ドル（日本支部会費は別途10,000円程度）\n- 教材費：3〜5万円（公式問題集、参考書等）\n- 講座受講費：15〜30万円（任意、各種資格スクール）\n- **合計目安：20〜50万円**\n\n**CISSP：**\n- 受験料：749ドル（約11万円）\n- 教材費：5〜8万円（公式ガイドブック、問題集等）\n- 講座受講費：20〜50万円（任意、5日間のブートキャンプ形式が一般的）\n- **合計目安：30〜70万円**\n\n**CIA：**\n- 受験料：各パート395ドル（IIA会員）/ 525ドル（非会員）\n- 登録料：230ドル\n- IIA年会費：245ドル（日本支部会費は別途）\n- 教材費：5〜10万円\n- 講座受講費：15〜40万円（任意）\n- **合計目安：30〜60万円**\n\n### 資格維持にかかる年間コスト\n\nすべての資格には継続教育（CPE：Continuing Professional Education）の要件があります。\n\n**CISA：**\n- 年間維持料：45ドル（ISACA会員）/ 85ドル（非会員）\n- CPE要件：年間20時間以上、3年間で120時間以上\n- CPE取得コスト：無料〜数万円（セミナー参加、書籍購読等）\n- **年間合計目安：1〜5万円**\n\n**CISSP：**\n- 年間維持料：125ドル\n- CPE要件：年間40時間以上、3年間で120時間以上\n- **年間合計目安：2〜7万円**\n\n**CIA：**\n- 年間維持料：会員は会費に含む\n- CPE要件：年間20時間以上、2年間で40時間以上\n- **年間合計目安：1〜5万円**\n\n### 企業による費用負担の実態\n\n多くの企業では、IT監査やセキュリティ関連の資格取得を支援する制度があります。\n\n一般的な支援内容：\n- 受験料の全額または一部補助（合格時のみの企業も多い）\n- 講座受講費の補助（上限10〜30万円が一般的）\n- 合格一時金（3〜10万円程度）\n- 資格手当（月額5,000〜30,000円）\n\n大手監査法人やコンサルティングファームでは、CISA・CISSP取得者に月額1〜3万円程度の資格手当が支給されるケースが多く、維持コストを十分にカバーできます。\n\n---\n\n## 4. キャリアパスと年収への影響\n\n### 各資格のキャリアマップ\n\n**CISA保有者の典型的なキャリアパス：**\n```\nIT監査スタッフ（1〜3年目）\n    ↓\nIT監査シニア/主任（4〜7年目）\n    ↓\nIT監査マネージャー（8〜12年目）\n    ↓\nIT監査部長/CAE（13年目〜）\n```\n\n活躍フィールド：\n- 監査法人のIT監査部門\n- 企業の内部監査部門（IT監査担当）\n- システムリスク管理部門\n- ITコンサルティングファーム\n\n**CISSP保有者の典型的なキャリアパス：**\n```\nセキュリティエンジニア（1〜3年目）\n    ↓\nセキュリティアーキテクト（4〜7年目）\n    ↓\nセキュリティマネージャー（8〜12年目）\n    ↓\nCISO/情報セキュリティ責任者（13年目〜）\n```\n\n活躍フィールド：\n- 企業のセキュリティ部門\n- セキュリティベンダー\n- SOC/CSIRT\n- セキュリティコンサルティング\n\n**CIA保有者の典型的なキャリアパス：**\n```\n内部監査スタッフ（1〜3年目）\n    ↓\n内部監査シニア（4〜7年目）\n    ↓\n内部監査マネージャー（8〜12年目）\n    ↓\nCAE/監査役（13年目〜）\n```\n\n活躍フィールド：\n- 企業の内部監査部門\n- 監査法人のアドバイザリー部門\n- リスクマネジメント部門\n- 取締役会事務局\n\n### 年収比較データ\n\n日本国内における各資格保有者の年収レンジ（2024年の求人データ等からの推計）：\n\n| 経験年数 | CISA | CISSP | CIA |\n|---------|------|-------|-----|\n| 3〜5年 | 500〜700万円 | 550〜800万円 | 500〜700万円 |\n| 5〜10年 | 700〜1,000万円 | 800〜1,200万円 | 700〜1,000万円 |\n| 10年以上 | 900〜1,500万円 | 1,000〜1,800万円 | 900〜1,400万円 |\n| 管理職 | 1,200〜2,000万円 | 1,500〜2,500万円 | 1,200〜2,000万円 |\n\nCISSPは技術的な専門性が高く、需給バランスからも高年収になりやすい傾向があります。特に外資系企業やセキュリティベンダーでは、CISSP保有を必須条件とするポジションも多く、年収プレミアムが顕著です。\n\n### 転職市場での評価\n\n大手転職エージェントの調査によると、IT監査・セキュリティ関連職種の求人において、以下の傾向が見られます。\n\n- CISA：監査法人・コンサル系求人の約60%で「歓迎要件」または「必須要件」\n- CISSP：セキュリティ専門職求人の約70%で「歓迎要件」、約30%で「必須要件」\n- CIA：内部監査部門求人の約50%で「歓迎要件」または「必須要件」\n\n特にBig4（四大監査法人）のIT監査部門では、CISAの保有が事実上の標準となっています。\n\n---\n\n## 5. 学習方法と効率的な合格戦略\n\n### CISA合格のための学習戦略\n\n**推奨する学習ステップ：**\n\n1. **基礎固め（1〜2ヶ月）**\n   - ISACA公式レビューマニュアルを通読\n   - 各ドメインの概念と相互関係を理解\n   - 不明な用語はその都度調べてノート化\n\n2. **問題演習中心期（2〜3ヶ月）**\n   - ISACA公式問題集（1,000問以上）を周回\n   - 間違えた問題はドメインごとに分類して弱点把握\n   - 正答率80%以上を目標に\n\n3. **仕上げ期（2〜4週間）**\n   - 模擬試験で時間配分を確認\n   - 弱点ドメインの集中復習\n   - ISACAの倫理規程・監査基準の暗記\n\n**効果的な教材：**\n- ISACA CISA Review Manual（必須）\n- ISACA CISA Review Questions, Answers \u0026 Explanations Database（必須）\n- Hemang Doshi著「CISA Review Questions」（補助）\n\n### CISSP合格のための学習戦略\n\n**推奨する学習ステップ：**\n\n1. **全体像把握（2週間〜1ヶ月）**\n   - (ISC)² Official Study Guideを一読\n   - 8ドメインの範囲と深さを確認\n   - 自身の強み・弱みを把握\n\n2. **ドメイン別深堀り（3〜4ヶ月）**\n   - 弱いドメインから優先的に学習\n   - 暗号技術、ネットワークは特に時間を確保\n   - 英語のリソース（NIST文書等）も参照\n\n3. **問題演習期（2〜3ヶ月）**\n   - Official Practice Testsを徹底活用\n   - CAT方式に慣れるため、時間を測って演習\n   - 各ドメイン70%以上の正答率を目標に\n\n4. **総仕上げ（2〜4週間）**\n   - 11th Hour CISSPで重要ポイント総復習\n   - 模擬試験を複数回実施\n   - 試験当日のコンディション調整\n\n**効果的な教材：**\n- (ISC)² CISSP Official Study Guide（必須）\n- (ISC)² CISSP Official Practice Tests（必須）\n- Sybex社「CISSP (ISC)² Certified Information Systems Security Professional Official Study Guide」\n- 「11th Hour CISSP」（直前対策用）\n\n### CIA合格のための学習戦略\n\n**推奨する学習ステップ：**\n\nCIAは3パートあるため、1パートずつ着実にクリアする戦略が有効です。\n\n1. **パート1対策（2〜3ヶ月）**\n   - 内部監査の基礎概念、IIA基準を徹底理解\n   - 倫理規程の暗記\n   - 問題演習で出題パターンを把握\n\n2. **パート2対策（2〜3ヶ月）**\n   - 実務プロセス（計画、実施、報告）の理解\n   - 不正検出、ITリスクの知識強化\n   - ケーススタディ形式の問題に慣れる\n\n3. **パート3対策（2〜3ヶ月）**\n   - 財務会計、管理会計の基礎\n   - ビジネスプロセス、IT基礎知識\n   - 最も範囲が広いため計画的に\n\n**効果的な教材：**\n- IIA公式学習システム（CIA Learning System）\n- Gleim CIA Review（定評あり）\n- Wiley CIA Exam Review\n\n### 複数資格の効率的な取得順序\n\nキャリアプランに応じて、以下の取得順序を推奨します。\n\n**IT監査専門を目指す場合：**\n```\nCISA → CIA → CISSP（余力があれば）\n```\n\n**セキュリティ専門を目指す場合：**\n```\nCISSP → CISA → CIA（余力があれば）\n```\n\n**内部監査全般を目指す場合：**\n```\nCIA → CISA → CISSP（IT監査も担当するなら）\n```\n\nCISA と CIA は監査という観点で知識が重複するため、片方を取得すると他方の学習効率が上がります。\n\n---\n\n## 6. 実務での活用シーン比較\n\n### CISA知識が活きる場面\n\n**1. ITシステムの評価・監査**\n金融機関のシステム監査で、勘定系システムの変更管理プロセスを評価する際、CISA で学んだ「変更管理のコントロール目標」「承認プロセスの設計ポイント」が直接活用できます。\n\n**具体例：**\n- SOC1/SOC2報告書の作成・レビュー\n- J-SOX（IT全般統制）の評価\n- システム開発プロジェクトの監査\n- ベンダー管理の第三者評価\n\n**2. ITガバナンス体制の構築支援**\n経営層向けにITガバナンスフレームワーク（COBIT等）を導入する際、CISA の知識が体系的な提案に役立ちます。\n\n### CISSP知識が活きる場面\n\n**1. セキュリティアーキテクチャの設計**\n新システム構築時に、認証基盤、暗号化方式、ネットワークセグメンテーションを設計する際、CISSP の8ドメインの知識が総合的に活用できます。\n\n**具体例：**\n- ゼロトラストアーキテクチャの設計\n- クラウドセキュリティ戦略の策定\n- インシデントレスポンス計画の作成\n- セキュリティ教育プログラムの企画\n\n**2. 経営層へのセキュリティ投資説明**\nセキュリティ対策の費用対効果を経営層に説明する際、CISSP で学んだリスクマネジメントフレームワークを活用できます。\n\n### CIA知識が活きる場面\n\n**1. 全社的リスクマネジメントへの貢献**\n内部監査の立場から、ITリスクだけでなく、財務リスク、オペレーショナルリスク、コンプライアンスリスクを統合的に評価できます。\n\n**具体例：**\n- 監査計画の策定（リスクベースアプローチ）\n- 不正調査（フォレンジック含む）\n- 経営層・監査委員会への報告\n- 内部統制の有効性評価\n\n**2. 監査部門のマネジメント**\nCAE（Chief Audit Executive）として監査部門を統括する際、IIA 基準に準拠した品質管理体制の構築に CIA の知識が必須となります。\n\n---\n\n## 7. 最新動向と将来性\n\n### 2024年〜2025年の試験・制度変更\n\n**CISA：**\n- 2024年にドメイン構成が5領域に再編（旧：5領域から変更なし、ただし内容の更新あり）\n- クラウド監査、AI/ML監査に関する出題比率が増加傾向\n- 日本語試験の品質向上が進行中\n\n**CISSP：**\n- 2024年4月に試験内容が改訂（AIセキュリティ、DevSecOps関連を強化）\n- 8ドメイン構成は維持\n- CAT方式の継続（最短100問、最長150問）\n\n**CIA：**\n- 2024年に新3パート試験体制が完全移行完了\n- IT監査に関する出題がパート2で増加傾向\n- データアナリティクス監査の知識がより重視される方向\n\n### 需要予測と将来性\n\n**IT監査人材の需給ギャップ：**\n経済産業省の調査によると、2025年時点でIT人材は約36万人不足すると予測されています。その中でもIT監査・セキュリティ人材は特に不足が深刻で、以下の要因から需要増が見込まれます。\n\n- サイバーセキュリティ経営ガイドラインの浸透\n- クラウドサービスの利用拡大に伴う第三者保証ニーズ\n- プライバシー規制の強化（個人情報保護法改正等）\n- ESG経営の観点からのITガバナンス強化\n\n**各資格の将来性評価：**\n\n| 資格 | 短期需要（1〜3年） | 中期需要（3〜5年） | 長期需要（5〜10年） |\n|------|------------------|------------------|-------------------|\n| CISA | ★★★★★ | ★★★★★ | ★★★★★ |\n| CISSP | ★★★★★ | ★★★★★ | ★★★★☆ |\n| CIA | ★★★★☆ | ★★★★☆ | ★★★★☆ |\n\nCISAとCISSPは引き続き高い需要が見込まれます。特にCISAは、DX推進に伴うシステム監査ニーズの増加から、さらなる需要拡大が予想されます。CISSPはAI/MLセキュリティなど新領域への適応が鍵となります。CIAは内部監査のデジタル化が進む中で、IT知識と組み合わせた活用が重要となるでしょう。\n\n---\n\n## 8. 資格選択のための判断フレームワーク\n\n### 自分に合った資格を選ぶためのチェックリスト\n\n以下の質問に答えて、最適な資格を判断してください。\n\n**【現在の業務内容】**\n- □ IT システムの監査・評価が主業務 → CISA\n- □ セキュリティ対策の企画・実装が主業務 → CISSP\n- □ 財務・業務監査が主業務でIT監査も担当 → CIA（+CISA）\n\n**【キャリア目標】**\n- □ 監査法人のIT監査部門でパートナーを目指す → CISA\n- □ 事業会社のCISO/セキュリティ責任者を目指す → CISSP\n- □ 事業会社のCAE/内部監査責任者を目指す → CIA\n- □ ITコンサルタントとして独立したい → CISA + CISSP\n\n**【学習リソース】**\n- □ 学習時間を200〜300時間確保できる → CISA\n- □ 学習時間を400時間以上確保できる → CISSP または CIA\n- □ 短期集中より長期分散で学びたい → CIA（3パート分割）\n\n**【予算】**\n- □ 費用を抑えたい（30万円以下） → CISA\n- □ ある程度投資できる（30〜50万円） → CIA\n- □ キャリアのため十分に投資できる → CISSP\n\n### ダブルライセンス・トリプルライセンスの価値\n\n複数資格を保有することで、専門性と守備範囲の両方をアピールできます。\n\n**効果的な組み合わせ：**\n\n1. **CISA + CIA**\n   - 内部監査とIT監査の両面をカバー\n   - 企業の内部監査部門で最も評価される組み合わせ\n   - 学習内容の重複が多く効率的\n\n2. **CISA + CISSP**\n   - 監査（評価）とセキュリティ（構築）の両面をカバー\n   - セキュリティコンサルタントに最適\n   - 年収プレミアムも高い傾向\n\n3. **CISA + CIA + CISSP（トリプル）**\n   - 最強の組み合わせだが取得・維持コストも大きい\n   - 部門長/役員クラスを目指す場合に有効\n   - 段階的に取得していくのが現実的\n\n## 関連記事\nIT監査・情報セキュリティの実務に役立つ情報を発信しています。\n👉 [IT Audit Compass](https://itauditcompass.com/)\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:51:57+09:00","group":null,"id":"8e9bfb1fa89986d99d4e","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"情報セキュリティ","versions":[]},{"name":"CISSP","versions":[]},{"name":"CISA","versions":[]},{"name":"セキュリティ資格","versions":[]}],"title":"IT監査におすすめ資格：CISA・CISSP・公認内部監査人を比較","updated_at":"2026-05-06T10:51:57+09:00","url":"https://qiita.com/KarioLabs/items/8e9bfb1fa89986d99d4e","user":{"description":"KarioLabs - IT監査 20年超の実務家 - 20年以上、金融機関を中心にIT監査に携わる。","facebook_id":"","followees_count":1,"followers_count":0,"github_login_name":null,"id":"KarioLabs","items_count":1,"linkedin_id":"","location":"東京","name":"KarioLabs","organization":"","permanent_id":4426344,"profile_image_url":"https://secure.gravatar.com/avatar/fac1b3bce5b8485d6ce4882d44174a8c","team_only":false,"twitter_screen_name":null,"website_url":"https://itauditcompass.com/"},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch2 data-sourcepos=\"1:1-1:15\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h2\u003e\n\u003cp data-sourcepos=\"3:1-3:151\"\u003eWordPressの \u003ccode\u003efunctions.php\u003c/code\u003e に外部API（OpenWeatherMapなど）の呼び出し処理を書いた場合、以下のような課題があります。\u003c/p\u003e\n\u003cul data-sourcepos=\"5:1-8:0\"\u003e\n\u003cli data-sourcepos=\"5:1-5:92\"\u003e\n\u003cstrong\u003eセキュリティ:\u003c/strong\u003e APIキーがテーマのコード内に露出してしまう不安\u003c/li\u003e\n\u003cli data-sourcepos=\"6:1-6:113\"\u003e\n\u003cstrong\u003e保守性:\u003c/strong\u003e WordPress本体やテーマの更新時にコードが消えたり影響が出たりする心配\u003c/li\u003e\n\u003cli data-sourcepos=\"7:1-8:0\"\u003e\n\u003cstrong\u003eパフォーマンス:\u003c/strong\u003e 処理が重くなった際、サイト全体のレスポンスに影響する可能性\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"9:1-9:251\"\u003eこれらの課題を解決するため、\u003cstrong\u003eAPIのデータ取得・HTML生成処理をWordPressの外（別ディレクトリ）に切り離し、ショートコード経由で安全に呼び出す\u003c/strong\u003eことに成功しましたので紹介します。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"12:1-12:21\"\u003e\n\u003cspan id=\"完成イメージ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%8C%E6%88%90%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e完成イメージ\u003c/h2\u003e\n\u003cp data-sourcepos=\"13:1-13:177\"\u003eこの記事の手法を最後まで実装すると、サイト内に以下のような川越市の天気情報をショートコードで表示できるようになります。\u003c/p\u003e\n\u003cp data-sourcepos=\"15:1-15:122\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1466604%2Fbd86ced4-f57e-4028-81f4-13ad42ddd590.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=5d721ca74e9ff795fc0c78bbf0145a6b\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1466604%2Fbd86ced4-f57e-4028-81f4-13ad42ddd590.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=5d721ca74e9ff795fc0c78bbf0145a6b\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1466604%2Fbd86ced4-f57e-4028-81f4-13ad42ddd590.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=b6f6a02b5424ce9444d25c2b75a20a85 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466604/bd86ced4-f57e-4028-81f4-13ad42ddd590.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"18:1-18:24\"\u003e\n\u003cspan id=\"システムの概要\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%AE%E6%A6%82%E8%A6%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eシステムの概要\u003c/h2\u003e\n\u003cp data-sourcepos=\"20:1-20:211\"\u003eWordPressの外に専用のPHPファイルを配置します。WordPress側はショートコード経由でそのファイルと通信（内部API呼び出し）を行い、結果のHTMLを表示させます。\u003c/p\u003e\n\u003col data-sourcepos=\"22:1-27:0\"\u003e\n\u003cli data-sourcepos=\"22:1-22:40\"\u003e\n\u003cstrong\u003eユーザー\u003c/strong\u003eがページを閲覧\u003c/li\u003e\n\u003cli data-sourcepos=\"23:1-23:109\"\u003e\n\u003cstrong\u003eWordPress\u003c/strong\u003eがショートコードを実行し、自サーバー内のPHPファイルにリクエスト\u003c/li\u003e\n\u003cli data-sourcepos=\"24:1-24:108\"\u003e\n\u003cstrong\u003ePHPファイル\u003c/strong\u003eがAPIキーを使って外部サービス（OpenWeatherMap）からデータを取得\u003c/li\u003e\n\u003cli data-sourcepos=\"25:1-25:63\"\u003e\n\u003cstrong\u003ePHPファイル\u003c/strong\u003eがHTMLを生成してJSON形式で返却\u003c/li\u003e\n\u003cli data-sourcepos=\"26:1-27:0\"\u003e\n\u003cstrong\u003eWordPress\u003c/strong\u003eがそのHTMLを受け取って表示\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr data-sourcepos=\"28:1-29:0\"\u003e\n\u003ch2 data-sourcepos=\"30:1-30:43\"\u003e\n\u003cspan id=\"手順1外部phpファイルの作成\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%89%8B%E9%A0%861%E5%A4%96%E9%83%A8php%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e手順1：外部PHPファイルの作成\u003c/h2\u003e\n\u003cp data-sourcepos=\"32:1-32:193\"\u003eまず、WordPressのインストールディレクトリ（公開ディレクトリ）の直下に \u003ccode\u003eapi\u003c/code\u003e などのフォルダを作成し、API処理用のPHPファイルを設置します。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"34:1-34:22\"\u003e\n\u003cspan id=\"配置場所の例\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E9%85%8D%E7%BD%AE%E5%A0%B4%E6%89%80%E3%81%AE%E4%BE%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e配置場所の例\u003c/h3\u003e\n\u003cp data-sourcepos=\"35:1-35:37\"\u003e\u003ccode\u003epublic_html/api/kawagoe_weather.php\u003c/code\u003e\u003c/p\u003e\n\u003ch3 data-sourcepos=\"37:1-37:44\"\u003e\n\u003cspan id=\"実装コードkawagoe_weatherphp\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E8%A3%85%E3%82%B3%E3%83%BC%E3%83%89kawagoe_weatherphp\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実装コード（kawagoe_weather.php）\u003c/h3\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"39:1-80:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// JSONを返す設定とCORS対策（安全にデータを渡すため）\u003c/span\u003e\n\u003cspan class=\"nb\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'Content-Type: application/json; charset=utf-8'\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003cspan class=\"nb\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'Access-Control-Allow-Origin: *'\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// --- 設定項目 ---\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$api_key\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'YOUR_API_KEY'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"c1\"\u003e// OpenWeatherMapのAPIキー\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$city_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'1859171'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e      \u003cspan class=\"c1\"\u003e// 川越市のID\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$url\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\"[https://api.openweathermap.org/data/2.5/weather?id=](https://api.openweathermap.org/data/2.5/weather?id=)\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nv\"\u003e$city_id\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026amp;appid=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nv\"\u003e$api_key\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026amp;units=metric\u0026amp;lang=ja\"\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 1. 外部APIからデータを取得\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$response\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"nb\"\u003efile_get_contents\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$url\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$response\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"kc\"\u003eFALSE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eecho\u003c/span\u003e \u003cspan class=\"nb\"\u003ejson_encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'error'\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"s1\"\u003e'天気情報が取得できません'\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eexit\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"nv\"\u003e$data\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003ejson_decode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$response\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'cod'\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e!==\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eecho\u003c/span\u003e \u003cspan class=\"nb\"\u003ejson_encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'error'\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"s1\"\u003e'エラー: '\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'message'\u003c/span\u003e\u003cspan class=\"p\"\u003e]));\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eexit\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 2. 必要なデータの抽出\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$temp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eround\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'main'\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s1\"\u003e'temp'\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$description\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'weather'\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s1\"\u003e'description'\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$icon\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'weather'\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s1\"\u003e'icon'\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 3. 表示用のHTML生成\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$html\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u0026lt;div class=\"weather-box\" style=\"border: 1px solid #ccc; padding: 15px; border-radius: 10px; background: #fff; max-width: 300px; margin: 20px 0; text-align: center;\"\u0026gt;'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$html\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u0026lt;p style=\"margin: 0; font-size: 0.9em; color: #666;\"\u0026gt;川越市の現在の天気\u0026lt;/p\u0026gt;'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$html\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u0026lt;img src=\"[https://openweathermap.org/img/wn/](https://openweathermap.org/img/wn/)'\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"nv\"\u003e$icon\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"s1\"\u003e'@2x.png\" style=\"width: 80px; margin: -10px 0;\"\u0026gt;'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$html\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u0026lt;div style=\"font-size: 1.2em; font-weight: bold;\"\u0026gt;'\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"nv\"\u003e$description\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"s1\"\u003e' / '\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"nv\"\u003e$temp\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e \u003cspan class=\"s1\"\u003e'℃\u0026lt;/div\u0026gt;'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"nv\"\u003e$html\u003c/span\u003e \u003cspan class=\"mf\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'\u0026lt;/div\u0026gt;'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"c1\"\u003e// 4. 結果をJSON形式で出力\u003c/span\u003e\n\u003cspan class=\"k\"\u003eecho\u003c/span\u003e \u003cspan class=\"nb\"\u003ejson_encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'html'\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nv\"\u003e$html\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"82:1-82:53\"\u003e\n\u003cspan id=\"手順2wordpress側の設定functionsphp\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%89%8B%E9%A0%862wordpress%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9Afunctionsphp\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e手順2：WordPress側の設定（functions.php）\u003c/h2\u003e\n\u003cp data-sourcepos=\"84:1-84:114\"\u003e手順1で作ったPHPファイルを呼び出すための「橋渡し」を \u003ccode\u003efunctions.php\u003c/code\u003e に記述します。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"86:1-86:19\"\u003e\n\u003cspan id=\"実装コード\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E8%A3%85%E3%82%B3%E3%83%BC%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実装コード\u003c/h3\u003e\n\u003cp data-sourcepos=\"88:1-88:71\"\u003eテーマの \u003ccode\u003efunctions.php\u003c/code\u003e に以下のコードを追加します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"90:1-116:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003ekawagoe_weather_shortcode\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// 手順1で設置したファイルのURL（自分のサイトのドメインに書き換えてください）\u003c/span\u003e\n    \u003cspan class=\"nv\"\u003e$api_url\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e'[https://example.com/api/kawagoe_weather.php](https://example.com/api/kawagoe_weather.php)'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n    \n    \u003cspan class=\"c1\"\u003e// WordPressの関数を使って内部通信を行う\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// 自分のサイト内なので sslverify は false でも問題ありません\u003c/span\u003e\n    \u003cspan class=\"nv\"\u003e$response\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ewp_remote_get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$api_url\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'sslverify'\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nf\"\u003eis_wp_error\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$response\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"s1\"\u003e'天気情報の取得に失敗しました（通信エラー）'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"nv\"\u003e$body\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003ewp_remote_retrieve_body\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$response\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"nv\"\u003e$data\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003ejson_decode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$body\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n    \u003cspan class=\"c1\"\u003e// JSONからHTMLを取り出して出力する\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003eisset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'html'\u003c/span\u003e\u003cspan class=\"p\"\u003e]))\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nv\"\u003e$data\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e'html'\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"s1\"\u003e'データの読み込みに失敗しました'\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e// ショートコード名 [kawagoe_weather] を登録\u003c/span\u003e\n\u003cspan class=\"nf\"\u003eadd_shortcode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e'kawagoe_weather'\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e'kawagoe_weather_shortcode'\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 data-sourcepos=\"118:1-118:31\"\u003e\n\u003cspan id=\"手順3記事への表示\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%89%8B%E9%A0%863%E8%A8%98%E4%BA%8B%E3%81%B8%E3%81%AE%E8%A1%A8%E7%A4%BA\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e手順3：記事への表示\u003c/h2\u003e\n\u003cp data-sourcepos=\"120:1-121:17\"\u003eあとは、WordPressのブロックエディタやクラシックエディタで、表示したい場所にショートコードを記述するだけです。\u003cbr\u003e\n[kawagoe_weather]\u003c/p\u003e\n\u003ch2 data-sourcepos=\"123:1-123:21\"\u003e\n\u003cspan id=\"実際の表示例\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E9%9A%9B%E3%81%AE%E8%A1%A8%E7%A4%BA%E4%BE%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実際の表示例\u003c/h2\u003e\n\u003cp data-sourcepos=\"125:1-125:168\"\u003e今回解説した手法を用いて、実際に天気を表示させているページがこちらです。表示速度やデザインの参考にしてください。\u003c/p\u003e\n\u003cp data-sourcepos=\"127:1-127:99\"\u003e\u003cstrong\u003e\u003ca href=\"https://www.cibalabo.site/c1/sokonashi1/\" rel=\"nofollow noopener\" target=\"_blank\"\u003e川越市の現在の天気（実装サンプル）\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003chr data-sourcepos=\"129:1-129:3\"\u003e\n\u003ch2 data-sourcepos=\"130:1-130:12\"\u003e\n\u003cspan id=\"まとめ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%BE%E3%81%A8%E3%82%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eまとめ\u003c/h2\u003e\n\u003cp data-sourcepos=\"132:1-132:54\"\u003eこの手法のメリットは以下の通りです。\u003c/p\u003e\n\u003col data-sourcepos=\"134:1-137:0\"\u003e\n\u003cli data-sourcepos=\"134:1-134:106\"\u003e\n\u003cstrong\u003e管理の分離:\u003c/strong\u003e \u003ccode\u003efunctions.php\u003c/code\u003e がスッキリし、APIロジックの修正が容易になる。\u003c/li\u003e\n\u003cli data-sourcepos=\"135:1-135:139\"\u003e\n\u003cstrong\u003e再利用性:\u003c/strong\u003e 今回作ったPHPファイルは、WordPress以外のサイトやJavaScript（Fetch API）からも利用できる。\u003c/li\u003e\n\u003cli data-sourcepos=\"136:1-137:0\"\u003e\n\u003cstrong\u003e安全性:\u003c/strong\u003e APIキーをテーマファイル内に持たせないため、セキュリティリスクを軽減できる。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp data-sourcepos=\"138:1-138:168\"\u003eWordPressを単独のサイトとしてだけでなく、「APIサーバー」の入り口として活用する第一歩として、ぜひ試してみてください。\u003c/p\u003e\n","body":"## はじめに\n\nWordPressの `functions.php` に外部API（OpenWeatherMapなど）の呼び出し処理を書いた場合、以下のような課題があります。\n\n- **セキュリティ:** APIキーがテーマのコード内に露出してしまう不安\n- **保守性:** WordPress本体やテーマの更新時にコードが消えたり影響が出たりする心配\n- **パフォーマンス:** 処理が重くなった際、サイト全体のレスポンスに影響する可能性\n\nこれらの課題を解決するため、**APIのデータ取得・HTML生成処理をWordPressの外（別ディレクトリ）に切り離し、ショートコード経由で安全に呼び出す**ことに成功しましたので紹介します。\n\n\n## 完成イメージ\nこの記事の手法を最後まで実装すると、サイト内に以下のような川越市の天気情報をショートコードで表示できるようになります。\n\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1466604/bd86ced4-f57e-4028-81f4-13ad42ddd590.png)\n\n\n## システムの概要\n\nWordPressの外に専用のPHPファイルを配置します。WordPress側はショートコード経由でそのファイルと通信（内部API呼び出し）を行い、結果のHTMLを表示させます。\n\n1. **ユーザー**がページを閲覧\n2. **WordPress**がショートコードを実行し、自サーバー内のPHPファイルにリクエスト\n3. **PHPファイル**がAPIキーを使って外部サービス（OpenWeatherMap）からデータを取得\n4. **PHPファイル**がHTMLを生成してJSON形式で返却\n5. **WordPress**がそのHTMLを受け取って表示\n\n---\n\n## 手順1：外部PHPファイルの作成\n\nまず、WordPressのインストールディレクトリ（公開ディレクトリ）の直下に `api` などのフォルダを作成し、API処理用のPHPファイルを設置します。\n\n### 配置場所の例\n`public_html/api/kawagoe_weather.php`\n\n### 実装コード（kawagoe_weather.php）\n\n```php\n\u003c?php\n// JSONを返す設定とCORS対策（安全にデータを渡すため）\nheader('Content-Type: application/json; charset=utf-8');\nheader('Access-Control-Allow-Origin: *');\n\n// --- 設定項目 ---\n$api_key = 'YOUR_API_KEY'; // OpenWeatherMapのAPIキー\n$city_id = '1859171';      // 川越市のID\n$url = \"[https://api.openweathermap.org/data/2.5/weather?id=](https://api.openweathermap.org/data/2.5/weather?id=){$city_id}\u0026appid={$api_key}\u0026units=metric\u0026lang=ja\";\n\n// 1. 外部APIからデータを取得\n$response = @file_get_contents($url);\n\nif ($response === FALSE) {\n    echo json_encode(array('error' =\u003e '天気情報が取得できません'));\n    exit;\n}\n\n$data = json_decode($response, true);\n\nif ($data['cod'] !== 200) {\n    echo json_encode(array('error' =\u003e 'エラー: ' . $data['message']));\n    exit;\n}\n\n// 2. 必要なデータの抽出\n$temp = round($data['main']['temp']);\n$description = $data['weather'][0]['description'];\n$icon = $data['weather'][0]['icon'];\n\n// 3. 表示用のHTML生成\n$html = '\u003cdiv class=\"weather-box\" style=\"border: 1px solid #ccc; padding: 15px; border-radius: 10px; background: #fff; max-width: 300px; margin: 20px 0; text-align: center;\"\u003e';\n$html .= '\u003cp style=\"margin: 0; font-size: 0.9em; color: #666;\"\u003e川越市の現在の天気\u003c/p\u003e';\n$html .= '\u003cimg src=\"[https://openweathermap.org/img/wn/](https://openweathermap.org/img/wn/)' . $icon . '@2x.png\" style=\"width: 80px; margin: -10px 0;\"\u003e';\n$html .= '\u003cdiv style=\"font-size: 1.2em; font-weight: bold;\"\u003e' . $description . ' / ' . $temp . '℃\u003c/div\u003e';\n$html .= '\u003c/div\u003e';\n\n// 4. 結果をJSON形式で出力\necho json_encode(array('html' =\u003e $html));\n\n```\n\n## 手順2：WordPress側の設定（functions.php）\n\n手順1で作ったPHPファイルを呼び出すための「橋渡し」を `functions.php` に記述します。\n\n### 実装コード\n\nテーマの `functions.php` に以下のコードを追加します。\n\n```php\nfunction kawagoe_weather_shortcode() {\n    // 手順1で設置したファイルのURL（自分のサイトのドメインに書き換えてください）\n    $api_url = '[https://example.com/api/kawagoe_weather.php](https://example.com/api/kawagoe_weather.php)';\n    \n    // WordPressの関数を使って内部通信を行う\n    // 自分のサイト内なので sslverify は false でも問題ありません\n    $response = wp_remote_get($api_url, array('sslverify' =\u003e false));\n\n    if (is_wp_error($response)) {\n        return '天気情報の取得に失敗しました（通信エラー）';\n    }\n\n    $body = wp_remote_retrieve_body($response);\n    $data = json_decode($body, true);\n\n    // JSONからHTMLを取り出して出力する\n    if (isset($data['html'])) {\n        return $data['html'];\n    } else {\n        return 'データの読み込みに失敗しました';\n    }\n}\n// ショートコード名 [kawagoe_weather] を登録\nadd_shortcode('kawagoe_weather', 'kawagoe_weather_shortcode');\n\n```\n\n## 手順3：記事への表示\n\nあとは、WordPressのブロックエディタやクラシックエディタで、表示したい場所にショートコードを記述するだけです。\n[kawagoe_weather]\n\n## 実際の表示例\n\n今回解説した手法を用いて、実際に天気を表示させているページがこちらです。表示速度やデザインの参考にしてください。\n\n**[川越市の現在の天気（実装サンプル）](https://www.cibalabo.site/c1/sokonashi1/)**\n\n---\n## まとめ\n\nこの手法のメリットは以下の通りです。\n\n1. **管理の分離:** `functions.php` がスッキリし、APIロジックの修正が容易になる。\n2. **再利用性:** 今回作ったPHPファイルは、WordPress以外のサイトやJavaScript（Fetch API）からも利用できる。\n3. **安全性:** APIキーをテーマファイル内に持たせないため、セキュリティリスクを軽減できる。\n\nWordPressを単独のサイトとしてだけでなく、「APIサーバー」の入り口として活用する第一歩として、ぜひ試してみてください。\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:47:53+09:00","group":null,"id":"8d314fb1b5a52a3e066a","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"PHP","versions":[]},{"name":"WordPress","versions":[]},{"name":"API","versions":[]},{"name":"初心者","versions":[]},{"name":"OpenWeatherMap","versions":[]}],"title":"【WordPress】外部API処理をfunctions.phpから切り離して安全に実装する方法（OpenWeatherMapの例）","updated_at":"2026-05-06T10:47:53+09:00","url":"https://qiita.com/chiba_labo/items/8d314fb1b5a52a3e066a","user":{"description":"こんにちは、ちばらぼです。身の周りの不思議について検証したり面白いソフトウェアを自作し、報告するサイトを運営しております。ここではExcelでVBAを使って作成したアニメーション、サーバでプログラミングして提供しているゲームやアプリについてその仕組みを紹介させていただきます。最近はChatGPTとのAPI通信を使ったものを作ったりしています。よかったらご覧ください。","facebook_id":"","followees_count":0,"followers_count":0,"github_login_name":null,"id":"chiba_labo","items_count":1,"linkedin_id":"","location":"","name":"らぼ ちば","organization":"","permanent_id":1466604,"profile_image_url":"https://lh3.googleusercontent.com/a/AATXAJxQcTBm3XsqCpnlcxTL7uLsIj6KRc2xA3g-ZsL6=s50-mo","team_only":false,"twitter_screen_name":"Mc83412395","website_url":"https://www.cibalabo.site/"},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:93\"\u003e\n\u003cspan id=\"railsの非同期ioベースwebサーバーfalconで見えてきたrails-apiの可能性\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#rails%E3%81%AE%E9%9D%9E%E5%90%8C%E6%9C%9Fio%E3%83%99%E3%83%BC%E3%82%B9web%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BCfalcon%E3%81%A7%E8%A6%8B%E3%81%88%E3%81%A6%E3%81%8D%E3%81%9Frails-api%E3%81%AE%E5%8F%AF%E8%83%BD%E6%80%A7\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRailsの非同期I/OベースWebサーバー(Falcon)で見えてきたRails APIの可能性\u003c/h1\u003e\n\u003cp data-sourcepos=\"3:1-3:359\"\u003eマイクロサービスの技術選定でRailsが話題に上がると、I/O待ちの多い構成でのスループットを理由に選択肢から外れることがあります。RubyKaigi 2026でShopifyの発表を聞いたことをきっかけに、その前提を自分で確かめてみました。この記事はその実験と考察の記録です。\u003c/p\u003e\n\u003cp data-sourcepos=\"5:1-5:242\"\u003e\u003cstrong\u003ePumaとFalconのスループット差を3パターンの実験で比較し、Railsマイクロサービスへの示唆を考えます\u003c/strong\u003e。希望的観測を含む個人的な考察として読んでいただけるとありがたいです。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"7:1-7:24\"\u003e\n\u003cspan id=\"この記事の結論\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E7%B5%90%E8%AB%96\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eこの記事の結論\u003c/h2\u003e\n\u003cul data-sourcepos=\"9:1-12:0\"\u003e\n\u003cli data-sourcepos=\"9:1-9:143\"\u003e実際のAPIに近い複合I/Oの条件では、WebサーバーをFalconに変えるだけでPumaの\u003cstrong\u003e約4倍のスループット\u003c/strong\u003eが出た\u003c/li\u003e\n\u003cli data-sourcepos=\"10:1-10:96\"\u003e\n\u003ccode\u003eNet::HTTP\u003c/code\u003e を使った既存のコードはそのまま動く。\u003cstrong\u003eコード変更は不要\u003c/strong\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"11:1-12:0\"\u003e外部API連携・BFF・Webhookなど、I/Oバウンドはマイクロサービスでむしろ典型的なユースケース。Railsが現実的な選択肢になりうる土壌が整いつつある\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"13:1-13:67\"\u003e\u003cstrong\u003eただし、この結論には以下の前提があります。\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"15:1-18:0\"\u003e\n\u003cli data-sourcepos=\"15:1-15:91\"\u003eDBはPostgreSQLが前提。MySQLには対応する非同期ドライバが存在しない\u003c/li\u003e\n\u003cli data-sourcepos=\"16:1-16:92\"\u003e今回はローカル単一ノードでの計測。本番環境では別途検証が必要\u003c/li\u003e\n\u003cli data-sourcepos=\"17:1-18:0\"\u003e新規マイクロサービスでRails+Falconを採用する場合でも、Rack互換・gem依存の確認が必要\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"19:1-19:60\"\u003e詳細な実験データと考察は以下に続きます。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"21:1-21:62\"\u003e\n\u003cspan id=\"railsがマイクロサービスで選ばれにくい背景\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#rails%E3%81%8C%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A7%E9%81%B8%E3%81%B0%E3%82%8C%E3%81%AB%E3%81%8F%E3%81%84%E8%83%8C%E6%99%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRailsがマイクロサービスで選ばれにくい背景\u003c/h2\u003e\n\u003cp data-sourcepos=\"23:1-23:101\"\u003eマイクロサービスの話になると、Railsが選択肢から外れることが多いです。\u003c/p\u003e\n\u003cp data-sourcepos=\"25:1-25:266\"\u003eRailsのデフォルトサーバーであるPumaはスレッドベースのモデルを採用しています。外部APIの呼び出しやDBアクセスの待ち時間中も、スレッドがブロックされたまま他のリクエストを処理できません。\u003c/p\u003e\n\u003cp data-sourcepos=\"27:1-27:168\"\u003e結果として、スループットを上げるためにプロセスやインフラを積む必要があり、コスト効率の面で不利とされてきました。\u003c/p\u003e\n\u003cp data-sourcepos=\"29:1-29:340\"\u003eFalconはこの問題を、\u003cstrong\u003e非同期I/Oによる並行処理\u003c/strong\u003eで解決します。I/O待ち中にスレッドをブロックせず、Fiber Schedulerが処理を切り替えることで他のリクエストを並行して進めます。では、その効果が実際のAPIでどの程度出るのか。実験で確かめてみました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"31:1-31:57\"\u003e\n\u003cspan id=\"rubykaigi-2026のshopify事例がきっかけだった\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#rubykaigi-2026%E3%81%AEshopify%E4%BA%8B%E4%BE%8B%E3%81%8C%E3%81%8D%E3%81%A3%E3%81%8B%E3%81%91%E3%81%A0%E3%81%A3%E3%81%9F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRubyKaigi 2026のShopify事例がきっかけだった\u003c/h2\u003e\n\u003cp data-sourcepos=\"33:1-33:96\"\u003eRubyKaigi 2026で、Falconの作者Samuel WilliamsとShopifyのチームが登壇しました。\u003c/p\u003e\n\u003cp data-sourcepos=\"35:1-35:385\"\u003eセッションタイトルは\u003ca href=\"https://rubykaigi.org/2026/presentations/ioquatix.html\" rel=\"nofollow noopener\" target=\"_blank\"\u003e「Surviving Black Friday: 329 billion requests with Falcon!」\u003c/a\u003e。Shopifyがストアフロントのインフラを\u003cstrong\u003eFalcon（非同期I/OベースのRuby製Webサーバー）\u003c/strong\u003e に移行し、Black Friday 2025を\u003cstrong\u003e8500万リクエスト/分・ドロップゼロ\u003c/strong\u003eで乗り切った話でした。\u003c/p\u003e\n\u003cp data-sourcepos=\"37:1-37:455\"\u003eShopifyが移行を決断した背景として語っていたのは、「外部決済APIや在庫サービスへの大量のHTTPリクエストが発生するワークロードで、スレッドブロッキングがボトルネックになっていた」という点でした。I/O待ちの多い構成でFalconの非同期I/Oが有効に働くという話は、マイクロサービスでよく見るワークロードそのものだと感じました。\u003c/p\u003e\n\u003cp data-sourcepos=\"39:1-39:99\"\u003eこの話を聞いて、実際に自分でも実験をして確かめてみたくなりました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"41:1-41:62\"\u003e\n\u003cspan id=\"実験設計pumaとfalconを3パターンで比較した\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E9%A8%93%E8%A8%AD%E8%A8%88puma%E3%81%A8falcon%E3%82%923%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%A7%E6%AF%94%E8%BC%83%E3%81%97%E3%81%9F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実験設計：PumaとFalconを3パターンで比較した\u003c/h2\u003e\n\u003cp data-sourcepos=\"43:1-43:109\"\u003e同一のRails APIをPumaとFalconで動かし、スループットとレイテンシを計測しました。\u003c/p\u003e\n\u003ctable data-sourcepos=\"45:1-52:72\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"45:1-45:19\"\u003e\n\u003cth data-sourcepos=\"45:2-45:9\"\u003e項目\u003c/th\u003e\n\u003cth data-sourcepos=\"45:11-45:18\"\u003e内容\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"47:1-47:16\"\u003e\n\u003ctd data-sourcepos=\"47:2-47:7\"\u003eRuby\u003c/td\u003e\n\u003ctd data-sourcepos=\"47:9-47:15\"\u003e4.0.1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"48:1-48:30\"\u003e\n\u003ctd data-sourcepos=\"48:2-48:8\"\u003eRails\u003c/td\u003e\n\u003ctd data-sourcepos=\"48:10-48:29\"\u003e8.1.3 APIモード\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"49:1-49:22\"\u003e\n\u003ctd data-sourcepos=\"49:2-49:5\"\u003eDB\u003c/td\u003e\n\u003ctd data-sourcepos=\"49:7-49:21\"\u003ePostgreSQL 17\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"50:1-50:47\"\u003e\n\u003ctd data-sourcepos=\"50:2-50:18\"\u003e負荷ツール\u003c/td\u003e\n\u003ctd data-sourcepos=\"50:20-50:46\"\u003ewrk（-t4 -c1000 -d30s）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"51:1-51:79\"\u003e\n\u003ctd data-sourcepos=\"51:2-51:21\"\u003eプロセス構成\u003c/td\u003e\n\u003ctd data-sourcepos=\"51:23-51:78\"\u003e1プロセス（Phase 1）/ 4プロセス（Phase 2）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"52:1-52:72\"\u003e\n\u003ctd data-sourcepos=\"52:2-52:15\"\u003e実行環境\u003c/td\u003e\n\u003ctd data-sourcepos=\"52:17-52:71\"\u003emacOS（ローカル単一ノード）、Apple M3 Max\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"54:1-54:90\"\u003e実験の前提として、PumaとFalconではDB接続の管理方法が異なります。\u003c/p\u003e\n\u003cp data-sourcepos=\"56:1-56:168\"\u003ePumaはスレッド数がそのままDB pool数の上限として機能します。スレッドが増えすぎないため、pool枯渇が起きにくい構造です。\u003c/p\u003e\n\u003cp data-sourcepos=\"58:1-58:267\"\u003eFalconは並行処理を無制限に生成できるため、制限なしでは全リクエストが同時にDB接続を取得しようとしてpoolが枯渇します。そのため\u003ccode\u003eAsync::Semaphore\u003c/code\u003eで同時接続数を明示的に制限する必要があります。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"ruby\" data-sourcepos=\"60:1-66:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e# config/initializers/db_semaphore.rb\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# Falcon起動時のみDB同時接続数を制限するセマフォを定義する\u003c/span\u003e\n\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"k\"\u003edefined?\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"no\"\u003eAsync\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eSemaphore\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"no\"\u003eDB_SEMAPHORE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eAsync\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eSemaphore\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003enew\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"no\"\u003eENV\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003efetch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\"DB_CONCURRENCY\"\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003eto_i\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"ruby\" data-sourcepos=\"68:1-74:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e# app/controllers/application_controller.rb\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# DB_SEMAPHOREの有無をPuma/Falcon両対応で吸収する\u003c/span\u003e\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ewith_db_semaphore\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eblock\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"k\"\u003edefined?\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"no\"\u003eDB_SEMAPHORE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"no\"\u003eDB_SEMAPHORE\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eacquire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eblock\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eblock\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ecall\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"76:1-76:278\"\u003e\u003ccode\u003ewith_db_semaphore\u003c/code\u003eはPuma環境では\u003ccode\u003eDB_SEMAPHORE\u003c/code\u003eが定義されないためブロックをそのまま実行し、Falcon環境ではセマフォで同時接続数を制限します。パターンCのコードでこのヘルパーを使っているのはこのためです。\u003c/p\u003e\n\u003cp data-sourcepos=\"78:1-78:55\"\u003e比較した3パターンは以下のとおりです。\u003c/p\u003e\n\u003cul data-sourcepos=\"80:1-83:0\"\u003e\n\u003cli data-sourcepos=\"80:1-80:75\"\u003e\n\u003cstrong\u003eパターンA\u003c/strong\u003e：単純なDBアクセスのみ（ベースライン）\u003c/li\u003e\n\u003cli data-sourcepos=\"81:1-81:168\"\u003e\n\u003cstrong\u003eパターンB\u003c/strong\u003e：\u003ccode\u003esleep(0.5s)\u003c/code\u003e による簡易I/O待ちの原理確認。Falconの効果がなぜ出るかをシンプルに示すための人工的なパターン\u003c/li\u003e\n\u003cli data-sourcepos=\"82:1-83:0\"\u003e\n\u003cstrong\u003eパターンC\u003c/strong\u003e：実際のマイクロサービスAPIを模した実用検証。\u003ccode\u003eNet::HTTP\u003c/code\u003e による外部HTTPリクエスト（300ms × 2回）＋DBクエリ5本を直列実行。\u003cstrong\u003eこのパターンの結果がメインの結論\u003c/strong\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"84:1-84:54\"\u003e\n\u003cspan id=\"結果io待ちがあると劇的な差が出た\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%B5%90%E6%9E%9Cio%E5%BE%85%E3%81%A1%E3%81%8C%E3%81%82%E3%82%8B%E3%81%A8%E5%8A%87%E7%9A%84%E3%81%AA%E5%B7%AE%E3%81%8C%E5%87%BA%E3%81%9F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e結果：I/O待ちがあると劇的な差が出た\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"86:1-86:67\"\u003e\n\u003cspan id=\"1-パターンa単純dbアクセスベースライン\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3a%E5%8D%98%E7%B4%94db%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%99%E3%83%BC%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%B3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. パターンA（単純DBアクセス）：ベースライン\u003c/h3\u003e\n\u003cp data-sourcepos=\"88:1-88:134\"\u003eエンドポイントはシンプルなUser取得です。\u003ccode\u003ebefore_action\u003c/code\u003e でDBからユーザーを取得してJSONを返します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"ruby\" data-sourcepos=\"90:1-95:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e# GET /users/:id\u003c/span\u003e\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eshow\u003c/span\u003e\n  \u003cspan class=\"n\"\u003erender\u003c/span\u003e \u003cspan class=\"ss\"\u003ejson: \u003c/span\u003e\u003cspan class=\"vi\"\u003e@user\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# before_action :set_user で取得済み\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ctable data-sourcepos=\"97:1-100:78\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"97:1-97:32\"\u003e\n\u003cth data-sourcepos=\"97:2-97:9\"\u003e構成\u003c/th\u003e\n\u003cth data-sourcepos=\"97:11-97:16\"\u003ePuma\u003c/th\u003e\n\u003cth data-sourcepos=\"97:18-97:25\"\u003eFalcon\u003c/th\u003e\n\u003cth data-sourcepos=\"97:27-97:31\"\u003e差\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"99:1-99:74\"\u003e\n\u003ctd data-sourcepos=\"99:2-99:34\"\u003e1プロセス × 20スレッド\u003c/td\u003e\n\u003ctd data-sourcepos=\"99:36-99:46\"\u003e339 req/s\u003c/td\u003e\n\u003ctd data-sourcepos=\"99:48-99:58\"\u003e338 req/s\u003c/td\u003e\n\u003ctd data-sourcepos=\"99:60-99:73\"\u003eほぼ同等\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"100:1-100:78\"\u003e\n\u003ctd data-sourcepos=\"100:2-100:34\"\u003e4プロセス × 20スレッド\u003c/td\u003e\n\u003ctd data-sourcepos=\"100:36-100:48\"\u003e1,005 req/s\u003c/td\u003e\n\u003ctd data-sourcepos=\"100:50-100:66\"\u003e\u003cstrong\u003e1,627 req/s\u003c/strong\u003e\u003c/td\u003e\n\u003ctd data-sourcepos=\"100:68-100:77\"\u003e\u003cstrong\u003e+62%\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"102:1-102:116\"\u003e1プロセス構成ではほぼ差がありません。4プロセスに増やすとFalconが62%上回りました。\u003c/p\u003e\n\u003cp data-sourcepos=\"104:1-104:185\"\u003eDBクエリ数ms程度の短いI/O待ちでも、4プロセス構成ではFalconが上回ります。ただし差は限定的で、I/O待ち時間が長いほど差は広がります。\u003c/p\u003e\n\u003cp data-sourcepos=\"106:1-106:69\"\u003e次に、I/O待ちを加えたケースで原理を確認します。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"108:1-108:86\"\u003e\n\u003cspan id=\"2-パターンb原理確認仕組みを理解するための人工実験\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3b%E5%8E%9F%E7%90%86%E7%A2%BA%E8%AA%8D%E4%BB%95%E7%B5%84%E3%81%BF%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E4%BA%BA%E5%B7%A5%E5%AE%9F%E9%A8%93\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. パターンB（原理確認）：仕組みを理解するための人工実験\u003c/h3\u003e\n\u003cp data-sourcepos=\"110:1-110:219\"\u003e\u003ccode\u003esleep(0.5s)\u003c/code\u003e で外部API待ちを簡易的に再現したパターンです。実際の外部接続は使わず、シンプルなI/O待ちのケースでPumaとFalconの性能差を確認することが目的です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"ruby\" data-sourcepos=\"112:1-122:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e# GET /users/:id/pattern_b\u003c/span\u003e\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003epattern_b\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"no\"\u003eAsync\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eTask\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ecurrent?\u003c/span\u003e\n    \u003cspan class=\"no\"\u003eAsync\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eTask\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ecurrent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003esleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n    \u003cspan class=\"nb\"\u003esleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n  \u003cspan class=\"n\"\u003erender\u003c/span\u003e \u003cspan class=\"ss\"\u003ejson: \u003c/span\u003e\u003cspan class=\"vi\"\u003e@user\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ctable data-sourcepos=\"124:1-127:85\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"124:1-124:32\"\u003e\n\u003cth data-sourcepos=\"124:2-124:9\"\u003e構成\u003c/th\u003e\n\u003cth data-sourcepos=\"124:11-124:16\"\u003ePuma\u003c/th\u003e\n\u003cth data-sourcepos=\"124:18-124:25\"\u003eFalcon\u003c/th\u003e\n\u003cth data-sourcepos=\"124:27-124:31\"\u003e差\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"126:1-126:83\"\u003e\n\u003ctd data-sourcepos=\"126:2-126:34\"\u003e1プロセス × 20スレッド\u003c/td\u003e\n\u003ctd data-sourcepos=\"126:36-126:48\"\u003e34.68 req/s\u003c/td\u003e\n\u003ctd data-sourcepos=\"126:50-126:67\"\u003e\u003cstrong\u003e349.14 req/s\u003c/strong\u003e\u003c/td\u003e\n\u003ctd data-sourcepos=\"126:69-126:82\"\u003e\u003cstrong\u003e約10倍\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"127:1-127:85\"\u003e\n\u003ctd data-sourcepos=\"127:2-127:34\"\u003e4プロセス × 20スレッド\u003c/td\u003e\n\u003ctd data-sourcepos=\"127:36-127:49\"\u003e150.15 req/s\u003c/td\u003e\n\u003ctd data-sourcepos=\"127:51-127:70\"\u003e\u003cstrong\u003e1,375.87 req/s\u003c/strong\u003e\u003c/td\u003e\n\u003ctd data-sourcepos=\"127:72-127:84\"\u003e\u003cstrong\u003e約9倍\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"129:1-129:129\"\u003ePumaの場合、スレッドがsleep中にブロックされるため処理できるリクエスト数に上限があります。\u003c/p\u003e\n\u003ctable data-sourcepos=\"131:1-134:57\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"131:1-131:22\"\u003e\n\u003cth data-sourcepos=\"131:2-131:3\"\u003e\u003c/th\u003e\n\u003cth data-sourcepos=\"131:5-131:15\"\u003e計算式\u003c/th\u003e\n\u003cth data-sourcepos=\"131:17-131:21\"\u003e値\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"133:1-133:57\"\u003e\n\u003ctd data-sourcepos=\"133:2-133:20\"\u003ePuma 理論上限\u003c/td\u003e\n\u003ctd data-sourcepos=\"133:22-133:45\"\u003e20スレッド ÷ 0.5s\u003c/td\u003e\n\u003ctd data-sourcepos=\"133:47-133:56\"\u003e40 req/s\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"134:1-134:57\"\u003e\n\u003ctd data-sourcepos=\"134:2-134:36\"\u003ePuma 実測値（1プロセス）\u003c/td\u003e\n\u003ctd data-sourcepos=\"134:38-134:42\"\u003e—\u003c/td\u003e\n\u003ctd data-sourcepos=\"134:44-134:56\"\u003e34.68 req/s\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"136:1-136:159\"\u003e理論値と実測値がほぼ一致しており、スレッド数の壁がそのままスループットの天井になっていることがわかります。\u003c/p\u003e\n\u003cp data-sourcepos=\"138:1-138:181\"\u003eFalconはsleep中に非同期I/Oで処理を切り替えるため、1000接続が並行して待機・処理されます。4プロセス構成の詳細は以下のとおりです。\u003c/p\u003e\n\u003ctable data-sourcepos=\"140:1-145:31\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"140:1-140:115\"\u003e\n\u003cth data-sourcepos=\"140:2-140:9\"\u003e指標\u003c/th\u003e\n\u003cth data-sourcepos=\"140:11-140:53\"\u003ePuma（4プロセス × 20スレッド）\u003c/th\u003e\n\u003cth data-sourcepos=\"140:55-140:114\"\u003eFalcon（4プロセス × DB接続上限20/プロセス）\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"142:1-142:35\"\u003e\n\u003ctd data-sourcepos=\"142:2-142:10\"\u003eReq/sec\u003c/td\u003e\n\u003ctd data-sourcepos=\"142:12-142:19\"\u003e150.15\u003c/td\u003e\n\u003ctd data-sourcepos=\"142:21-142:34\"\u003e\u003cstrong\u003e1,375.87\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"143:1-143:35\"\u003e\n\u003ctd data-sourcepos=\"143:2-143:14\"\u003eLatency Avg\u003c/td\u003e\n\u003ctd data-sourcepos=\"143:16-143:22\"\u003e5.87s\u003c/td\u003e\n\u003ctd data-sourcepos=\"143:24-143:34\"\u003e\u003cstrong\u003e706ms\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"144:1-144:35\"\u003e\n\u003ctd data-sourcepos=\"144:2-144:14\"\u003eLatency Max\u003c/td\u003e\n\u003ctd data-sourcepos=\"144:16-144:22\"\u003e7.20s\u003c/td\u003e\n\u003ctd data-sourcepos=\"144:24-144:34\"\u003e\u003cstrong\u003e1.32s\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"145:1-145:31\"\u003e\n\u003ctd data-sourcepos=\"145:2-145:12\"\u003e成功率\u003c/td\u003e\n\u003ctd data-sourcepos=\"145:14-145:19\"\u003e100%\u003c/td\u003e\n\u003ctd data-sourcepos=\"145:21-145:30\"\u003e\u003cstrong\u003e100%\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"147:1-147:126\"\u003eあくまで私個人の感想ですが、0.5秒の待ちがこれほどの差を生むとは思っていませんでした。\u003c/p\u003e\n\u003cp data-sourcepos=\"149:1-149:147\"\u003eこれはsleepという人工的な条件での結果です。実際のAPIに近い条件ではどうなるか。パターンCで確認します。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"151:1-151:80\"\u003e\n\u003cspan id=\"3-パターンc実用検証実際のapiに近い条件での結果\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3c%E5%AE%9F%E7%94%A8%E6%A4%9C%E8%A8%BC%E5%AE%9F%E9%9A%9B%E3%81%AEapi%E3%81%AB%E8%BF%91%E3%81%84%E6%9D%A1%E4%BB%B6%E3%81%A7%E3%81%AE%E7%B5%90%E6%9E%9C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. パターンC（実用検証）：実際のAPIに近い条件での結果\u003c/h3\u003e\n\u003cp data-sourcepos=\"153:1-154:108\"\u003eパターンBがsleepによる簡易な擬似だったのに対し、パターンCは実際のマイクロサービスに近い構成です。\u003ccode\u003eNet::HTTP\u003c/code\u003e で外部サーバーへのHTTPリクエスト（300ms × 2回）と、DBクエリ5本をすべて直列で処理します。\u003cbr\u003e\n外部サーバーはDockerで起動した\u003cstrong\u003ehttpbin\u003c/strong\u003eを利用して、HTTP通信を再現しています。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"ruby\" data-sourcepos=\"156:1-181:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"c1\"\u003e# GET /users/:id/pattern_c\u003c/span\u003e\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003epattern_c\u003c/span\u003e\n  \u003cspan class=\"n\"\u003euser\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewith_db_semaphore\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"no\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparams\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n  \u003cspan class=\"n\"\u003eposts\u003c/span\u003e    \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewith_db_semaphore\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"no\"\u003ePost\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003euser_id: \u003c/span\u003e\u003cspan class=\"n\"\u003eparams\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"p\"\u003e]).\u003c/span\u003e\u003cspan class=\"nf\"\u003eorder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003ecreated_at: :desc\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003eto_a\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n  \u003cspan class=\"n\"\u003ecomments\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewith_db_semaphore\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"no\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003euser_id: \u003c/span\u003e\u003cspan class=\"n\"\u003eparams\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"p\"\u003e]).\u003c/span\u003e\u003cspan class=\"nf\"\u003eorder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003ecreated_at: :desc\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003eto_a\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n  \u003cspan class=\"n\"\u003estats\u003c/span\u003e    \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ewith_db_semaphore\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n      \u003cspan class=\"ss\"\u003epost_count:    \u003c/span\u003e\u003cspan class=\"no\"\u003ePost\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003euser_id: \u003c/span\u003e\u003cspan class=\"n\"\u003eparams\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"p\"\u003e]).\u003c/span\u003e\u003cspan class=\"nf\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n      \u003cspan class=\"ss\"\u003ecomment_count: \u003c/span\u003e\u003cspan class=\"no\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003euser_id: \u003c/span\u003e\u003cspan class=\"n\"\u003eparams\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"p\"\u003e]).\u003c/span\u003e\u003cspan class=\"nf\"\u003ecount\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n  \u003cspan class=\"n\"\u003euri\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eURI\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\"http://localhost:8888/delay/0.3\"\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n  \u003cspan class=\"n\"\u003eexternals\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n  \u003cspan class=\"n\"\u003efetch_count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n  \u003cspan class=\"k\"\u003ewhile\u003c/span\u003e \u003cspan class=\"n\"\u003efetch_count\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\n    \u003cspan class=\"n\"\u003eexternals\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"no\"\u003eNet\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eHTTP\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euri\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ehost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003euri\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eport\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ehttp\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n      \u003cspan class=\"no\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eparse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euri\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003erequest_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n    \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n    \u003cspan class=\"n\"\u003efetch_count\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n  \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n  \u003cspan class=\"n\"\u003erender\u003c/span\u003e \u003cspan class=\"ss\"\u003ejson: \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:,\u003c/span\u003e \u003cspan class=\"n\"\u003eposts\u003c/span\u003e\u003cspan class=\"p\"\u003e:,\u003c/span\u003e \u003cspan class=\"n\"\u003ecomments\u003c/span\u003e\u003cspan class=\"p\"\u003e:,\u003c/span\u003e \u003cspan class=\"n\"\u003estats\u003c/span\u003e\u003cspan class=\"p\"\u003e:,\u003c/span\u003e \u003cspan class=\"ss\"\u003eexternals: \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"183:1-183:300\"\u003e1リクエストあたりの外部HTTP待ちは合計600ms（300ms × 2回）です。直列なので1リクエスト自体が速くなるわけではありません。しかし、Falconは各I/O待ちのたびに非同期で処理を切り替え、他のリクエストを並行して進めます。\u003c/p\u003e\n\u003cp data-sourcepos=\"185:1-185:327\"\u003eこのAPIは1リクエストあたり外部HTTP 600ms＋DBクエリで合計700ms前後かかります。wrkのtimeout 2sはAPIの処理時間に対して余裕が少なく、高負荷時にタイムアウトが発生しやすい条件です。そのため、より公平な比較としてtimeout 10sでも計測しました。\u003c/p\u003e\n\u003cp data-sourcepos=\"187:1-187:14\"\u003e\u003cstrong\u003etimeout 2s\u003c/strong\u003e\u003c/p\u003e\n\u003ctable data-sourcepos=\"189:1-195:31\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"189:1-189:115\"\u003e\n\u003cth data-sourcepos=\"189:2-189:9\"\u003e指標\u003c/th\u003e\n\u003cth data-sourcepos=\"189:11-189:53\"\u003ePuma（4プロセス × 20スレッド）\u003c/th\u003e\n\u003cth data-sourcepos=\"189:55-189:114\"\u003eFalcon（4プロセス × DB接続上限20/プロセス）\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"191:1-191:46\"\u003e\n\u003ctd data-sourcepos=\"191:2-191:10\"\u003eReq/sec\u003c/td\u003e\n\u003ctd data-sourcepos=\"191:12-191:19\"\u003e125.33\u003c/td\u003e\n\u003ctd data-sourcepos=\"191:21-191:45\"\u003e\u003cstrong\u003e495.78（約4倍）\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"192:1-192:58\"\u003e\n\u003ctd data-sourcepos=\"192:2-192:36\"\u003e完了リクエスト数（30s）\u003c/td\u003e\n\u003ctd data-sourcepos=\"192:38-192:44\"\u003e3,767\u003c/td\u003e\n\u003ctd data-sourcepos=\"192:46-192:57\"\u003e\u003cstrong\u003e14,897\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"193:1-193:78\"\u003e\n\u003ctd data-sourcepos=\"193:2-193:21\"\u003e成功率（2xx）\u003c/td\u003e\n\u003ctd data-sourcepos=\"193:23-193:48\"\u003e50.9%（3,740 / 7,345）\u003c/td\u003e\n\u003ctd data-sourcepos=\"193:50-193:77\"\u003e73.2%（14,897 / 20,338）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"194:1-194:31\"\u003e\n\u003ctd data-sourcepos=\"194:2-194:14\"\u003eLatency Avg\u003c/td\u003e\n\u003ctd data-sourcepos=\"194:16-194:22\"\u003e1.18s\u003c/td\u003e\n\u003ctd data-sourcepos=\"194:24-194:30\"\u003e1.56s\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"195:1-195:31\"\u003e\n\u003ctd data-sourcepos=\"195:2-195:14\"\u003eLatency Max\u003c/td\u003e\n\u003ctd data-sourcepos=\"195:16-195:22\"\u003e2.00s\u003c/td\u003e\n\u003ctd data-sourcepos=\"195:24-195:30\"\u003e2.00s\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"197:1-197:15\"\u003e\u003cstrong\u003etimeout 10s\u003c/strong\u003e\u003c/p\u003e\n\u003ctable data-sourcepos=\"199:1-205:35\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"199:1-199:115\"\u003e\n\u003cth data-sourcepos=\"199:2-199:9\"\u003e指標\u003c/th\u003e\n\u003cth data-sourcepos=\"199:11-199:53\"\u003ePuma（4プロセス × 20スレッド）\u003c/th\u003e\n\u003cth data-sourcepos=\"199:55-199:114\"\u003eFalcon（4プロセス × DB接続上限20/プロセス）\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"201:1-201:46\"\u003e\n\u003ctd data-sourcepos=\"201:2-201:10\"\u003eReq/sec\u003c/td\u003e\n\u003ctd data-sourcepos=\"201:12-201:19\"\u003e123.39\u003c/td\u003e\n\u003ctd data-sourcepos=\"201:21-201:45\"\u003e\u003cstrong\u003e487.36（約4倍）\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"202:1-202:58\"\u003e\n\u003ctd data-sourcepos=\"202:2-202:36\"\u003e完了リクエスト数（30s）\u003c/td\u003e\n\u003ctd data-sourcepos=\"202:38-202:44\"\u003e3,708\u003c/td\u003e\n\u003ctd data-sourcepos=\"202:46-202:57\"\u003e\u003cstrong\u003e14,641\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"203:1-203:78\"\u003e\n\u003ctd data-sourcepos=\"203:2-203:21\"\u003e成功率（2xx）\u003c/td\u003e\n\u003ctd data-sourcepos=\"203:23-203:48\"\u003e99.7%（3,696 / 3,708）\u003c/td\u003e\n\u003ctd data-sourcepos=\"203:50-203:77\"\u003e99.6%（14,641 / 14,693）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"204:1-204:35\"\u003e\n\u003ctd data-sourcepos=\"204:2-204:14\"\u003eLatency Avg\u003c/td\u003e\n\u003ctd data-sourcepos=\"204:16-204:22\"\u003e6.89s\u003c/td\u003e\n\u003ctd data-sourcepos=\"204:24-204:34\"\u003e\u003cstrong\u003e1.83s\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"205:1-205:35\"\u003e\n\u003ctd data-sourcepos=\"205:2-205:14\"\u003eLatency Max\u003c/td\u003e\n\u003ctd data-sourcepos=\"205:16-205:22\"\u003e8.86s\u003c/td\u003e\n\u003ctd data-sourcepos=\"205:24-205:34\"\u003e\u003cstrong\u003e2.73s\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp data-sourcepos=\"207:1-207:424\"\u003eここで重要なのは、\u003cstrong\u003ePumaとFalconで全く同じコードのAPIを起動している\u003c/strong\u003e点です。\u003ccode\u003eNet::HTTP\u003c/code\u003e はFalconの非同期I/O機構（Fiber Scheduler）にフックされるため、whileループで直列に書かれた既存のHTTP呼び出しも、各イテレーション中にFalconが処理を切り替えます。既存のRailsコードをそのまま動かすだけで恩恵を受けられます。\u003c/p\u003e\n\u003cp data-sourcepos=\"209:1-209:294\"\u003e※ただし、すべてのI/O操作が完全にノンブロッキングになるわけではありません。ライブラリの実装によってはブロッキングI/Oが混在する可能性があります。本番導入前にgem依存を個別に確認することをお勧めします。\u003c/p\u003e\n\u003cp data-sourcepos=\"211:1-211:99\"\u003eでは、この結果はマイクロサービスの文脈でどう解釈すべきでしょうか。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"213:1-213:78\"\u003e\n\u003cspan id=\"マイクロサービスの典型的なワークロードに利用できる\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E5%85%B8%E5%9E%8B%E7%9A%84%E3%81%AA%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%89%E3%81%AB%E5%88%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eマイクロサービスの典型的なワークロードに利用できる\u003c/h2\u003e\n\u003cp data-sourcepos=\"215:1-215:170\"\u003eパターンCで差が出た「\u003cstrong\u003e外部HTTP＋複数DBクエリの直列処理\u003c/strong\u003e」は、マイクロサービスのユースケースそのものだと感じました。\u003c/p\u003e\n\u003cul data-sourcepos=\"217:1-222:0\"\u003e\n\u003cli data-sourcepos=\"217:1-217:53\"\u003e決済・金融系：外部決済API、与信照会\u003c/li\u003e\n\u003cli data-sourcepos=\"218:1-218:44\"\u003e通知系：SendGrid、Twilio、Push通知\u003c/li\u003e\n\u003cli data-sourcepos=\"219:1-219:44\"\u003e認証・認可系：外部IdP呼び出し\u003c/li\u003e\n\u003cli data-sourcepos=\"220:1-220:62\"\u003eデータ集約系：複数サービスのAPIを束ねるBFF\u003c/li\u003e\n\u003cli data-sourcepos=\"221:1-222:0\"\u003eWebhook処理：受信して外部に転送するもの\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"223:1-223:466\"\u003eRails APIモード + Falconであれば、ActiveRecordや認証・バリデーションといった既存のRailsエコシステムをそのまま活かした上で、I/OバウンドなAPIでも実用的な構成が取れます。すでにRailsで開発しているチームにとっては、言語やスタックを切り替えることなくマイクロサービスを現実的な選択肢として検討できるようになりつつある、という話です。\u003c/p\u003e\n\u003cp data-sourcepos=\"225:1-225:453\"\u003eもちろん、スループット以外にも考慮すべき点はあります。メモリ使用量・オブザーバビリティ（分散トレーシング、ログの集約）・コンテナ化との相性など、実運用では別途検証が必要な領域が残ります。この記事はあくまでスループットに絞った実験であり、「マイクロサービスとしてすべて解決済み」という意味ではありません。\u003c/p\u003e\n\u003cp data-sourcepos=\"227:1-227:119\"\u003eでは、実際にRailsをマイクロサービスで選ぶかどうか、どう判断すればよいでしょうか。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"229:1-229:63\"\u003e\n\u003cspan id=\"rails-api--falconが現実的な選択肢になるケース\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#rails-api--falcon%E3%81%8C%E7%8F%BE%E5%AE%9F%E7%9A%84%E3%81%AA%E9%81%B8%E6%8A%9E%E8%82%A2%E3%81%AB%E3%81%AA%E3%82%8B%E3%82%B1%E3%83%BC%E3%82%B9\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRails API + Falconが現実的な選択肢になるケース\u003c/h2\u003e\n\u003cp data-sourcepos=\"231:1-231:207\"\u003eスループット面でI/OバウンドなAPIにRails APIが不利とされてきた前提が、少なくともI/OバウンドなAPIに関してはかなり改善が見込めそうな実験結果でした。\u003c/p\u003e\n\u003cp data-sourcepos=\"233:1-233:141\"\u003eただし、すべての構成で有効かというと、そうではありません。条件によって向き・不向きがあります。\u003c/p\u003e\n\u003cp data-sourcepos=\"235:1-235:64\"\u003e\u003cstrong\u003eRails API + Falconが現実的な選択肢になるケース\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"237:1-242:0\"\u003e\n\u003cli data-sourcepos=\"237:1-237:56\"\u003eBFF（複数サービスのAPIを束ねる中間層）\u003c/li\u003e\n\u003cli data-sourcepos=\"238:1-238:62\"\u003e外部API集約（決済、通知、IdP呼び出しなど）\u003c/li\u003e\n\u003cli data-sourcepos=\"239:1-239:24\"\u003eWebhook受信・転送\u003c/li\u003e\n\u003cli data-sourcepos=\"240:1-240:59\"\u003eI/O待ち時間が支配的なエンドポイント全般\u003c/li\u003e\n\u003cli data-sourcepos=\"241:1-242:0\"\u003eすでにRailsを採用していて社内の知見がRailsで充分にあるチーム\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"243:1-243:49\"\u003e\u003cstrong\u003e他のスタックを検討すべきケース\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"245:1-247:0\"\u003e\n\u003cli data-sourcepos=\"245:1-245:62\"\u003eCPUバウンドな処理（画像処理、暗号化など）\u003c/li\u003e\n\u003cli data-sourcepos=\"246:1-247:0\"\u003e単純なCRUDが中心でI/O待ちが短いサービス（非同期I/Oの恩恵が少ない）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"248:1-248:58\"\u003e\u003cstrong\u003eRails+Falconを選ぶ前に確認が必要なケース\u003c/strong\u003e\u003c/p\u003e\n\u003cul data-sourcepos=\"250:1-251:0\"\u003e\n\u003cli data-sourcepos=\"250:1-251:0\"\u003eMySQL利用が前提のプロジェクト（非同期ドライバが存在しないため、PostgreSQLへの切り替えが前提になる）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"252:1-252:433\"\u003eまずは新規の小さなマイクロサービス、特にBFFや外部API集約のようなI/Oバウンドな用途から試してみるのが現実的な一歩だと思います。Shopifyの事例がなければ、自分でこの実験をしようとは思いませんでした。RubyKaigiで聞いた話を自分の手で検証し、体験として理解できたことは、自分にとって価値ある学習経験でした。\u003c/p\u003e\n\u003cp data-sourcepos=\"254:1-254:63\"\u003eここまでお読み頂き、ありがとうございます！\u003c/p\u003e\n","body":"# Railsの非同期I/OベースWebサーバー(Falcon)で見えてきたRails APIの可能性\n\nマイクロサービスの技術選定でRailsが話題に上がると、I/O待ちの多い構成でのスループットを理由に選択肢から外れることがあります。RubyKaigi 2026でShopifyの発表を聞いたことをきっかけに、その前提を自分で確かめてみました。この記事はその実験と考察の記録です。\n\n**PumaとFalconのスループット差を3パターンの実験で比較し、Railsマイクロサービスへの示唆を考えます**。希望的観測を含む個人的な考察として読んでいただけるとありがたいです。\n\n## この記事の結論\n\n- 実際のAPIに近い複合I/Oの条件では、WebサーバーをFalconに変えるだけでPumaの**約4倍のスループット**が出た\n- `Net::HTTP` を使った既存のコードはそのまま動く。**コード変更は不要**\n- 外部API連携・BFF・Webhookなど、I/Oバウンドはマイクロサービスでむしろ典型的なユースケース。Railsが現実的な選択肢になりうる土壌が整いつつある\n\n**ただし、この結論には以下の前提があります。**\n\n- DBはPostgreSQLが前提。MySQLには対応する非同期ドライバが存在しない\n- 今回はローカル単一ノードでの計測。本番環境では別途検証が必要\n- 新規マイクロサービスでRails+Falconを採用する場合でも、Rack互換・gem依存の確認が必要\n\n詳細な実験データと考察は以下に続きます。\n\n## Railsがマイクロサービスで選ばれにくい背景\n\nマイクロサービスの話になると、Railsが選択肢から外れることが多いです。\n\nRailsのデフォルトサーバーであるPumaはスレッドベースのモデルを採用しています。外部APIの呼び出しやDBアクセスの待ち時間中も、スレッドがブロックされたまま他のリクエストを処理できません。\n\n結果として、スループットを上げるためにプロセスやインフラを積む必要があり、コスト効率の面で不利とされてきました。\n\nFalconはこの問題を、**非同期I/Oによる並行処理**で解決します。I/O待ち中にスレッドをブロックせず、Fiber Schedulerが処理を切り替えることで他のリクエストを並行して進めます。では、その効果が実際のAPIでどの程度出るのか。実験で確かめてみました。\n\n## RubyKaigi 2026のShopify事例がきっかけだった\n\nRubyKaigi 2026で、Falconの作者Samuel WilliamsとShopifyのチームが登壇しました。\n\nセッションタイトルは[「Surviving Black Friday: 329 billion requests with Falcon!」](https://rubykaigi.org/2026/presentations/ioquatix.html)。Shopifyがストアフロントのインフラを**Falcon（非同期I/OベースのRuby製Webサーバー）** に移行し、Black Friday 2025を**8500万リクエスト/分・ドロップゼロ**で乗り切った話でした。\n\nShopifyが移行を決断した背景として語っていたのは、「外部決済APIや在庫サービスへの大量のHTTPリクエストが発生するワークロードで、スレッドブロッキングがボトルネックになっていた」という点でした。I/O待ちの多い構成でFalconの非同期I/Oが有効に働くという話は、マイクロサービスでよく見るワークロードそのものだと感じました。\n\nこの話を聞いて、実際に自分でも実験をして確かめてみたくなりました。\n\n## 実験設計：PumaとFalconを3パターンで比較した\n\n同一のRails APIをPumaとFalconで動かし、スループットとレイテンシを計測しました。\n\n| 項目 | 内容 |\n| --- | --- |\n| Ruby | 4.0.1 |\n| Rails | 8.1.3 APIモード |\n| DB | PostgreSQL 17 |\n| 負荷ツール | wrk（-t4 -c1000 -d30s） |\n| プロセス構成 | 1プロセス（Phase 1）/ 4プロセス（Phase 2） |\n| 実行環境 | macOS（ローカル単一ノード）、Apple M3 Max |\n\n実験の前提として、PumaとFalconではDB接続の管理方法が異なります。\n\nPumaはスレッド数がそのままDB pool数の上限として機能します。スレッドが増えすぎないため、pool枯渇が起きにくい構造です。\n\nFalconは並行処理を無制限に生成できるため、制限なしでは全リクエストが同時にDB接続を取得しようとしてpoolが枯渇します。そのため`Async::Semaphore`で同時接続数を明示的に制限する必要があります。\n\n```ruby\n# config/initializers/db_semaphore.rb\n# Falcon起動時のみDB同時接続数を制限するセマフォを定義する\nif defined?(Async::Semaphore)\n  DB_SEMAPHORE = Async::Semaphore.new(ENV.fetch(\"DB_CONCURRENCY\", 10).to_i)\nend\n```\n\n```ruby\n# app/controllers/application_controller.rb\n# DB_SEMAPHOREの有無をPuma/Falcon両対応で吸収する\ndef with_db_semaphore(\u0026block)\n  defined?(DB_SEMAPHORE) ? DB_SEMAPHORE.acquire(\u0026block) : block.call\nend\n```\n\n`with_db_semaphore`はPuma環境では`DB_SEMAPHORE`が定義されないためブロックをそのまま実行し、Falcon環境ではセマフォで同時接続数を制限します。パターンCのコードでこのヘルパーを使っているのはこのためです。\n\n比較した3パターンは以下のとおりです。\n\n- **パターンA**：単純なDBアクセスのみ（ベースライン）\n- **パターンB**：`sleep(0.5s)` による簡易I/O待ちの原理確認。Falconの効果がなぜ出るかをシンプルに示すための人工的なパターン\n- **パターンC**：実際のマイクロサービスAPIを模した実用検証。`Net::HTTP` による外部HTTPリクエスト（300ms × 2回）＋DBクエリ5本を直列実行。**このパターンの結果がメインの結論**\n\n## 結果：I/O待ちがあると劇的な差が出た\n\n### 1. パターンA（単純DBアクセス）：ベースライン\n\nエンドポイントはシンプルなUser取得です。`before_action` でDBからユーザーを取得してJSONを返します。\n\n```ruby\n# GET /users/:id\ndef show\n  render json: @user  # before_action :set_user で取得済み\nend\n```\n\n| 構成 | Puma | Falcon | 差 |\n| --- | --- | --- | --- |\n| 1プロセス × 20スレッド | 339 req/s | 338 req/s | ほぼ同等 |\n| 4プロセス × 20スレッド | 1,005 req/s | **1,627 req/s** | **+62%** |\n\n1プロセス構成ではほぼ差がありません。4プロセスに増やすとFalconが62%上回りました。\n\nDBクエリ数ms程度の短いI/O待ちでも、4プロセス構成ではFalconが上回ります。ただし差は限定的で、I/O待ち時間が長いほど差は広がります。\n\n次に、I/O待ちを加えたケースで原理を確認します。\n\n### 2. パターンB（原理確認）：仕組みを理解するための人工実験\n\n`sleep(0.5s)` で外部API待ちを簡易的に再現したパターンです。実際の外部接続は使わず、シンプルなI/O待ちのケースでPumaとFalconの性能差を確認することが目的です。\n\n```ruby\n# GET /users/:id/pattern_b\ndef pattern_b\n  if Async::Task.current?\n    Async::Task.current.sleep(0.5)\n  else\n    sleep(0.5)\n  end\n  render json: @user\nend\n```\n\n| 構成 | Puma | Falcon | 差 |\n| --- | --- | --- | --- |\n| 1プロセス × 20スレッド | 34.68 req/s | **349.14 req/s** | **約10倍** |\n| 4プロセス × 20スレッド | 150.15 req/s | **1,375.87 req/s** | **約9倍** |\n\nPumaの場合、スレッドがsleep中にブロックされるため処理できるリクエスト数に上限があります。\n\n|  | 計算式 | 値 |\n| --- | --- | --- |\n| Puma 理論上限 | 20スレッド ÷ 0.5s | 40 req/s |\n| Puma 実測値（1プロセス） | — | 34.68 req/s |\n\n理論値と実測値がほぼ一致しており、スレッド数の壁がそのままスループットの天井になっていることがわかります。\n\nFalconはsleep中に非同期I/Oで処理を切り替えるため、1000接続が並行して待機・処理されます。4プロセス構成の詳細は以下のとおりです。\n\n| 指標 | Puma（4プロセス × 20スレッド） | Falcon（4プロセス × DB接続上限20/プロセス） |\n| --- | --- | --- |\n| Req/sec | 150.15 | **1,375.87** |\n| Latency Avg | 5.87s | **706ms** |\n| Latency Max | 7.20s | **1.32s** |\n| 成功率 | 100% | **100%** |\n\nあくまで私個人の感想ですが、0.5秒の待ちがこれほどの差を生むとは思っていませんでした。\n\nこれはsleepという人工的な条件での結果です。実際のAPIに近い条件ではどうなるか。パターンCで確認します。\n\n### 3. パターンC（実用検証）：実際のAPIに近い条件での結果\n\nパターンBがsleepによる簡易な擬似だったのに対し、パターンCは実際のマイクロサービスに近い構成です。`Net::HTTP` で外部サーバーへのHTTPリクエスト（300ms × 2回）と、DBクエリ5本をすべて直列で処理します。\n外部サーバーはDockerで起動した**httpbin**を利用して、HTTP通信を再現しています。\n\n```ruby\n# GET /users/:id/pattern_c\ndef pattern_c\n  user     = with_db_semaphore { User.find(params[:id]) }\n  posts    = with_db_semaphore { Post.where(user_id: params[:id]).order(created_at: :desc).to_a }\n  comments = with_db_semaphore { Comment.where(user_id: params[:id]).order(created_at: :desc).to_a }\n  stats    = with_db_semaphore do\n    {\n      post_count:    Post.where(user_id: params[:id]).count,\n      comment_count: Comment.where(user_id: params[:id]).count\n    }\n  end\n\n  uri = URI(\"http://localhost:8888/delay/0.3\")\n  externals = []\n  fetch_count = 0\n  while fetch_count \u003c 2\n    externals \u003c\u003c Net::HTTP.start(uri.host, uri.port) do |http|\n      JSON.parse(http.get(uri.request_uri).body)\n    end\n    fetch_count += 1\n  end\n\n  render json: { user:, posts:, comments:, stats:, externals: }\nend\n```\n\n1リクエストあたりの外部HTTP待ちは合計600ms（300ms × 2回）です。直列なので1リクエスト自体が速くなるわけではありません。しかし、Falconは各I/O待ちのたびに非同期で処理を切り替え、他のリクエストを並行して進めます。\n\nこのAPIは1リクエストあたり外部HTTP 600ms＋DBクエリで合計700ms前後かかります。wrkのtimeout 2sはAPIの処理時間に対して余裕が少なく、高負荷時にタイムアウトが発生しやすい条件です。そのため、より公平な比較としてtimeout 10sでも計測しました。\n\n**timeout 2s**\n\n| 指標 | Puma（4プロセス × 20スレッド） | Falcon（4プロセス × DB接続上限20/プロセス） |\n| --- | --- | --- |\n| Req/sec | 125.33 | **495.78（約4倍）** |\n| 完了リクエスト数（30s） | 3,767 | **14,897** |\n| 成功率（2xx） | 50.9%（3,740 / 7,345） | 73.2%（14,897 / 20,338） |\n| Latency Avg | 1.18s | 1.56s |\n| Latency Max | 2.00s | 2.00s |\n\n**timeout 10s**\n\n| 指標 | Puma（4プロセス × 20スレッド） | Falcon（4プロセス × DB接続上限20/プロセス） |\n| --- | --- | --- |\n| Req/sec | 123.39 | **487.36（約4倍）** |\n| 完了リクエスト数（30s） | 3,708 | **14,641** |\n| 成功率（2xx） | 99.7%（3,696 / 3,708） | 99.6%（14,641 / 14,693） |\n| Latency Avg | 6.89s | **1.83s** |\n| Latency Max | 8.86s | **2.73s** |\n\nここで重要なのは、**PumaとFalconで全く同じコードのAPIを起動している**点です。`Net::HTTP` はFalconの非同期I/O機構（Fiber Scheduler）にフックされるため、whileループで直列に書かれた既存のHTTP呼び出しも、各イテレーション中にFalconが処理を切り替えます。既存のRailsコードをそのまま動かすだけで恩恵を受けられます。\n\n※ただし、すべてのI/O操作が完全にノンブロッキングになるわけではありません。ライブラリの実装によってはブロッキングI/Oが混在する可能性があります。本番導入前にgem依存を個別に確認することをお勧めします。\n\nでは、この結果はマイクロサービスの文脈でどう解釈すべきでしょうか。\n\n## マイクロサービスの典型的なワークロードに利用できる\n\nパターンCで差が出た「**外部HTTP＋複数DBクエリの直列処理**」は、マイクロサービスのユースケースそのものだと感じました。\n\n- 決済・金融系：外部決済API、与信照会\n- 通知系：SendGrid、Twilio、Push通知\n- 認証・認可系：外部IdP呼び出し\n- データ集約系：複数サービスのAPIを束ねるBFF\n- Webhook処理：受信して外部に転送するもの\n\nRails APIモード + Falconであれば、ActiveRecordや認証・バリデーションといった既存のRailsエコシステムをそのまま活かした上で、I/OバウンドなAPIでも実用的な構成が取れます。すでにRailsで開発しているチームにとっては、言語やスタックを切り替えることなくマイクロサービスを現実的な選択肢として検討できるようになりつつある、という話です。\n\nもちろん、スループット以外にも考慮すべき点はあります。メモリ使用量・オブザーバビリティ（分散トレーシング、ログの集約）・コンテナ化との相性など、実運用では別途検証が必要な領域が残ります。この記事はあくまでスループットに絞った実験であり、「マイクロサービスとしてすべて解決済み」という意味ではありません。\n\nでは、実際にRailsをマイクロサービスで選ぶかどうか、どう判断すればよいでしょうか。\n\n## Rails API + Falconが現実的な選択肢になるケース\n\nスループット面でI/OバウンドなAPIにRails APIが不利とされてきた前提が、少なくともI/OバウンドなAPIに関してはかなり改善が見込めそうな実験結果でした。\n\nただし、すべての構成で有効かというと、そうではありません。条件によって向き・不向きがあります。\n\n**Rails API + Falconが現実的な選択肢になるケース**\n\n- BFF（複数サービスのAPIを束ねる中間層）\n- 外部API集約（決済、通知、IdP呼び出しなど）\n- Webhook受信・転送\n- I/O待ち時間が支配的なエンドポイント全般\n- すでにRailsを採用していて社内の知見がRailsで充分にあるチーム\n\n**他のスタックを検討すべきケース**\n\n- CPUバウンドな処理（画像処理、暗号化など）\n- 単純なCRUDが中心でI/O待ちが短いサービス（非同期I/Oの恩恵が少ない）\n\n**Rails+Falconを選ぶ前に確認が必要なケース**\n\n- MySQL利用が前提のプロジェクト（非同期ドライバが存在しないため、PostgreSQLへの切り替えが前提になる）\n\nまずは新規の小さなマイクロサービス、特にBFFや外部API集約のようなI/Oバウンドな用途から試してみるのが現実的な一歩だと思います。Shopifyの事例がなければ、自分でこの実験をしようとは思いませんでした。RubyKaigiで聞いた話を自分の手で検証し、体験として理解できたことは、自分にとって価値ある学習経験でした。\n\nここまでお読み頂き、ありがとうございます！\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:46:50+09:00","group":null,"id":"e2b5ee7fc58337c2a80d","likes_count":1,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"Ruby","versions":[]},{"name":"Rails","versions":[]},{"name":"パフォーマンス","versions":[]},{"name":"RubyKaigi2026","versions":[]}],"title":"Railsの非同期Webサーバー(Falcon)で見えてきたRails APIの可能性","updated_at":"2026-05-06T11:01:51+09:00","url":"https://qiita.com/takaya787/items/e2b5ee7fc58337c2a80d","user":{"description":null,"facebook_id":null,"followees_count":3,"followers_count":5,"github_login_name":"takaya787","id":"takaya787","items_count":7,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":754958,"profile_image_url":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754958/profile-images/1772116220","team_only":false,"twitter_screen_name":"gyIYOaOp9WjzXNk","website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:132\"\u003e\n\u003cspan id=\"実務1年目駆け出しエンジニアがlaravelreactでwebアプリケーション開発に挑戦してみたその43\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%AE%9F%E5%8B%991%E5%B9%B4%E7%9B%AE%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%8Claravelreact%E3%81%A7web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA%E3%81%AB%E6%8C%91%E6%88%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F%E3%81%9D%E3%81%AE43\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e実務1年目駆け出しエンジニアがLaravel\u0026amp;ReactでWebアプリケーション開発に挑戦してみた！（その43）\u003c/h1\u003e\n\u003ch2 data-sourcepos=\"3:1-3:15\"\u003e\n\u003cspan id=\"0-初めに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#0-%E5%88%9D%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e0. 初めに\u003c/h2\u003e\n\u003cp data-sourcepos=\"4:1-4:111\"\u003eこのシリーズでは、Webアプリケーション開発のやり方をゼロから解説しています！\u003c/p\u003e\n\u003cp data-sourcepos=\"6:1-6:105\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/10bcf379af516b355071\" id=\"reference-60bd21c48fbb8287bd58\"\u003e前回\u003c/a\u003eから、テストを開始しました！\u003c/p\u003e\n\u003cp data-sourcepos=\"8:1-8:85\"\u003e今日から本格的にUnitテストを実施していきたいと思います！！\u003c/p\u003e\n\u003ch2 data-sourcepos=\"10:1-10:24\"\u003e\n\u003cspan id=\"1-ブランチ運用\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1-%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E9%81%8B%E7%94%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1. ブランチ運用\u003c/h2\u003e\n\u003cp data-sourcepos=\"11:1-12:68\"\u003e\u003ccode\u003edevelop\u003c/code\u003eブランチを最新にして、新規ブランチを切って作業をしましょう。\u003cbr\u003e\nブランチ名は、\u003ccode\u003etest/unit/policies\u003c/code\u003eとかにしましょう。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"14:1-14:30\"\u003e\n\u003cspan id=\"2-レビューポリシー\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2-%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2. レビューポリシー\u003c/h2\u003e\n\u003cp data-sourcepos=\"15:1-16:73\"\u003eUnitテストでは主に\u003cstrong\u003eポリシー\u003c/strong\u003eと\u003cstrong\u003eモデル\u003c/strong\u003eについてテストしたいと思います。\u003cbr\u003e\n今回は、\u003cstrong\u003eポリシー\u003c/strong\u003eについてのテストを実施します。\u003c/p\u003e\n\u003cp data-sourcepos=\"18:1-18:94\"\u003eまずは、\u003cstrong\u003eレビューポリシー\u003c/strong\u003eについてのテストケースを作成します。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"20:1-20:26\"\u003e\n\u003cspan id=\"21-ファイル作成\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#21-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BD%9C%E6%88%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.1 ファイル作成\u003c/h3\u003e\n\u003cp data-sourcepos=\"21:1-22:176\"\u003e\u003ccode\u003e\\project-root\\src\\tests\\Unit\u003c/code\u003eというフォルダがあるかなと思います。\u003cbr\u003e\nこれは、Laravelをインストールした際に自動で生成されたもので、Unitテストのテストケースを格納するためのフォルダとなります。\u003c/p\u003e\n\u003cp data-sourcepos=\"24:1-24:166\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/10bcf379af516b355071\"\u003e前回\u003c/a\u003e修正した、\u003ccode\u003ephpunit.xml\u003c/code\u003eで設定されているため、テスト実行時に参照されます。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"xml\" data-sourcepos=\"25:1-34:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\phpunit.xml\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;testsuites\u0026gt;\u003c/span\u003e\n        \u003cspan class=\"nt\"\u003e\u0026lt;testsuite\u003c/span\u003e \u003cspan class=\"na\"\u003ename=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"Unit\"\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n            \u003cspan class=\"nt\"\u003e\u0026lt;directory\u0026gt;\u003c/span\u003etests/Unit\u003cspan class=\"nt\"\u003e\u0026lt;/directory\u0026gt;\u003c/span\u003e\n        \u003cspan class=\"nt\"\u003e\u0026lt;/testsuite\u0026gt;\u003c/span\u003e\n        \u003cspan class=\"nt\"\u003e\u0026lt;testsuite\u003c/span\u003e \u003cspan class=\"na\"\u003ename=\u003c/span\u003e\u003cspan class=\"s\"\u003e\"Feature\"\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n            \u003cspan class=\"nt\"\u003e\u0026lt;directory\u0026gt;\u003c/span\u003etests/Feature\u003cspan class=\"nt\"\u003e\u0026lt;/directory\u0026gt;\u003c/span\u003e\n        \u003cspan class=\"nt\"\u003e\u0026lt;/testsuite\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"nt\"\u003e\u0026lt;/testsuites\u0026gt;\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"36:1-36:155\"\u003eこの下にさらに\u003ccode\u003ePolicies\u003c/code\u003eというフォルダを作成して、その中に\u003ccode\u003eReviewPolicyTest.php\u003c/code\u003eというファイルを作成してください。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"38:1-67:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\ReviewPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\Review\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\ReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// ← DB不要のときはこちら\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"c1\"\u003e// テスト名は日本語OK。「何が・どうなるべきか」を書く\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のレビューは編集できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// 1. 準備（Arrange）\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReview\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// 2. 実行（Act）\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// 3. 検証（Assert）\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003ch3 data-sourcepos=\"69:1-69:53\"\u003e\n\u003cspan id=\"22-テストケース作成aaaパターン\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#22-%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9%E4%BD%9C%E6%88%90aaa%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.2 テストケース作成（AAAパターン）\u003c/h3\u003e\n\u003cp data-sourcepos=\"70:1-71:130\"\u003e作成したファイルの中身について解説します。\u003cbr\u003e\nこれは、\u003cstrong\u003eAAAパターン\u003c/strong\u003eと呼ばれる世界的も普及している一般的になテストケースの書き方です。\u003c/p\u003e\n\u003cp data-sourcepos=\"73:1-74:46\"\u003e以下の記事が分かりやすいと思います。\u003cbr\u003e\n\u003ca href=\"https://tech.anycloud.co.jp/articles/test-aaa/\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://tech.anycloud.co.jp/articles/test-aaa/\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"76:1-76:21\"\u003eこれによると、\u003c/p\u003e\n\u003cblockquote data-sourcepos=\"77:1-79:55\"\u003e\n\u003cul data-sourcepos=\"77:2-79:55\"\u003e\n\u003cli data-sourcepos=\"77:2-77:86\"\u003e\n\u003cstrong\u003eArrange\u003c/strong\u003e：テストに必要なオブジェクトの生成、データの準備\u003c/li\u003e\n\u003cli data-sourcepos=\"78:2-78:56\"\u003e\n\u003cstrong\u003eAct\u003c/strong\u003e：テスト対象の機能を1回だけ実行\u003c/li\u003e\n\u003cli data-sourcepos=\"79:2-79:55\"\u003e\n\u003cstrong\u003eAssert\u003c/strong\u003e：期待する結果との比較を行う\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003cp data-sourcepos=\"81:1-81:132\"\u003eという三つの役割を意識して書くことで、読みやすいテストコードになりますということですね。\u003c/p\u003e\n\u003cp data-sourcepos=\"83:1-83:45\"\u003eこれを意識して見てみましょう。\u003c/p\u003e\n\u003cp data-sourcepos=\"85:1-85:51\"\u003eまず、以下の部分を見てみましょう。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"86:1-88:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"90:1-90:209\"\u003e\u003ccode\u003eTestCase\u003c/code\u003eはLaravel内部であらかじめ作られているクラスで、これを継承することでテストで使用されるメソッドなどを呼び出すことができるようになります。\u003c/p\u003e\n\u003cp data-sourcepos=\"92:1-92:78\"\u003eこれは、DBを使わないUnitテストを使うときに使用します。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"93:1-95:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"97:1-97:24\"\u003e次に以下の部分。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"98:1-100:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のレビューは編集できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"102:1-103:178\"\u003eクラスの中にメソッドを定義しています。\u003cbr\u003e\nPHPUnitでは、クラス名に日本語を使用することができ、読みやすさの観点から日本ではこのように書くのが流行っているみたいです。\u003c/p\u003e\n\u003cp data-sourcepos=\"105:1-106:48\"\u003e最後に、メソッドの中身を見ていきましょう。\u003cbr\u003e\nまずは、Arange（準備）の部分です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"107:1-113:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReview\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"115:1-116:90\"\u003eこのブロックでは、データの準備をします。\u003cbr\u003e\n\u003ccode\u003enew\u003c/code\u003eでインスタンス化して、DBを使わずに直接idを代入しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"118:1-118:63\"\u003e次に、Act（実行）ブロックを見てみましょう。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"119:1-121:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"123:1-123:102\"\u003eArrangeブロックでインスタンス化した\u003ccode\u003e$policy\u003c/code\u003eの\u003ccode\u003eupdate\u003c/code\u003eメソッドを実行します。\u003c/p\u003e\n\u003cp data-sourcepos=\"125:1-125:126\"\u003eこれは、\u003ca href=\"https://qiita.com/ArakiPhp/items/3f821f6603006cf36970\" id=\"reference-229571f42038d9c218e6\"\u003eバックエンド実装編\u003c/a\u003eで作成したものです。\u003c/p\u003e\n\u003cp data-sourcepos=\"127:1-127:48\"\u003e最後に、Assert（確認）の部分です。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"128:1-130:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"131:1-131:118\"\u003e\u003ccode\u003eassertTrue\u003c/code\u003eはPHPUnitが用意しているメソッドで、引数が\u003ccode\u003etrue\u003c/code\u003eならテスト成功を意味します。\u003c/p\u003e\n\u003cp data-sourcepos=\"133:1-134:46\"\u003e※アサーションメソッドについては、以下のドキュメントにすべて書かれているので気になる場合は参照してください。\u003cbr\u003e\n\u003ca href=\"https://net-newbie.com/phpunit/assertions.html\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://net-newbie.com/phpunit/assertions.html\u003c/a\u003e\u003c/p\u003e\n\u003ch3 data-sourcepos=\"136:1-136:23\"\u003e\n\u003cspan id=\"23-テスト実行\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#23-%E3%83%86%E3%82%B9%E3%83%88%E5%AE%9F%E8%A1%8C\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.3 テスト実行\u003c/h3\u003e\n\u003cp data-sourcepos=\"137:1-137:105\"\u003e以下のコマンドをPHPのコンテナの中で実行することでテストを実行できます。\u003c/p\u003e\n\u003cp data-sourcepos=\"139:1-139:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"140:1-142:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eReviewPolicityTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"144:1-145:122\"\u003e結果は以下の通りです。\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fc0e34a28-8875-4030-afb6-d2fa14a6b814.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=432d6d11255db8f4e13d11f906f4a579\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fc0e34a28-8875-4030-afb6-d2fa14a6b814.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=432d6d11255db8f4e13d11f906f4a579\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fc0e34a28-8875-4030-afb6-d2fa14a6b814.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=d42b393e3a85c30a8d100959841078d0 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/c0e34a28-8875-4030-afb6-d2fa14a6b814.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"147:1-148:45\"\u003eまだ、テストケースを一つしか作成していないので、「\u003cstrong\u003e1 passed\u003c/strong\u003e」と表示されています。\u003cbr\u003e\n成功していることが分かります！\u003c/p\u003e\n\u003cp data-sourcepos=\"150:1-150:101\"\u003e試しに、Assertの部分を以下のように反転させてあえて失敗させてみると...??\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"151:1-153:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"155:1-156:122\"\u003e失敗した件数と、どこで失敗したのかが表示されます。\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F9e0fec42-f4da-484d-bb12-1a776b942012.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=d97ccac4ea490bacd120c2c12a88a1da\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F9e0fec42-f4da-484d-bb12-1a776b942012.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=d97ccac4ea490bacd120c2c12a88a1da\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F9e0fec42-f4da-484d-bb12-1a776b942012.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=3ed483eda4f1f052f19d7050dc1c2121 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/9e0fec42-f4da-484d-bb12-1a776b942012.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"158:1-158:171\"\u003e今回は挙動を見るためにあえて失敗させてみたので、テストケースのAssertの部分は確認が終わったら元に戻しておきましょう。\u003c/p\u003e\n\u003cp data-sourcepos=\"160:1-160:101\"\u003eまた、これはDBに登録や削除はしないので何度実行しても問題ありません。\u003c/p\u003e\n\u003ch3 data-sourcepos=\"162:1-162:56\"\u003e\n\u003cspan id=\"24-振り返り結局何をしているのか\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#24-%E6%8C%AF%E3%82%8A%E8%BF%94%E3%82%8A%E7%B5%90%E5%B1%80%E4%BD%95%E3%82%92%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.4 振り返り（結局何をしているのか）\u003c/h3\u003e\n\u003cp data-sourcepos=\"163:1-163:156\"\u003eこれは、過去に作成したポリシー（どのユーザーがどの操作をすることができるのか）をテストしているものです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"165:1-173:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\app\\Policies\\ReviewPolicy.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e    \u003cspan class=\"c1\"\u003e// ユーザーがレビューを編集できるかどうかを判定\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eUser\u003c/span\u003e \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eReview\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// 自分が投稿したレビューのみ編集可能\u003c/span\u003e\n        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"175:1-176:222\"\u003eユーザーのIDとレビュー投稿者のIDが一致しているかどうかを見ています。\u003cbr\u003e\nこれにより、「自分が投稿したレビューが誰かに編集される」こともないし、「自分が他の人のレビューを編集したりすることができないこと」を保証しています！\u003c/p\u003e\n\u003ch3 data-sourcepos=\"178:1-178:32\"\u003e\n\u003cspan id=\"25-テストケース追加\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#25-%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9%E8%BF%BD%E5%8A%A0\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.5 テストケース追加\u003c/h3\u003e\n\u003cp data-sourcepos=\"179:1-179:126\"\u003e同様に、\u003ccode\u003efalse\u003c/code\u003eが返るケースと\u003ccode\u003edelete\u003c/code\u003eについてのテストケース用のメソッドも追加しましょう。\u003c/p\u003e\n\u003cp data-sourcepos=\"181:1-181:120\"\u003e\u003ccode\u003eassertFalse\u003c/code\u003eもPHPUnitが用意しているメソッドで、引数が逆に\u003ccode\u003efalse\u003c/code\u003eなら成功とするものです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"183:1-259:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\ReviewPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\Review\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\ReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のレビューは編集できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReview\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_他人のレビューは編集できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReview\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のレビューは削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReview\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_他人のレビューは削除できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReview\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eReviewPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$review\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"261:1-261:57\"\u003e最終的なコードは上記のようになります。\u003c/p\u003e\n\u003cp data-sourcepos=\"263:1-263:78\"\u003eファイルを保存出来たら、再びテストを実行しましょう！\u003c/p\u003e\n\u003cp data-sourcepos=\"265:1-265:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"266:1-268:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eReviewPolicyTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"270:1-271:122\"\u003e感じに「4 passed」と出ていれば成功です！\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F037e45a8-2019-419f-9abe-f025cf6661c8.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f70d7ff377dea7d51b4515bf8631b470\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F037e45a8-2019-419f-9abe-f025cf6661c8.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f70d7ff377dea7d51b4515bf8631b470\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F037e45a8-2019-419f-9abe-f025cf6661c8.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=6364b8ea9dd8622a94c60d44254ccd8d 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/037e45a8-2019-419f-9abe-f025cf6661c8.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"273:1-273:98\"\u003e\u003ccode\u003ecreate\u003c/code\u003eはDB登録もしたいので、Featureテストの方に回そうかなと思います。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"275:1-275:30\"\u003e\n\u003cspan id=\"3-コメントポリシー\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3-%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3. コメントポリシー\u003c/h2\u003e\n\u003cp data-sourcepos=\"276:1-277:81\"\u003e同じ要領で、\u003cstrong\u003eコメントポリシー\u003c/strong\u003eのテストも作成しましょう。\u003cbr\u003e\n先ほどと同様の場所に新規のファイルを作成してください。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"279:1-375:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\CommentPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\Comment\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\CommentPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eCommentPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のコメントは編集できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eCommentPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_他人のコメントは編集できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eCommentPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のコメントは削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eCommentPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_管理者は他人のコメントを削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eCommentPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_一般ユーザーは他人のコメントを削除できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eComment\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eCommentPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$comment\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"377:1-377:130\"\u003eレビューにはない「\u003cstrong\u003e管理者かどうか\u003c/strong\u003e」という観点が増えますが、なんてことはないでしょう！\u003c/p\u003e\n\u003cp data-sourcepos=\"379:1-379:24\"\u003e実行してみます。\u003c/p\u003e\n\u003cp data-sourcepos=\"381:1-381:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"382:1-384:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eCommentPolicyTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"386:1-387:122\"\u003e以下のように「5 passed」と出ていれば成功です！\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fd0e5ee4f-6d06-4ee7-a1eb-9ef3536fd6cb.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=a8f4dfcf83ca7181c48fb1798f320ae6\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fd0e5ee4f-6d06-4ee7-a1eb-9ef3536fd6cb.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=a8f4dfcf83ca7181c48fb1798f320ae6\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fd0e5ee4f-6d06-4ee7-a1eb-9ef3536fd6cb.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=53799c7b8a0ade349510e5139c5556e1 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/d0e5ee4f-6d06-4ee7-a1eb-9ef3536fd6cb.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"389:1-389:36\"\u003e\n\u003cspan id=\"4-ブックマークポリシー\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#4-%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e4. ブックマークポリシー\u003c/h2\u003e\n\u003cp data-sourcepos=\"390:1-390:90\"\u003e同じノリでブックマークポリシーに関しても作成してみましょう。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"392:1-437:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\BookmarkPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\Bookmark\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\BookmarkPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eBookmarkPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_自分のブックマークは削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$bookmark\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eBookmark\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$bookmark\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eBookmarkPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$bookmark\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_他人のブックマークは削除できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$bookmark\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eBookmark\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$bookmark\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003euser_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eBookmarkPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$bookmark\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"439:1-439:48\"\u003e更新メソッドがないので楽ですね。\u003c/p\u003e\n\u003cp data-sourcepos=\"441:1-441:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"442:1-444:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eBookmarkPolicyTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"446:1-447:122\"\u003e以下のような結果が得られればOK\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F8bbc7b7d-2dc8-49cf-9b82-d4918a5e4c10.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=55c5c90e0955d2a99eb5be6e6b098737\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F8bbc7b7d-2dc8-49cf-9b82-d4918a5e4c10.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=55c5c90e0955d2a99eb5be6e6b098737\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F8bbc7b7d-2dc8-49cf-9b82-d4918a5e4c10.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=1f6ebd8307dd7935ac880b764def735d 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/8bbc7b7d-2dc8-49cf-9b82-d4918a5e4c10.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"449:1-449:27\"\u003e\n\u003cspan id=\"5-研究室ポリシー\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#5-%E7%A0%94%E7%A9%B6%E5%AE%A4%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e5. 研究室ポリシー\u003c/h2\u003e\n\u003cp data-sourcepos=\"450:1-450:36\"\u003eこちらは、削除のみです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"452:1-492:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\LabPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\LabPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eLabPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_管理者は研究室を削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eLabPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_一般ユーザーは研究室を削除できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eLabPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"494:1-494:163\"\u003e「ログインしているかどうか」に関しては、ミドルウェアを使って実装してきたのでFeatureテストに回したいと思います。\u003c/p\u003e\n\u003cp data-sourcepos=\"496:1-496:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"497:1-499:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eLabPolicyTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"501:1-502:122\"\u003e\u003cstrong\u003e実行結果\u003c/strong\u003e\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F2fdf6ac5-d7cc-4f4a-a6a1-286c4008d43c.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=d842cce8b1550ab2ef69300a41b725fa\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F2fdf6ac5-d7cc-4f4a-a6a1-286c4008d43c.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=d842cce8b1550ab2ef69300a41b725fa\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F2fdf6ac5-d7cc-4f4a-a6a1-286c4008d43c.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=851f0f69a3ace135d934003030258711 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/2fdf6ac5-d7cc-4f4a-a6a1-286c4008d43c.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"504:1-504:24\"\u003e\n\u003cspan id=\"6-学部ポリシー\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#6-%E5%AD%A6%E9%83%A8%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e6. 学部ポリシー\u003c/h2\u003e\n\u003cp data-sourcepos=\"505:1-505:45\"\u003e研究室ポリシーと全く同じです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"507:1-547:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\FacultyPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\FacultyPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eFacultyPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_管理者は学部を削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eFacultyPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_一般ユーザーは学部を削除できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eFacultyPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"549:1-549:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"550:1-552:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eFacultyPolicyTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"554:1-555:122\"\u003e\u003cstrong\u003e実行結果\u003c/strong\u003e\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fd85439a8-7164-49e2-9eb1-70257f86f0a9.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=67205788edb1182022bf47f8dc519051\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fd85439a8-7164-49e2-9eb1-70257f86f0a9.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=67205788edb1182022bf47f8dc519051\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2Fd85439a8-7164-49e2-9eb1-70257f86f0a9.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=9fa82f67818d9ec5ab1fa6f6ff9e483a 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/d85439a8-7164-49e2-9eb1-70257f86f0a9.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 data-sourcepos=\"557:1-557:24\"\u003e\n\u003cspan id=\"7-大学ポリシー\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#7-%E5%A4%A7%E5%AD%A6%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e7. 大学ポリシー\u003c/h2\u003e\n\u003cp data-sourcepos=\"558:1-558:27\"\u003eこちらも同じです。\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"php\" data-sourcepos=\"560:1-600:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e\\project-root\\src\\tests\\Unit\\Policies\\UniversityPolicyTest.php\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?php\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eTests\\Unit\\Policies\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Models\\User\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003eApp\\Policies\\UniversityPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003cspan class=\"kn\"\u003euse\u003c/span\u003e \u003cspan class=\"nc\"\u003ePHPUnit\\Framework\\TestCase\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\n\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eUniversityPolicyTest\u003c/span\u003e \u003cspan class=\"kd\"\u003eextends\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestCase\u003c/span\u003e\n\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_管理者は大学を削除できる\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUniversityPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n    \u003cspan class=\"k\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"n\"\u003etest_一般ユーザーは大学を削除できない\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n        \u003cspan class=\"c1\"\u003e// Arrange\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003eis_admin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nc\"\u003eUniversityPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Act\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$result\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$policy\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nb\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$user\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\n        \u003cspan class=\"c1\"\u003e// Assert\u003c/span\u003e\n        \u003cspan class=\"nv\"\u003e$this\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"nf\"\u003eassertFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$result\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"602:1-602:22\"\u003e\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"603:1-605:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--filter\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eUniversityPolicyTest\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"607:1-608:122\"\u003e\u003cstrong\u003e実行結果\u003c/strong\u003e\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F1e1d6fe9-fbc9-4f13-b04f-3e513c1c90b1.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f0ee1e0e6a23f34159a564df92aecfa7\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F1e1d6fe9-fbc9-4f13-b04f-3e513c1c90b1.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=f0ee1e0e6a23f34159a564df92aecfa7\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F1e1d6fe9-fbc9-4f13-b04f-3e513c1c90b1.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=4acff8ee08eb2526840d03e465c9f8df 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/1e1d6fe9-fbc9-4f13-b04f-3e513c1c90b1.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"610:1-611:22\"\u003eまた、これまでに作成したテストケースをまとめて実行するなら以下です。\u003cbr\u003e\n\u003cstrong\u003e実行コマンド\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"bash\" data-sourcepos=\"612:1-614:3\"\u003e\n\u003cdiv class=\"code-lang\"\u003e\u003cspan class=\"bold\"\u003e/var/www\u003c/span\u003e\u003c/div\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"nv\"\u003e$ \u003c/span\u003ephp artisan \u003cspan class=\"nb\"\u003etest\u003c/span\u003e \u003cspan class=\"nt\"\u003e--testsuite\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eUnit\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cp data-sourcepos=\"616:1-617:122\"\u003e\u003cstrong\u003e実行結果\u003c/strong\u003e\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F2daa20d5-0ce4-40eb-a727-e6d13c67f000.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=854d238568a22b391c13bf83b9a07933\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F2daa20d5-0ce4-40eb-a727-e6d13c67f000.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=854d238568a22b391c13bf83b9a07933\" alt=\"image.png\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4075450%2F2daa20d5-0ce4-40eb-a727-e6d13c67f000.png?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=133012912fe0339d9008220cb533d17d 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/2daa20d5-0ce4-40eb-a727-e6d13c67f000.png\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"619:1-619:36\"\u003e全部で18個通っていればOK!!\u003c/p\u003e\n\u003cp data-sourcepos=\"621:1-621:274\"\u003eこのようにまとめて実行することで、（今回はなかったですが）バグを見つけて修正したとこで別のところでバグがまた発生しないか（いわゆる\u003cstrong\u003eデグレ\u003c/strong\u003eがおきていないか）を確認することができます！\u003c/p\u003e\n\u003cp data-sourcepos=\"623:1-624:113\"\u003eここまでできていたら、今日はおしまいです！\u003cbr\u003e\nコミット・プッシュ、PR作成・マージ、ブランチ削除を忘れずにしておきましょう。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"626:1-626:30\"\u003e\n\u003cspan id=\"8-まとめ次回予告\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#8-%E3%81%BE%E3%81%A8%E3%82%81%E6%AC%A1%E5%9B%9E%E4%BA%88%E5%91%8A\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e8. まとめ・次回予告\u003c/h2\u003e\n\u003cp data-sourcepos=\"627:1-628:129\"\u003eお疲れ様でした。\u003cbr\u003e\n今回は、Unitテストの2回目ということで、\u003cstrong\u003eポリシー\u003c/strong\u003eについてのテストを作成・実行しました。\u003c/p\u003e\n\u003cp data-sourcepos=\"630:1-630:169\"\u003eテストの書き方では、「\u003cstrong\u003eAAAパターン\u003c/strong\u003e」という書き方を採用したことで読みやすいテストコードを作ることができましたね！\u003c/p\u003e\n\u003cp data-sourcepos=\"632:1-632:113\"\u003e次回は、引き続きUnitテストの\u003cstrong\u003eモデル\u003c/strong\u003eについて作成・実行していこうと思います！\u003c/p\u003e\n\u003ch2 data-sourcepos=\"634:1-634:30\"\u003e\n\u003cspan id=\"これまでの記事一覧\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%93%E3%82%8C%E3%81%BE%E3%81%A7%E3%81%AE%E8%A8%98%E4%BA%8B%E4%B8%80%E8%A6%A7\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eこれまでの記事一覧\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"635:1-635:31\"\u003e\n\u003cspan id=\"要件定義設計編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%A6%81%E4%BB%B6%E5%AE%9A%E7%BE%A9%E8%A8%AD%E8%A8%88%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e☆要件定義・設計編\u003c/h3\u003e\n\u003cdetails\u003e\n\u003cul data-sourcepos=\"638:1-639:0\"\u003e\n\u003cli data-sourcepos=\"638:1-639:0\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/3406a08b3291a32e315b\" id=\"reference-c62da9dfa040ac7d0847\"\u003eその１: 要件定義・設計編\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"642:1-642:22\"\u003e\n\u003cspan id=\"環境構築編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e☆環境構築編\u003c/h3\u003e\n\u003cdetails\u003e\n\u003cul data-sourcepos=\"645:1-649:115\"\u003e\n\u003cli data-sourcepos=\"645:1-645:120\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/f3ae293bd155b3ef2f06\" id=\"reference-c7f95a9f8f1c675d502d\"\u003eその２: 環境構築編① ~WSL, Ubuntuインストール~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"646:1-646:123\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/802a5c78ec8d5a049d09\" id=\"reference-c7819784ba6774ffdd13\"\u003eその３: 環境構築編② ~Docker Desktopインストール~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"647:1-647:121\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/01cca09c7b9ea75ea1a6\" id=\"reference-118bd7380f4ae29a2ef1\"\u003eその４: 環境構築編③ ~Dockerコンテナ立ち上げ~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"648:1-648:116\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/224cf8491a45c1400a78\" id=\"reference-8112458a19be912dc062\"\u003eその５: 環境構築編④ ~Laravelインストール~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"649:1-649:115\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/e2acad25ce6d0bec65b4\" id=\"reference-55c0453daed6a556b7a4\"\u003eその６: 環境構築編⑤ ~Gitリポジトリ接続~\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"652:1-652:34\"\u003e\n\u003cspan id=\"バックエンド実装編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89%E5%AE%9F%E8%A3%85%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e☆バックエンド実装編\u003c/h3\u003e\n\u003cdetails\u003e\n\u003cul data-sourcepos=\"655:1-670:141\"\u003e\n\u003cli data-sourcepos=\"655:1-655:119\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/3c7b960ea777a57c94ff\" id=\"reference-a55d46812ed64a0214c6\"\u003eその7: バックエンド実装編① ~認証機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"656:1-656:137\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/5583ebc0f1f73018074b\" id=\"reference-818fcdfdcc0f95be75f2\"\u003eその8: バックエンド実装編②前編 ~レビュー投稿機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"657:1-657:139\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/14c2ef13fdb4cb672de7\" id=\"reference-8141f5f267bfb99c2c3d\"\u003eその8.5: バックエンド実装編②後編 ~レビュー投稿機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"658:1-658:129\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/4ee29c0b1ebe00be20c8\" id=\"reference-2b4062c88fc04207cc4d\"\u003eその9: バックエンド実装編③ ~レビューCRUD機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"659:1-659:139\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/3f821f6603006cf36970\"\u003eその10: バックエンド実装編④ ~レビューCRUD機能作成その２~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"660:1-660:153\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/7f9f8297059ddc042415\" id=\"reference-722a002cd46d47b8abab\"\u003eその11: バックエンド実装編⑤ ~新規大学・学部・研究室作成機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"661:1-661:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/15fb22e3d701036721aa\" id=\"reference-6666aa9e48718a93ec56\"\u003eその12: バックエンド実装編⑥ ~大学検索機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"662:1-662:147\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/5e13fed839f80fa7da58\" id=\"reference-14775101e5d043cd0637\"\u003eその13: バックエンド実装編⑦ ~大学・学部・研究室編集機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"663:1-663:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/ac612b694c5763e465eb\" id=\"reference-992dd68aec303c8d101e\"\u003eその14: バックエンド実装編⑧ ~コメント投稿機能~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"664:1-664:135\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/4217232febd9f9613e71\" id=\"reference-afdbc667934f6485d8f1\"\u003eその15: バックエンド実装編⑨ ~コメント編集・削除機能~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"665:1-665:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/8ef563e9328e3e23b0c4\" id=\"reference-d50784fcfab7e8c75a64\"\u003eその16: バックエンド実装編⑩ ~ブックマーク機能~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"666:1-666:147\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/b45c8184639d0e0c1f10\" id=\"reference-503604cfdf139fd31640\"\u003eその17: バックエンド実装編⑪ ~排他制御・トランザクション処理~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"667:1-667:129\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/e48f91bc81dcd0ae494e\" id=\"reference-c1bf35542b34ea076631\"\u003eその18: バックエンド実装編⑫ ~マイページ機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"668:1-668:138\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/e48f91bc81dcd0ae494e\"\u003eその19: バックエンド実装編⑬ ~管理者アカウント機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"669:1-669:120\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/1e794a53808c11e89a75\" id=\"reference-2d1373dba90d807c092d\"\u003eその20: バックエンド実装編⑭ ~通知機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"670:1-670:141\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/c5d7691e2e905a7c8d8a\" id=\"reference-128d4936374da26f2c75\"\u003eその21: バックエンド実装編⑮ ~ソーシャルログイン機能作成~\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"673:1-673:37\"\u003e\n\u003cspan id=\"フロントエンド実装編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E5%AE%9F%E8%A3%85%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e☆フロントエンド実装編\u003c/h3\u003e\n\u003cdetails\u003e\n\u003cul data-sourcepos=\"676:1-697:141\"\u003e\n\u003cli data-sourcepos=\"676:1-676:141\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/6c7e296f0632453036c0\" id=\"reference-a11d0289bb50614345c0\"\u003eその22: フロントエンド実装編① ~メインコンテンツ領域作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"677:1-677:123\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/5b0f4c16336140c45e8c\" id=\"reference-40e240221fe214fb6124\"\u003eその23: フロントエンド実装編② ~ヘッダー作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"678:1-678:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/921e9a2c5897f0b80336\" id=\"reference-bcec6d10ff60b9d1b0fb\"\u003eその24: フロントエンド実装編③ ~サイドバー作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"679:1-679:129\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/91dfbfc652471f4e187d\" id=\"reference-78e6b626f77b63b536cc\"\u003eその25: フロントエンド実装編④ ~ホームページ作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"680:1-680:135\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/10f6d75e3fd600035f95\" id=\"reference-5c91387a5ceb793f6d11\"\u003eその26: フロントエンド実装編⑤ ~大学検索結果画面作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"681:1-681:144\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/317953bd803d47f4b8d0\" id=\"reference-7cbdff501058ed2a2a0f\"\u003eその27: フロントエンド実装編⑥ ~大学詳細・学部一覧画面作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"682:1-682:147\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/a65725d2fbd73d391641\" id=\"reference-6ff92635516013dca927\"\u003eその28: フロントエンド実装編⑦ ~学部詳細・研究室一覧画面作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"683:1-683:159\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/b60772758526971eac5b\" id=\"reference-95bc8cb7899d03e0a890\"\u003eその29: フロントエンド実装編⑧前編 ~研究室詳細・レビュー画面作成前編~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"684:1-684:161\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/14e928f3acb3d8219722\" id=\"reference-fffd9923b05346f96d63\"\u003eその29.5: フロントエンド実装編⑧後編 ~研究室詳細・レビュー画面作成後編~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"685:1-685:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/e3e05b985787d5ed4807\" id=\"reference-8f2fab9feb0aaa727452\"\u003eその30: フロントエンド実装編⑨ ~マイページ作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"686:1-686:132\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/b9e689a5cf7aee97ef4d\" id=\"reference-a696d4bab80703da1a61\"\u003eその31: フロントエンド実装編⑩ ~パンくずリスト作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"687:1-687:150\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/19e4280f67a16f83e178\" id=\"reference-ef3f89cc735f2694da99\"\u003eその32: フロントエンド実装編⑪ ~ログイン・新規登録モーダル作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"688:1-688:144\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/82b01c7a1c67491e7b06\" id=\"reference-e4f7a864f11e34e395ed\"\u003eその33: フロントエンド実装編⑫ ~大学作成・編集モーダル作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"689:1-689:144\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/4bbc8d52431d8e7f2ce8\" id=\"reference-118f3de4a53b6f4ddd58\"\u003eその34: フロントエンド実装編⑬ ~学部作成・編集モーダル作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"690:1-690:147\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/b6dd6ceaf650a9f2efda\" id=\"reference-8cdc62c39e59e732f144\"\u003eその35: フロントエンド実装編⑭ ~研究室作成・編集モーダル作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"691:1-691:150\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/7410d1888f1fce4c7193\" id=\"reference-f3479923f29caa9308ac\"\u003eその36: フロントエンド実装編⑮ ~レビュー作成・編集モーダル作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"692:1-692:147\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/7410d1888f1fce4c7193\"\u003eその37: フロントエンド実装編⑯ ~コメント一覧表示モーダル作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"693:1-693:186\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/d187d1a09a382b5975c9\" id=\"reference-075a3328f75ddf1b85bc\"\u003eその38: フロントエンド実装編⑰ ~編集履歴ページ・削除依頼ページ・通知ドロップダウン作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"694:1-694:123\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/beabea6450cd343c12fd\" id=\"reference-40d37dc01716b4d2f876\"\u003eその39: フロントエンド実装編⑱ ~トースト作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"695:1-695:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/f1e11adf3699b6db51a1\" id=\"reference-738f39512b1eb1ed4580\"\u003eその40: フロントエンド実装編⑲ ~退会ページ作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"696:1-696:126\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/f75aae8f679d93954f32\" id=\"reference-3452782574dfa2f7b4a9\"\u003eその41: フロントエンド実装編⑳ ~ファビコン作成~\u003c/a\u003e\u003c/li\u003e\n\u003cli data-sourcepos=\"697:1-697:141\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/c29c45acbb2084293c52\" id=\"reference-60badd2714094663e711\"\u003eその42: フロントエンド実装編㉑ ~レスポンシブデザイン対応~\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"700:1-700:34\"\u003e\n\u003cspan id=\"テストデバッグ編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%86%E3%82%B9%E3%83%88%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e☆テスト・デバッグ編\u003c/h3\u003e\n\u003cdetails\u003e\n\u003cul data-sourcepos=\"703:1-703:153\"\u003e\n\u003cli data-sourcepos=\"703:1-703:153\"\u003e\u003ca href=\"https://qiita.com/ArakiPhp/items/10bcf379af516b355071\"\u003eその43: テスト・デバッグ編① ~Unitテスト1[テストデータ準備・DB設定]~\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/details\u003e\n\u003ch2 data-sourcepos=\"706:1-706:9\"\u003e\n\u003cspan id=\"参考\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%8F%82%E8%80%83\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e参考\u003c/h2\u003e\n\u003cul data-sourcepos=\"707:1-708:0\"\u003e\n\u003cli data-sourcepos=\"707:1-708:0\"\u003e\u003ca href=\"https://tech.anycloud.co.jp/articles/test-aaa/\" rel=\"nofollow noopener\" target=\"_blank\"\u003eAAAパターンで実現するクリーンなテストコード\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 data-sourcepos=\"709:1-709:15\"\u003e\n\u003cspan id=\"軽く宣伝\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%BB%BD%E3%81%8F%E5%AE%A3%E4%BC%9D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e軽く宣伝\u003c/h2\u003e\n\u003cp data-sourcepos=\"710:1-711:138\"\u003eYouTubeを始めました（というか始めてました）。\u003cbr\u003e\n内容としては、Webエンジニアの生活や稼げるようになるまでの成長記録などを発信していく予定です。\u003c/p\u003e\n\u003cp data-sourcepos=\"713:1-713:182\"\u003e現在、まったく再生されておらず、落ち込みそうなので、見てくださる方はぜひ高評価を教えもらえると励みになると思います。\"(-\"\"-)\"\u003c/p\u003e\n\u003cp data-sourcepos=\"715:1-715:33\"\u003e\u003ciframe id=\"qiita-embed-content__27e7c5d02bf43b974a412d6071b6999b\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__27e7c5d02bf43b974a412d6071b6999b\" data-content=\"https%3A%2F%2Fwww.youtube.com%2F%40ArakiPhp\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n","body":"# 実務1年目駆け出しエンジニアがLaravel\u0026ReactでWebアプリケーション開発に挑戦してみた！（その43）\n\n## 0. 初めに\nこのシリーズでは、Webアプリケーション開発のやり方をゼロから解説しています！\n\n[前回](https://qiita.com/ArakiPhp/items/10bcf379af516b355071)から、テストを開始しました！\n\n今日から本格的にUnitテストを実施していきたいと思います！！\n\n## 1. ブランチ運用\n`develop`ブランチを最新にして、新規ブランチを切って作業をしましょう。\nブランチ名は、`test/unit/policies`とかにしましょう。\n\n## 2. レビューポリシー\nUnitテストでは主に**ポリシー**と**モデル**についてテストしたいと思います。\n今回は、**ポリシー**についてのテストを実施します。\n\nまずは、**レビューポリシー**についてのテストケースを作成します。\n\n### 2.1 ファイル作成\n`\\project-root\\src\\tests\\Unit`というフォルダがあるかなと思います。\nこれは、Laravelをインストールした際に自動で生成されたもので、Unitテストのテストケースを格納するためのフォルダとなります。\n\n[前回](https://qiita.com/ArakiPhp/items/10bcf379af516b355071)修正した、`phpunit.xml`で設定されているため、テスト実行時に参照されます。\n```xml:\\project-root\\src\\phpunit.xml\n    \u003ctestsuites\u003e\n        \u003ctestsuite name=\"Unit\"\u003e\n            \u003cdirectory\u003etests/Unit\u003c/directory\u003e\n        \u003c/testsuite\u003e\n        \u003ctestsuite name=\"Feature\"\u003e\n            \u003cdirectory\u003etests/Feature\u003c/directory\u003e\n        \u003c/testsuite\u003e\n    \u003c/testsuites\u003e\n```\n\nこの下にさらに`Policies`というフォルダを作成して、その中に`ReviewPolicyTest.php`というファイルを作成してください。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\ReviewPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\User;\nuse App\\Models\\Review;\nuse App\\Policies\\ReviewPolicy;\nuse PHPUnit\\Framework\\TestCase;  // ← DB不要のときはこちら\n\nclass ReviewPolicyTest extends TestCase\n{\n    // テスト名は日本語OK。「何が・どうなるべきか」を書く\n    public function test_自分のレビューは編集できる(): void\n    {\n        // 1. 準備（Arrange）\n        $user = new User();\n        $user-\u003eid = 1;\n        $review = new Review();\n        $review-\u003euser_id = 1;\n        $policy = new ReviewPolicy();\n\n        // 2. 実行（Act）\n        $result = $policy-\u003eupdate($user, $review);\n\n        // 3. 検証（Assert）\n        $this-\u003eassertTrue($result);\n    }\n}\n```\n\n### 2.2 テストケース作成（AAAパターン）\n作成したファイルの中身について解説します。\nこれは、**AAAパターン**と呼ばれる世界的も普及している一般的になテストケースの書き方です。\n\n以下の記事が分かりやすいと思います。\nhttps://tech.anycloud.co.jp/articles/test-aaa/\n\nこれによると、\n\u003e* **Arrange**：テストに必要なオブジェクトの生成、データの準備\n\u003e* **Act**：テスト対象の機能を1回だけ実行\n\u003e* **Assert**：期待する結果との比較を行う\n\nという三つの役割を意識して書くことで、読みやすいテストコードになりますということですね。\n\nこれを意識して見てみましょう。\n\nまず、以下の部分を見てみましょう。\n```php:\nclass ReviewPolicyTest extends TestCase\n```\n\n`TestCase`はLaravel内部であらかじめ作られているクラスで、これを継承することでテストで使用されるメソッドなどを呼び出すことができるようになります。\n\nこれは、DBを使わないUnitテストを使うときに使用します。\n```php:\nuse PHPUnit\\Framework\\TestCase;\n```\n\n次に以下の部分。\n```php:\n    public function test_自分のレビューは編集できる(): void\n```\n\nクラスの中にメソッドを定義しています。\nPHPUnitでは、クラス名に日本語を使用することができ、読みやすさの観点から日本ではこのように書くのが流行っているみたいです。\n\n最後に、メソッドの中身を見ていきましょう。\nまずは、Arange（準備）の部分です。\n```php:\n        $user = new User();\n        $user-\u003eid = 1;\n        $review = new Review();\n        $review-\u003euser_id = 1;\n        $policy = new ReviewPolicy();\n```\n\nこのブロックでは、データの準備をします。\n`new`でインスタンス化して、DBを使わずに直接idを代入しています。\n\n次に、Act（実行）ブロックを見てみましょう。\n```php:\n        $result = $policy-\u003eupdate($user, $review);\n```\n\nArrangeブロックでインスタンス化した`$policy`の`update`メソッドを実行します。\n\nこれは、[バックエンド実装編](https://qiita.com/ArakiPhp/items/3f821f6603006cf36970)で作成したものです。\n\n最後に、Assert（確認）の部分です。\n```php:\n        $this-\u003eassertTrue($result);\n```\n`assertTrue`はPHPUnitが用意しているメソッドで、引数が`true`ならテスト成功を意味します。\n\n※アサーションメソッドについては、以下のドキュメントにすべて書かれているので気になる場合は参照してください。\nhttps://net-newbie.com/phpunit/assertions.html\n\n### 2.3 テスト実行\n以下のコマンドをPHPのコンテナの中で実行することでテストを実行できます。\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=ReviewPolicityTest\n```\n\n結果は以下の通りです。\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/c0e34a28-8875-4030-afb6-d2fa14a6b814.png)\n\nまだ、テストケースを一つしか作成していないので、「**1 passed**」と表示されています。\n成功していることが分かります！\n\n試しに、Assertの部分を以下のように反転させてあえて失敗させてみると...??\n```php:\n        $this-\u003eassertTrue(!$result);\n```\n\n失敗した件数と、どこで失敗したのかが表示されます。\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/9e0fec42-f4da-484d-bb12-1a776b942012.png)\n\n今回は挙動を見るためにあえて失敗させてみたので、テストケースのAssertの部分は確認が終わったら元に戻しておきましょう。\n\nまた、これはDBに登録や削除はしないので何度実行しても問題ありません。\n\n### 2.4 振り返り（結局何をしているのか）\nこれは、過去に作成したポリシー（どのユーザーがどの操作をすることができるのか）をテストしているものです。\n\n```php:\\project-root\\src\\app\\Policies\\ReviewPolicy.php\n    // ユーザーがレビューを編集できるかどうかを判定\n    public function update(User $user, Review $review)\n    {\n        // 自分が投稿したレビューのみ編集可能\n        return $user-\u003eid === $review-\u003euser_id;\n    }\n\n```\n\nユーザーのIDとレビュー投稿者のIDが一致しているかどうかを見ています。\nこれにより、「自分が投稿したレビューが誰かに編集される」こともないし、「自分が他の人のレビューを編集したりすることができないこと」を保証しています！\n\n### 2.5 テストケース追加\n同様に、`false`が返るケースと`delete`についてのテストケース用のメソッドも追加しましょう。\n\n`assertFalse`もPHPUnitが用意しているメソッドで、引数が逆に`false`なら成功とするものです。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\ReviewPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\User;\nuse App\\Models\\Review;\nuse App\\Policies\\ReviewPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\nclass ReviewPolicyTest extends TestCase\n{\n    public function test_自分のレビューは編集できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $review = new Review();\n        $review-\u003euser_id = 1;\n        $policy = new ReviewPolicy();\n\n        // Act\n        $result = $policy-\u003eupdate($user, $review);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_他人のレビューは編集できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $review = new Review();\n        $review-\u003euser_id = 2;\n        $policy = new ReviewPolicy();\n\n        // Act\n        $result = $policy-\u003eupdate($user, $review);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n\n    public function test_自分のレビューは削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $review = new Review();\n        $review-\u003euser_id = 1;\n        $policy = new ReviewPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $review);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_他人のレビューは削除できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $review = new Review();\n        $review-\u003euser_id = 2;\n        $policy = new ReviewPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $review);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n}\n```\n\n最終的なコードは上記のようになります。\n\nファイルを保存出来たら、再びテストを実行しましょう！\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=ReviewPolicyTest\n```\n\n感じに「4 passed」と出ていれば成功です！\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/037e45a8-2019-419f-9abe-f025cf6661c8.png)\n\n`create`はDB登録もしたいので、Featureテストの方に回そうかなと思います。\n\n## 3. コメントポリシー\n同じ要領で、**コメントポリシー**のテストも作成しましょう。\n先ほどと同様の場所に新規のファイルを作成してください。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\CommentPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\Comment;\nuse App\\Models\\User;\nuse App\\Policies\\CommentPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\nclass CommentPolicyTest extends TestCase\n{\n    public function test_自分のコメントは編集できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $comment = new Comment();\n        $comment-\u003euser_id = 1;\n        $policy = new CommentPolicy();\n\n        // Act\n        $result = $policy-\u003eupdate($user, $comment);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_他人のコメントは編集できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $comment = new Comment();\n        $comment-\u003euser_id = 2;\n        $policy = new CommentPolicy();\n\n        // Act\n        $result = $policy-\u003eupdate($user, $comment);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n\n    public function test_自分のコメントは削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $user-\u003eis_admin = false;\n        $comment = new Comment();\n        $comment-\u003euser_id = 1;\n        $policy = new CommentPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $comment);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_管理者は他人のコメントを削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $user-\u003eis_admin = true;\n        $comment = new Comment();\n        $comment-\u003euser_id = 2;\n        $policy = new CommentPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $comment);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_一般ユーザーは他人のコメントを削除できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $user-\u003eis_admin = false;\n        $comment = new Comment();\n        $comment-\u003euser_id = 2;\n        $policy = new CommentPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $comment);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n}\n\n```\n\nレビューにはない「**管理者かどうか**」という観点が増えますが、なんてことはないでしょう！\n\n実行してみます。\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=CommentPolicyTest\n```\n\n以下のように「5 passed」と出ていれば成功です！\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/d0e5ee4f-6d06-4ee7-a1eb-9ef3536fd6cb.png)\n\n## 4. ブックマークポリシー\n同じノリでブックマークポリシーに関しても作成してみましょう。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\BookmarkPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\Bookmark;\nuse App\\Models\\User;\nuse App\\Policies\\BookmarkPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\nclass BookmarkPolicyTest extends TestCase\n{\n    public function test_自分のブックマークは削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $bookmark = new Bookmark();\n        $bookmark-\u003euser_id = 1;\n        $policy = new BookmarkPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $bookmark);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_他人のブックマークは削除できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eid = 1;\n        $bookmark = new Bookmark();\n        $bookmark-\u003euser_id = 2;\n        $policy = new BookmarkPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user, $bookmark);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n}\n\n```\n\n更新メソッドがないので楽ですね。\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=BookmarkPolicyTest\n```\n\n以下のような結果が得られればOK\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/8bbc7b7d-2dc8-49cf-9b82-d4918a5e4c10.png)\n\n## 5. 研究室ポリシー\nこちらは、削除のみです。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\LabPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\User;\nuse App\\Policies\\LabPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\nclass LabPolicyTest extends TestCase\n{\n    public function test_管理者は研究室を削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eis_admin = true;\n        $policy = new LabPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_一般ユーザーは研究室を削除できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eis_admin = false;\n        $policy = new LabPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n}\n\n```\n\n「ログインしているかどうか」に関しては、ミドルウェアを使って実装してきたのでFeatureテストに回したいと思います。\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=LabPolicyTest\n```\n\n**実行結果**\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/2fdf6ac5-d7cc-4f4a-a6a1-286c4008d43c.png)\n\n## 6. 学部ポリシー\n研究室ポリシーと全く同じです。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\FacultyPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\User;\nuse App\\Policies\\FacultyPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\nclass FacultyPolicyTest extends TestCase\n{\n    public function test_管理者は学部を削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eis_admin = true;\n        $policy = new FacultyPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_一般ユーザーは学部を削除できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eis_admin = false;\n        $policy = new FacultyPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n}\n\n```\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=FacultyPolicyTest\n```\n\n**実行結果**\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/d85439a8-7164-49e2-9eb1-70257f86f0a9.png)\n\n## 7. 大学ポリシー\nこちらも同じです。\n\n```php:\\project-root\\src\\tests\\Unit\\Policies\\UniversityPolicyTest.php\n\u003c?php\n\nnamespace Tests\\Unit\\Policies;\n\nuse App\\Models\\User;\nuse App\\Policies\\UniversityPolicy;\nuse PHPUnit\\Framework\\TestCase;\n\nclass UniversityPolicyTest extends TestCase\n{\n    public function test_管理者は大学を削除できる(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eis_admin = true;\n        $policy = new UniversityPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user);\n\n        // Assert\n        $this-\u003eassertTrue($result);\n    }\n\n    public function test_一般ユーザーは大学を削除できない(): void\n    {\n        // Arrange\n        $user = new User();\n        $user-\u003eis_admin = false;\n        $policy = new UniversityPolicy();\n\n        // Act\n        $result = $policy-\u003edelete($user);\n\n        // Assert\n        $this-\u003eassertFalse($result);\n    }\n}\n\n```\n\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --filter=UniversityPolicyTest\n```\n\n**実行結果**\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/1e1d6fe9-fbc9-4f13-b04f-3e513c1c90b1.png)\n\nまた、これまでに作成したテストケースをまとめて実行するなら以下です。\n**実行コマンド**\n```bash:/var/www\n$ php artisan test --testsuite=Unit\n```\n\n**実行結果**\n![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/2daa20d5-0ce4-40eb-a727-e6d13c67f000.png)\n\n全部で18個通っていればOK!!\n\nこのようにまとめて実行することで、（今回はなかったですが）バグを見つけて修正したとこで別のところでバグがまた発生しないか（いわゆる**デグレ**がおきていないか）を確認することができます！\n\nここまでできていたら、今日はおしまいです！\nコミット・プッシュ、PR作成・マージ、ブランチ削除を忘れずにしておきましょう。\n\n## 8. まとめ・次回予告\nお疲れ様でした。\n今回は、Unitテストの2回目ということで、**ポリシー**についてのテストを作成・実行しました。\n\nテストの書き方では、「**AAAパターン**」という書き方を採用したことで読みやすいテストコードを作ることができましたね！\n\n次回は、引き続きUnitテストの**モデル**について作成・実行していこうと思います！\n\n## これまでの記事一覧\n### ☆要件定義・設計編\n\u003cdetails\u003e\n\n* [その１: 要件定義・設計編](https://qiita.com/ArakiPhp/items/3406a08b3291a32e315b)\n\n\u003c/details\u003e\n\n### ☆環境構築編\n\u003cdetails\u003e\n\n* [その２: 環境構築編① ~WSL, Ubuntuインストール~](https://qiita.com/ArakiPhp/items/f3ae293bd155b3ef2f06)\n* [その３: 環境構築編② ~Docker Desktopインストール~](https://qiita.com/ArakiPhp/items/802a5c78ec8d5a049d09)\n* [その４: 環境構築編③ ~Dockerコンテナ立ち上げ~](https://qiita.com/ArakiPhp/items/01cca09c7b9ea75ea1a6)\n* [その５: 環境構築編④ ~Laravelインストール~](https://qiita.com/ArakiPhp/items/224cf8491a45c1400a78)\n* [その６: 環境構築編⑤ ~Gitリポジトリ接続~](https://qiita.com/ArakiPhp/items/e2acad25ce6d0bec65b4)\n\u003c/details\u003e\n\n### ☆バックエンド実装編\n\u003cdetails\u003e\n\n* [その7: バックエンド実装編① ~認証機能作成~](https://qiita.com/ArakiPhp/items/3c7b960ea777a57c94ff)\n* [その8: バックエンド実装編②前編 ~レビュー投稿機能作成~](https://qiita.com/ArakiPhp/items/5583ebc0f1f73018074b)\n* [その8.5: バックエンド実装編②後編 ~レビュー投稿機能作成~](https://qiita.com/ArakiPhp/items/14c2ef13fdb4cb672de7)\n* [その9: バックエンド実装編③ ~レビューCRUD機能作成~](https://qiita.com/ArakiPhp/items/4ee29c0b1ebe00be20c8)\n* [その10: バックエンド実装編④ ~レビューCRUD機能作成その２~](https://qiita.com/ArakiPhp/items/3f821f6603006cf36970)\n* [その11: バックエンド実装編⑤ ~新規大学・学部・研究室作成機能作成~](https://qiita.com/ArakiPhp/items/7f9f8297059ddc042415)\n* [その12: バックエンド実装編⑥ ~大学検索機能作成~](https://qiita.com/ArakiPhp/items/15fb22e3d701036721aa)\n* [その13: バックエンド実装編⑦ ~大学・学部・研究室編集機能作成~](https://qiita.com/ArakiPhp/items/5e13fed839f80fa7da58)\n* [その14: バックエンド実装編⑧ ~コメント投稿機能~](https://qiita.com/ArakiPhp/items/ac612b694c5763e465eb)\n* [その15: バックエンド実装編⑨ ~コメント編集・削除機能~](https://qiita.com/ArakiPhp/items/4217232febd9f9613e71)\n* [その16: バックエンド実装編⑩ ~ブックマーク機能~](https://qiita.com/ArakiPhp/items/8ef563e9328e3e23b0c4)\n* [その17: バックエンド実装編⑪ ~排他制御・トランザクション処理~](https://qiita.com/ArakiPhp/items/b45c8184639d0e0c1f10)\n* [その18: バックエンド実装編⑫ ~マイページ機能作成~](https://qiita.com/ArakiPhp/items/e48f91bc81dcd0ae494e)\n* [その19: バックエンド実装編⑬ ~管理者アカウント機能作成~](https://qiita.com/ArakiPhp/items/e48f91bc81dcd0ae494e)\n* [その20: バックエンド実装編⑭ ~通知機能作成~](https://qiita.com/ArakiPhp/items/1e794a53808c11e89a75)\n* [その21: バックエンド実装編⑮ ~ソーシャルログイン機能作成~](https://qiita.com/ArakiPhp/items/c5d7691e2e905a7c8d8a)\n\u003c/details\u003e\n\n### ☆フロントエンド実装編\n\u003cdetails\u003e\n\n* [その22: フロントエンド実装編① ~メインコンテンツ領域作成~](https://qiita.com/ArakiPhp/items/6c7e296f0632453036c0)\n* [その23: フロントエンド実装編② ~ヘッダー作成~](https://qiita.com/ArakiPhp/items/5b0f4c16336140c45e8c)\n* [その24: フロントエンド実装編③ ~サイドバー作成~](https://qiita.com/ArakiPhp/items/921e9a2c5897f0b80336)\n* [その25: フロントエンド実装編④ ~ホームページ作成~](https://qiita.com/ArakiPhp/items/91dfbfc652471f4e187d)\n* [その26: フロントエンド実装編⑤ ~大学検索結果画面作成~](https://qiita.com/ArakiPhp/items/10f6d75e3fd600035f95)\n* [その27: フロントエンド実装編⑥ ~大学詳細・学部一覧画面作成~](https://qiita.com/ArakiPhp/items/317953bd803d47f4b8d0)\n* [その28: フロントエンド実装編⑦ ~学部詳細・研究室一覧画面作成~](https://qiita.com/ArakiPhp/items/a65725d2fbd73d391641)\n* [その29: フロントエンド実装編⑧前編 ~研究室詳細・レビュー画面作成前編~](https://qiita.com/ArakiPhp/items/b60772758526971eac5b)\n* [その29.5: フロントエンド実装編⑧後編 ~研究室詳細・レビュー画面作成後編~](https://qiita.com/ArakiPhp/items/14e928f3acb3d8219722)\n* [その30: フロントエンド実装編⑨ ~マイページ作成~](https://qiita.com/ArakiPhp/items/e3e05b985787d5ed4807)\n* [その31: フロントエンド実装編⑩ ~パンくずリスト作成~](https://qiita.com/ArakiPhp/items/b9e689a5cf7aee97ef4d)\n* [その32: フロントエンド実装編⑪ ~ログイン・新規登録モーダル作成~](https://qiita.com/ArakiPhp/items/19e4280f67a16f83e178)\n* [その33: フロントエンド実装編⑫ ~大学作成・編集モーダル作成~](https://qiita.com/ArakiPhp/items/82b01c7a1c67491e7b06)\n* [その34: フロントエンド実装編⑬ ~学部作成・編集モーダル作成~](https://qiita.com/ArakiPhp/items/4bbc8d52431d8e7f2ce8)\n* [その35: フロントエンド実装編⑭ ~研究室作成・編集モーダル作成~](https://qiita.com/ArakiPhp/items/b6dd6ceaf650a9f2efda)\n* [その36: フロントエンド実装編⑮ ~レビュー作成・編集モーダル作成~](https://qiita.com/ArakiPhp/items/7410d1888f1fce4c7193)\n* [その37: フロントエンド実装編⑯ ~コメント一覧表示モーダル作成~](https://qiita.com/ArakiPhp/items/7410d1888f1fce4c7193)\n* [その38: フロントエンド実装編⑰ ~編集履歴ページ・削除依頼ページ・通知ドロップダウン作成~](https://qiita.com/ArakiPhp/items/d187d1a09a382b5975c9)\n* [その39: フロントエンド実装編⑱ ~トースト作成~](https://qiita.com/ArakiPhp/items/beabea6450cd343c12fd)\n* [その40: フロントエンド実装編⑲ ~退会ページ作成~](https://qiita.com/ArakiPhp/items/f1e11adf3699b6db51a1)\n* [その41: フロントエンド実装編⑳ ~ファビコン作成~](https://qiita.com/ArakiPhp/items/f75aae8f679d93954f32)\n* [その42: フロントエンド実装編㉑ ~レスポンシブデザイン対応~](https://qiita.com/ArakiPhp/items/c29c45acbb2084293c52)\n\u003c/details\u003e\n\n### ☆テスト・デバッグ編\n\u003cdetails\u003e\n\n* [その43: テスト・デバッグ編① ~Unitテスト1[テストデータ準備・DB設定]~](https://qiita.com/ArakiPhp/items/10bcf379af516b355071)\n\u003c/details\u003e\n\n## 参考\n* [AAAパターンで実現するクリーンなテストコード](https://tech.anycloud.co.jp/articles/test-aaa/)\n\n## 軽く宣伝\nYouTubeを始めました（というか始めてました）。\n内容としては、Webエンジニアの生活や稼げるようになるまでの成長記録などを発信していく予定です。\n\n現在、まったく再生されておらず、落ち込みそうなので、見てくださる方はぜひ高評価を教えもらえると励みになると思います。\"(-\"\"-)\"\n\nhttps://www.youtube.com/@ArakiPhp\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:45:51+09:00","group":null,"id":"67156304989d47e3a502","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"PHP","versions":[]},{"name":"JavaScript","versions":[]},{"name":"PHPUnit","versions":[]},{"name":"Laravel","versions":[]},{"name":"React","versions":[]}],"title":"実務1年目駆け出しエンジニアがLaravel\u0026ReactでWebアプリケーション開発に挑戦してみた！（テスト・デバッグ編②）~Unitテスト2[ポリシー]~","updated_at":"2026-05-06T10:45:51+09:00","url":"https://qiita.com/ArakiPhp/items/67156304989d47e3a502","user":{"description":"自社開発系ミドルベンチャーで実務1年目のWebエンジニアです。","facebook_id":"","followees_count":1,"followers_count":12,"github_login_name":null,"id":"ArakiPhp","items_count":52,"linkedin_id":"","location":"","name":"","organization":"","permanent_id":4075450,"profile_image_url":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075450/profile-images/1761799153","team_only":false,"twitter_screen_name":"ArakiPhp","website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:14\"\u003e\n\u003cspan id=\"前の記事\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%89%8D%E3%81%AE%E8%A8%98%E4%BA%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e前の記事\u003c/h1\u003e\n\u003ch2 data-sourcepos=\"3:1-3:12\"\u003e\n\u003cspan id=\"構築編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%A7%8B%E7%AF%89%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e構築編\u003c/h2\u003e\n\u003cp data-sourcepos=\"4:1-4:51\"\u003e\u003ciframe id=\"qiita-embed-content__46abdb623ea6be44e49e215edd6b6c7d\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__46abdb623ea6be44e49e215edd6b6c7d\" data-content=\"https%3A%2F%2Fqiita.com%2FTama79%2Fitems%2F494a6170d19f9de05a81\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003ch2 data-sourcepos=\"6:1-6:18\"\u003e\n\u003cspan id=\"環境確認編\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E7%92%B0%E5%A2%83%E7%A2%BA%E8%AA%8D%E7%B7%A8\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e環境確認編\u003c/h2\u003e\n\u003cp data-sourcepos=\"7:1-7:51\"\u003e\u003ciframe id=\"qiita-embed-content__dd3e2aab3f050a4884fdce0bc1ab01fa\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__dd3e2aab3f050a4884fdce0bc1ab01fa\" data-content=\"https%3A%2F%2Fqiita.com%2FTama79%2Fitems%2Ffe40ee51ab69c0a1e18c\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003ch1 data-sourcepos=\"9:1-9:26\"\u003e\n\u003cspan id=\"メモリ環境の確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%A1%E3%83%A2%E3%83%AA%E7%92%B0%E5%A2%83%E3%81%AE%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eメモリ環境の確認\u003c/h1\u003e\n\u003ch2 data-sourcepos=\"11:1-11:32\"\u003e\n\u003cspan id=\"1メモリサイズの確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AE%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1.メモリサイズの確認\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"12:1-12:35\"\u003e\n\u003cspan id=\"11メモリサイズを確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#11%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1.1.メモリサイズを確認\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003efreeコマンドで確認\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"17:1-17:53\"\u003eメガバイト単位（-mオプション）で確認\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"19:1-24:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003efree \u003cspan class=\"nt\"\u003e-m\u003c/span\u003e\n\u003cspan class=\"go\"\u003e               total        used        free      shared  buff/cache   available\nMem:             503         200         181           0         152         303\nSwap:            502         155         347\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"25:1-25:12\"\u003eすくな！\u003c/p\u003e\n\u003c/details\u003e\n\u003ch1 data-sourcepos=\"28:1-28:29\"\u003e\n\u003cspan id=\"スワップメモリ増加\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E5%A2%97%E5%8A%A0\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eスワップメモリ増加\u003c/h1\u003e\n\u003ch2 data-sourcepos=\"30:1-30:35\"\u003e\n\u003cspan id=\"1スワップメモリの確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#1%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%AE%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1.スワップメモリの確認\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"31:1-31:38\"\u003e\n\u003cspan id=\"11スワップメモリを確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#11%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%92%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e1.1.スワップメモリを確認\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003eswaponコマンドで確認\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"36:1-36:56\"\u003eswapon（-sオプション）でサマリ情報を確認\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"38:1-42:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003eswapon \u003cspan class=\"nt\"\u003e-s\u003c/span\u003e\n\u003cspan class=\"go\"\u003eFilename                                Type            Size            Used            Priority\n/.swapfile                              file            515068          159700          -2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003c/details\u003e\n\u003ch2 data-sourcepos=\"45:1-45:35\"\u003e\n\u003cspan id=\"2スワップメモリの作成\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#2%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.スワップメモリの作成\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"46:1-46:53\"\u003e\n\u003cspan id=\"21スワップメモリ領域を新規に作成\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#21%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E9%A0%98%E5%9F%9F%E3%82%92%E6%96%B0%E8%A6%8F%E3%81%AB%E4%BD%9C%E6%88%90\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.1.スワップメモリ領域を新規に作成\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003eddコマンドで作成\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"51:1-51:11\"\u003eddで作成\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"53:1-62:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003esudo dd \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e/dev/zero \u003cspan class=\"nv\"\u003eof\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e/var/swapfile \u003cspan class=\"nv\"\u003ebs\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e1M \u003cspan class=\"nv\"\u003ecount\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e2048\n\u003cspan class=\"go\"\u003e2048+0 records in\n2048+0 records out\n2147483648 bytes (2.1 GB, 2.0 GiB) copied, 40.3121 s, 53.3 MB/s\n\u003c/span\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003esudo \u003c/span\u003emkswap /var/swapfile\n\u003cspan class=\"go\"\u003emkswap: /var/swapfile: insecure permissions 0644, fix with: chmod 0600 /var/swapfile\nSetting up swapspace version 1, size = 2 GiB (2147479552 bytes)\nno label, UUID=f1a00827-c23f-4039-86b0-1508abae9733\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ctable data-sourcepos=\"63:1-68:93\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"63:1-63:31\"\u003e\n\u003cth style=\"text-align: left\" data-sourcepos=\"63:2-63:16\"\u003eオプション\u003c/th\u003e\n\u003cth style=\"text-align: left\" data-sourcepos=\"63:18-63:23\"\u003e内容\u003c/th\u003e\n\u003cth style=\"text-align: left\" data-sourcepos=\"63:25-63:30\"\u003e備考\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"65:1-65:82\"\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"65:2-65:3\"\u003eif\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"65:5-65:22\"\u003e入力ファイル\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"65:24-65:81\"\u003e/dev/zeroを指定し、新規作成領域を0で埋める\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"66:1-66:107\"\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"66:2-66:3\"\u003eof\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"66:5-66:22\"\u003e主力ファイル\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"66:24-66:106\"\u003e/var/swapfileを指定し、/var/swapfileを新規スワップファイルとする\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"67:1-67:43\"\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"67:2-67:3\"\u003ebs\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"67:5-67:25\"\u003eブロックサイズ\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"67:27-67:42\"\u003e1M(1MB)を指定\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr data-sourcepos=\"68:1-68:93\"\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"68:2-68:6\"\u003ecount\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"68:8-68:40\"\u003eブロックサイズ繰り返し\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"68:42-68:92\"\u003eブロックサイズ1MB×2048回（2GB）を指定\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"71:1-71:59\"\u003e\n\u003cspan id=\"22スワップメモリ領域の権限を変更する\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#22%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E9%A0%98%E5%9F%9F%E3%81%AE%E6%A8%A9%E9%99%90%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.2.スワップメモリ領域の権限を変更する\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003echmodコマンドで作成\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"76:1-77:18\"\u003echmodで権限変更\u003cbr\u003e\n変更後は確認\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"79:1-83:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003esudo chmod \u003c/span\u003e600 /var/swapfile\n\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003ell /var/swapfile\n\u003cspan class=\"go\"\u003e-rw-------. 1 root root 2147483648 May  6 01:01 /var/swapfile\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ctable data-sourcepos=\"84:1-86:157\"\u003e\n\u003cthead\u003e\n\u003ctr data-sourcepos=\"84:1-84:31\"\u003e\n\u003cth style=\"text-align: left\" data-sourcepos=\"84:2-84:16\"\u003eオプション\u003c/th\u003e\n\u003cth style=\"text-align: left\" data-sourcepos=\"84:18-84:23\"\u003e内容\u003c/th\u003e\n\u003cth style=\"text-align: left\" data-sourcepos=\"84:25-84:30\"\u003e備考\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr data-sourcepos=\"86:1-86:157\"\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"86:2-86:1\"\u003e\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"86:3-86:5\"\u003e600\u003c/td\u003e\n\u003ctd style=\"text-align: left\" data-sourcepos=\"86:7-86:156\"\u003e所有者：6(読取:4=アリ, 書込:2=アリ,実行:0=無し)\u003cbr\u003e所有グループ：0(アクセス権無し)\u003cbr\u003eその他:0(アクセス権無し)\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"90:1-90:50\"\u003e\n\u003cspan id=\"23スワップメモリファイルの指定\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#23%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%8C%87%E5%AE%9A\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e2.3.スワップメモリファイルの指定\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003eswaponコマンドで指定\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"95:1-95:39\"\u003eswaponコマンドでファイル指定\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"97:1-100:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003esudo \u003c/span\u003eswapon /var/swapfile\n\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003c/details\u003e\n\u003ch2 data-sourcepos=\"103:1-103:35\"\u003e\n\u003cspan id=\"3スワップメモリの確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#3%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%AE%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3.スワップメモリの確認\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"104:1-104:38\"\u003e\n\u003cspan id=\"31スワップメモリを確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#31%E3%82%B9%E3%83%AF%E3%83%83%E3%83%97%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%92%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3.1.スワップメモリを確認\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003eswaponコマンドで確認\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"109:1-109:56\"\u003eswapon（-sオプション）でサマリ情報を確認\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"111:1-116:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003eswapon \u003cspan class=\"nt\"\u003e-s\u003c/span\u003e\n\u003cspan class=\"go\"\u003eFilename                                Type            Size            Used            Priority\n/.swapfile                              file            515068          161732          -2\n/var/swapfile                           file            2097148         0               -3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"117:1-117:28\"\u003e/var/swapfileができた！\u003c/p\u003e\n\u003c/details\u003e\n\u003ch3 data-sourcepos=\"120:1-120:35\"\u003e\n\u003cspan id=\"32メモリサイズを確認\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#32%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E7%A2%BA%E8%AA%8D\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e3.2.メモリサイズを確認\u003c/h3\u003e\n\u003cdetails\u003e\n\u003csummary\u003efreeコマンドで確認\n\u003c/summary\u003e\n\u003cp data-sourcepos=\"125:1-125:53\"\u003eメガバイト単位（-mオプション）で確認\u003c/p\u003e\n\u003cdiv class=\"code-frame\" data-lang=\"shell-session\" data-sourcepos=\"127:1-132:3\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003ccode\u003e\u003cspan class=\"gp\"\u003e[opc@test-server ~]$\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003efree \u003cspan class=\"nt\"\u003e-m\u003c/span\u003e\n\u003cspan class=\"go\"\u003e               total        used        free      shared  buff/cache   available\nMem:             503         200          36           1         289         303\nSwap:           2550         157        2393\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp data-sourcepos=\"133:1-133:12\"\u003eふえた！\u003c/p\u003e\n\u003c/details\u003e\n\u003ch1 data-sourcepos=\"136:1-136:11\"\u003e\n\u003cspan id=\"最後に\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%9C%80%E5%BE%8C%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e最後に\u003c/h1\u003e\n\u003cp data-sourcepos=\"137:1-137:162\"\u003e記事の内容はCC BY-SA 4.0（著作者の情報とCCライセンス継承はお願いします。商用利用・改変・再配布は問題なし）です。\u003c/p\u003e\n","body":"# 前の記事\n\n## 構築編\nhttps://qiita.com/Tama79/items/494a6170d19f9de05a81\n\n## 環境確認編\nhttps://qiita.com/Tama79/items/fe40ee51ab69c0a1e18c\n\n# メモリ環境の確認\n\n## 1.メモリサイズの確認\n### 1.1.メモリサイズを確認\n\u003cdetails\u003e\n\u003csummary\u003efreeコマンドで確認\n\u003c/summary\u003e\n\nメガバイト単位（-mオプション）で確認\n\n```shell-session\n[opc@test-server ~]$ free -m\n               total        used        free      shared  buff/cache   available\nMem:             503         200         181           0         152         303\nSwap:            502         155         347\n```\nすくな！\n\u003c/details\u003e\n\n# スワップメモリ増加\n\n## 1.スワップメモリの確認\n### 1.1.スワップメモリを確認\n\u003cdetails\u003e\n\u003csummary\u003eswaponコマンドで確認\n\u003c/summary\u003e\n\nswapon（-sオプション）でサマリ情報を確認\n\n```shell-session\n[opc@test-server ~]$ swapon -s\nFilename                                Type            Size            Used            Priority\n/.swapfile                              file            515068          159700          -2\n```\n\u003c/details\u003e\n\n## 2.スワップメモリの作成\n### 2.1.スワップメモリ領域を新規に作成\n\u003cdetails\u003e\n\u003csummary\u003eddコマンドで作成\n\u003c/summary\u003e\n\nddで作成\n\n```shell-session\n[opc@test-server ~]$ sudo dd if=/dev/zero of=/var/swapfile bs=1M count=2048\n2048+0 records in\n2048+0 records out\n2147483648 bytes (2.1 GB, 2.0 GiB) copied, 40.3121 s, 53.3 MB/s\n[opc@test-server ~]$ sudo mkswap /var/swapfile\nmkswap: /var/swapfile: insecure permissions 0644, fix with: chmod 0600 /var/swapfile\nSetting up swapspace version 1, size = 2 GiB (2147479552 bytes)\nno label, UUID=f1a00827-c23f-4039-86b0-1508abae9733\n```\n|オプション|内容|備考|\n|:-|:-|:-|\n|if|入力ファイル|/dev/zeroを指定し、新規作成領域を0で埋める|\n|of|主力ファイル|/var/swapfileを指定し、/var/swapfileを新規スワップファイルとする|\n|bs|ブロックサイズ|1M(1MB)を指定|\n|count|ブロックサイズ繰り返し|ブロックサイズ1MB×2048回（2GB）を指定|\n\u003c/details\u003e\n\n### 2.2.スワップメモリ領域の権限を変更する\n\u003cdetails\u003e\n\u003csummary\u003echmodコマンドで作成\n\u003c/summary\u003e\n\nchmodで権限変更\n変更後は確認\n\n```shell-session\n[opc@test-server ~]$ sudo chmod 600 /var/swapfile\n[opc@test-server ~]$ ll /var/swapfile\n-rw-------. 1 root root 2147483648 May  6 01:01 /var/swapfile\n```\n|オプション|内容|備考|\n|:-|:-|:-|\n||600|所有者：6(読取:4=アリ, 書込:2=アリ,実行:0=無し)\u003cbr\u003e所有グループ：0(アクセス権無し)\u003cbr\u003eその他:0(アクセス権無し)|\n\n\u003c/details\u003e\n\n### 2.3.スワップメモリファイルの指定\n\u003cdetails\u003e\n\u003csummary\u003eswaponコマンドで指定\n\u003c/summary\u003e\n\nswaponコマンドでファイル指定\n\n```shell-session\n[opc@test-server ~]$ sudo swapon /var/swapfile\n[opc@test-server ~]$\n```\n\u003c/details\u003e\n\n## 3.スワップメモリの確認\n### 3.1.スワップメモリを確認\n\u003cdetails\u003e\n\u003csummary\u003eswaponコマンドで確認\n\u003c/summary\u003e\n\nswapon（-sオプション）でサマリ情報を確認\n\n```shell-session\n[opc@test-server ~]$ swapon -s\nFilename                                Type            Size            Used            Priority\n/.swapfile                              file            515068          161732          -2\n/var/swapfile                           file            2097148         0               -3\n```\n/var/swapfileができた！\n\u003c/details\u003e\n\n### 3.2.メモリサイズを確認\n\u003cdetails\u003e\n\u003csummary\u003efreeコマンドで確認\n\u003c/summary\u003e\n\nメガバイト単位（-mオプション）で確認\n\n```shell-session\n[opc@test-server ~]$ free -m\n               total        used        free      shared  buff/cache   available\nMem:             503         200          36           1         289         303\nSwap:           2550         157        2393\n```\nふえた！\n\u003c/details\u003e\n\n# 最後に\n記事の内容はCC BY-SA 4.0（著作者の情報とCCライセンス継承はお願いします。商用利用・改変・再配布は問題なし）です。 \n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:43:19+09:00","group":null,"id":"7cfb21d6804d09e75e19","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"swapon","versions":[]},{"name":"OracleLinux","versions":[]},{"name":"swap","versions":[]},{"name":"OracleLinux9","versions":[]}],"title":"Oracle LinuxでSwap領域を拡張する","updated_at":"2026-05-06T10:43:19+09:00","url":"https://qiita.com/Tama79/items/7cfb21d6804d09e75e19","user":{"description":null,"facebook_id":null,"followees_count":1,"followers_count":0,"github_login_name":null,"id":"Tama79","items_count":25,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":4017908,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/4017908/6f9ecd06ac878da2849468f9fea45675d636a4eb/x_large.png?1740192842","team_only":false,"twitter_screen_name":null,"website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cp data-sourcepos=\"1:1-1:123\"\u003eこの記事は「新人プログラマ応援 - みんなで新人を育てよう！」イベントの参加記事です。\u003c/p\u003e\n\u003ch1 data-sourcepos=\"3:1-3:14\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eはじめに\u003c/h1\u003e\n\u003cp data-sourcepos=\"5:1-5:119\"\u003e3年前に、「新人プログラマの方におすすめしたい技術書5選」という記事を書きました。\u003c/p\u003e\n\u003cp data-sourcepos=\"7:1-7:52\"\u003e\u003ciframe id=\"qiita-embed-content__62065a03352b6e6d2e4ed8065556b246\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__62065a03352b6e6d2e4ed8065556b246\" data-content=\"https%3A%2F%2Fqiita.com%2FSyuparn%2Fitems%2Fc0d3776dcccfd0f7083f\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"9:1-9:155\"\u003eここで挙げた本は技術や考え方の原則が学べるものなので、今(2026年）、もしくはそれ以降でもおすすめできます。\u003c/p\u003e\n\u003cp data-sourcepos=\"11:1-11:102\"\u003e書籍紹介記事は他にもたくさん書かれているので、本記事では切り口を変え\u003c/p\u003e\n\u003cul data-sourcepos=\"13:1-15:0\"\u003e\n\u003cli data-sourcepos=\"13:1-13:92\"\u003e「そもそも、分からないことをどうやって学んでいけばいいの？」\u003c/li\u003e\n\u003cli data-sourcepos=\"14:1-15:0\"\u003e「自分にとって分かりやすい学び方は何？」\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"16:1-16:91\"\u003eという疑問に答えてくれる本を3冊（独断と偏見で）まとめました。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"18:1-18:112\"\u003e\n\u003cspan id=\"プログラマー脳-優れたプログラマーになるための認知科学に基づくアプローチ\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E8%84%B3-%E5%84%AA%E3%82%8C%E3%81%9F%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E8%AA%8D%E7%9F%A5%E7%A7%91%E5%AD%A6%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%8F%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eプログラマー脳 ～優れたプログラマーになるための認知科学に基づくアプローチ\u003c/h2\u003e\n\u003cp data-sourcepos=\"20:1-20:53\"\u003e\u003ciframe id=\"qiita-embed-content__124504f9ebeb1d4fb4dfb82117202f25\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__124504f9ebeb1d4fb4dfb82117202f25\" data-content=\"https%3A%2F%2Fwww.shuwasystem.co.jp%2Fbook%2F9784798068534.html\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"22:1-23:166\"\u003e「コーディングが難しい」「苦手なのかも」という感覚を、本人の適性ではなく認知科学の観点からとらえなおした本です。\u003cbr\u003e\n本書ではソースコードを理解するためのボトルネックを3種類に分類し、それぞれに対するトレーニングを紹介しています。\u003c/p\u003e\n\u003cul data-sourcepos=\"25:1-31:0\"\u003e\n\u003cli data-sourcepos=\"25:1-26:45\"\u003e長期記憶\n\u003cul data-sourcepos=\"26:5-26:45\"\u003e\n\u003cli data-sourcepos=\"26:5-26:45\"\u003e文法や設計パターン等の知識\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"27:1-28:33\"\u003e短期記憶\n\u003cul data-sourcepos=\"28:5-28:33\"\u003e\n\u003cli data-sourcepos=\"28:5-28:33\"\u003e変数名やデータ構造\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli data-sourcepos=\"29:1-31:0\"\u003eワーキングメモリ\n\u003cul data-sourcepos=\"30:5-31:0\"\u003e\n\u003cli data-sourcepos=\"30:5-31:0\"\u003e今追っている変数の値や状態遷移（脳内でのプログラムの実行=トレース）\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp data-sourcepos=\"32:1-32:270\"\u003eイメージとしては、入試の難問へ挑む前に100マス計算でケアレスミスを防ぎ、問題自体に落ち着いて取り組む方法を知る本です。分からないときにも自分を責めずにすむという意味でもおすすめです。\u003c/p\u003e\n\u003cp data-sourcepos=\"34:1-34:112\"\u003e1人でやるだけでなく、チームの勉強会として取り入れてみるのも良いと思います。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"36:1-36:91\"\u003e\n\u003cspan id=\"100日チャレンジ-毎日連続100本アプリを作ったら人生が変わった\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#100%E6%97%A5%E3%83%81%E3%83%A3%E3%83%AC%E3%83%B3%E3%82%B8-%E6%AF%8E%E6%97%A5%E9%80%A3%E7%B6%9A100%E6%9C%AC%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%89%E4%BA%BA%E7%94%9F%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%9F\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e＃100日チャレンジ 毎日連続100本アプリを作ったら人生が変わった\u003c/h2\u003e\n\u003cp data-sourcepos=\"38:1-38:56\"\u003e\u003ciframe id=\"qiita-embed-content__5d83564597f828af9ea476c7037a401f\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__5d83564597f828af9ea476c7037a401f\" data-content=\"https%3A%2F%2Fbookplus.nikkei.com%2Fatcl%2Fcatalog%2F24%2F12%2F05%2F01757%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"40:1-40:168\"\u003e仕事にLLMが欠かせなくなってきている今日この頃。コーディングよりプロンプトを書く時間の方が長くなるかもしれません。\u003c/p\u003e\n\u003cp data-sourcepos=\"42:1-44:72\"\u003e本書は、大学生（当時）の著者が、LLMを使って毎日1本アプリを作成した記録をつづったものです。\u003cbr\u003e\n知識を集めるよりも実践で成長する！という方に特におすすめです。\u003cbr\u003e\n（私と全く違う学習法だったので勉強になりました）\u003c/p\u003e\n\u003cp data-sourcepos=\"46:1-46:189\"\u003eプロンプトを書いてプログラムができた後、なぜそうなるのか、詳しい動作はなんなのか、自然と振り返る習慣のヒントになると思います。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"48:1-48:102\"\u003e\n\u003cspan id=\"医師のつくった頭のよさテスト認知特性から見た６つのパターン\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E5%8C%BB%E5%B8%AB%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%9F%E9%A0%AD%E3%81%AE%E3%82%88%E3%81%95%E3%83%86%E3%82%B9%E3%83%88%E8%AA%8D%E7%9F%A5%E7%89%B9%E6%80%A7%E3%81%8B%E3%82%89%E8%A6%8B%E3%81%9F%EF%BC%96%E3%81%A4%E3%81%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e医師のつくった「頭のよさ」テスト～認知特性から見た６つのパターン～\u003c/h2\u003e\n\u003cp data-sourcepos=\"50:1-50:38\"\u003e\u003ciframe id=\"qiita-embed-content__4ed9f7f4c2b7809036a299270f78da08\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__4ed9f7f4c2b7809036a299270f78da08\" data-content=\"https%3A%2F%2Fwww.amazon.co.jp%2Fdp%2FB009KZ45RU\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"52:1-52:135\"\u003e最後は技術書ではありませんが、自分が得意／苦手なインプット方法を知れる本として紹介します。\u003c/p\u003e\n\u003cp data-sourcepos=\"54:1-54:222\"\u003e「頭のよさ」テストという名前ですが、IQテストではありません。写真、ストーリー、音声等どんな情報形式と相性がいいか6つのタイプで分類するというものです。\u003c/p\u003e\n\u003cp data-sourcepos=\"56:1-57:180\"\u003e図形、口頭、文章、どのコミュニケーションが理解しやすいか、または苦手かを知り、自分に合った手法を工夫する手がかりが得られるでしょう。\u003cbr\u003e\n（例えば私は \u003ccode\u003e聴覚 \u0026gt; 言語 \u0026gt; 視覚\u003c/code\u003e なので、新しいUIツールを見たらボタンの場所を文章でメモしたり声に出して覚えたりしています）\u003c/p\u003e\n\u003ch2 data-sourcepos=\"59:1-59:15\"\u003e\n\u003cspan id=\"おわりに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eおわりに\u003c/h2\u003e\n\u003cp data-sourcepos=\"60:1-61:90\"\u003e以上、学び方を学べる本の紹介でした。\u003cbr\u003e\n自分の強みを活かし、仕事で活躍していくことを応援しています！\u003c/p\u003e\n","body":"この記事は「新人プログラマ応援 - みんなで新人を育てよう！」イベントの参加記事です。\n\n# はじめに\n\n3年前に、「新人プログラマの方におすすめしたい技術書5選」という記事を書きました。\n\nhttps://qiita.com/Syuparn/items/c0d3776dcccfd0f7083f\n\nここで挙げた本は技術や考え方の原則が学べるものなので、今(2026年）、もしくはそれ以降でもおすすめできます。\n\n書籍紹介記事は他にもたくさん書かれているので、本記事では切り口を変え\n\n- 「そもそも、分からないことをどうやって学んでいけばいいの？」\n- 「自分にとって分かりやすい学び方は何？」\n\nという疑問に答えてくれる本を3冊（独断と偏見で）まとめました。\n\n## プログラマー脳 ～優れたプログラマーになるための認知科学に基づくアプローチ\n\nhttps://www.shuwasystem.co.jp/book/9784798068534.html\n\n「コーディングが難しい」「苦手なのかも」という感覚を、本人の適性ではなく認知科学の観点からとらえなおした本です。\n本書ではソースコードを理解するためのボトルネックを3種類に分類し、それぞれに対するトレーニングを紹介しています。\n\n- 長期記憶\n    - 文法や設計パターン等の知識\n- 短期記憶\n    - 変数名やデータ構造\n- ワーキングメモリ\n    - 今追っている変数の値や状態遷移（脳内でのプログラムの実行=トレース）\n\nイメージとしては、入試の難問へ挑む前に100マス計算でケアレスミスを防ぎ、問題自体に落ち着いて取り組む方法を知る本です。分からないときにも自分を責めずにすむという意味でもおすすめです。\n\n1人でやるだけでなく、チームの勉強会として取り入れてみるのも良いと思います。\n\n## ＃100日チャレンジ 毎日連続100本アプリを作ったら人生が変わった\n\nhttps://bookplus.nikkei.com/atcl/catalog/24/12/05/01757/\n\n仕事にLLMが欠かせなくなってきている今日この頃。コーディングよりプロンプトを書く時間の方が長くなるかもしれません。\n\n本書は、大学生（当時）の著者が、LLMを使って毎日1本アプリを作成した記録をつづったものです。\n知識を集めるよりも実践で成長する！という方に特におすすめです。\n（私と全く違う学習法だったので勉強になりました）\n\nプロンプトを書いてプログラムができた後、なぜそうなるのか、詳しい動作はなんなのか、自然と振り返る習慣のヒントになると思います。\n\n## 医師のつくった「頭のよさ」テスト～認知特性から見た６つのパターン～\n\nhttps://www.amazon.co.jp/dp/B009KZ45RU\n\n最後は技術書ではありませんが、自分が得意／苦手なインプット方法を知れる本として紹介します。\n\n「頭のよさ」テストという名前ですが、IQテストではありません。写真、ストーリー、音声等どんな情報形式と相性がいいか6つのタイプで分類するというものです。\n\n図形、口頭、文章、どのコミュニケーションが理解しやすいか、または苦手かを知り、自分に合った手法を工夫する手がかりが得られるでしょう。\n（例えば私は `聴覚 \u003e 言語 \u003e 視覚` なので、新しいUIツールを見たらボタンの場所を文章でメモしたり声に出して覚えたりしています）\n\n## おわりに\n以上、学び方を学べる本の紹介でした。\n自分の強みを活かし、仕事で活躍していくことを応援しています！\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:42:52+09:00","group":null,"id":"26633b740caf8650d144","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"本","versions":[]},{"name":"新人プログラマ応援","versions":[]}],"title":"「ついていけない」を解消したい！学び方を学べる本3選","updated_at":"2026-05-06T10:42:52+09:00","url":"https://qiita.com/Syuparn/items/26633b740caf8650d144","user":{"description":"見習いエンジニア、記憶をあきらめ記録に頼ります。\r\n自作言語Pangaea, Cholc開発中","facebook_id":"","followees_count":16,"followers_count":32,"github_login_name":"Syuparn","id":"Syuparn","items_count":178,"linkedin_id":"","location":"Kanagawa, Japan","name":"","organization":"","permanent_id":239887,"profile_image_url":"https://avatars3.githubusercontent.com/u/36665200?v=4","team_only":false,"twitter_screen_name":null,"website_url":""},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cp data-sourcepos=\"1:1-1:158\"\u003e\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F27037%2F35fe578f-cff5-437f-8403-bfbfad99cd3e.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=25ec9734660fd45d849a44cb3933e205\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F27037%2F35fe578f-cff5-437f-8403-bfbfad99cd3e.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=25ec9734660fd45d849a44cb3933e205\" alt=\"プログラミング雑記2026年5月6日.jpg\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F27037%2F35fe578f-cff5-437f-8403-bfbfad99cd3e.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=7f15b1eb09c86ce85e018c866279efbf 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27037/35fe578f-cff5-437f-8403-bfbfad99cd3e.jpeg\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"3:1-3:117\"\u003e本日も、ネットに流れるトピックから個人的に興味を引かれたものを拾っていきます。\u003c/p\u003e\n\u003cp data-sourcepos=\"5:1-5:112\"\u003e連休も気がつけば最終日。シティスカ2やっていたら無駄に時間が溶けていました…\u003c/p\u003e\n\u003cp data-sourcepos=\"7:1-7:78\"\u003eこの記事への感想等コメントで頂けるとありがたいです。\u003c/p\u003e\n\u003ch2 data-sourcepos=\"9:1-9:24\"\u003e\n\u003cspan id=\"プログラミング\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eプログラミング\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"11:1-11:8\"\u003e\n\u003cspan id=\"java\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#java\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eJava\u003c/h3\u003e\n\u003cp data-sourcepos=\"13:1-13:82\"\u003e\u003ciframe id=\"qiita-embed-content__165ee56c465bbf6edb24e3481c037a0e\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__165ee56c465bbf6edb24e3481c037a0e\" data-content=\"https%3A%2F%2Fdevblogs.microsoft.com%2Fjava%2Fjava-openjdk-april-2026-patch-security-update%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"15:1-15:194\"\u003eMicrosoftは2026年4月のOpenJDKパッチとセキュリティアップデートをリリースし、複数のバージョン（25、21、17、11）で修正と改善を提供しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"16:1-17:0\"\u003e\n\u003cp data-sourcepos=\"18:1-18:72\"\u003e\u003ciframe id=\"qiita-embed-content__7a6569d026157191f11bec571532875c\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__7a6569d026157191f11bec571532875c\" data-content=\"https%3A%2F%2Fblog.jetbrains.com%2Fidea%2F2026%2F05%2Fjava-annotated-monthly-may-2026%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"20:1-20:897\"\u003e2026年5月の「Java Annotated Monthly」は、業界を大きく変えているAIコーディングエージェントの採用を中心に取り扱っています。EmillyBache氏による特集では、AI支援コーディング設計における「ハーネス」の重要性や、テスト駆動開発への影響について論じられています。Java26のリリース情報やKotlinの最新動向、Spring関連の技術更新など、実践的なコンテンツが豊富に提供されています。また、JAX、Devoxx UK、GeeCon、JNationなど複数の国際的なカンファレンスの情報が掲載されており、AI開発における信頼性や倫理的課題についても重要なテーマとして取り上げられています。開発者がこれらのツールを効果的に活用するための知識と最新情報が充実した内容となっています。\u003c/p\u003e\n\u003chr data-sourcepos=\"21:1-22:0\"\u003e\n\u003ch3 data-sourcepos=\"23:1-23:14\"\u003e\n\u003cspan id=\"javascript\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#javascript\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eJavaScript\u003c/h3\u003e\n\u003cp data-sourcepos=\"25:1-25:42\"\u003e\u003ciframe id=\"qiita-embed-content__7b2780b8faf5d59930467fa6d3a4fcac\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__7b2780b8faf5d59930467fa6d3a4fcac\" data-content=\"https%3A%2F%2Fnodejs.org%2Fen%2Fblog%2Frelease%2Fv26.0.0\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"27:1-27:641\"\u003eNode.js 26.0.0がリリースされました。このバージョンは現在のリリース版として、10月に長期サポート版になるまで今後6ヶ月間利用できます。主な機能追加としまして、Temporal APIがデフォルトで有効になりました。これは従来のDateオブジェクトに代わる、より堅牢で機能豊富な現代的な日時APIです。また、JavaScriptエンジンのV8が14.6にアップデートされており、Chromium 134に含まれるバージョンとなっています。HTTP クライアント実装であるUndiciも8.0.2へアップデートされています。\u003c/p\u003e\n\u003cp data-sourcepos=\"29:1-29:357\"\u003e複数の非推奨機能が廃止されました。例えば、\u003ccode\u003ehttp.Server.prototype.writeHeader()\u003c/code\u003eは完全に削除されましたので、\u003ccode\u003ewriteHead()\u003c/code\u003eを使用してください。従来の\u003ccode\u003e_stream_*\u003c/code\u003eモジュールも廃止されています。さらに、GCC要件が13.2以上に引き上げられ、Python 3.9のサポートも終了しました。\u003c/p\u003e\n\u003cp data-sourcepos=\"31:1-31:234\"\u003eこのバージョンでは多くのセキュリティ関連の改善とバグ修正が含まれており、開発者に新機能の活用と既存アプリケーションへの影響を評価することが推奨されています。\u003c/p\u003e\n\u003chr data-sourcepos=\"32:1-33:0\"\u003e\n\u003ch3 data-sourcepos=\"34:1-34:11\"\u003e\n\u003cspan id=\"windows\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#windows\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eWindows\u003c/h3\u003e\n\u003cp data-sourcepos=\"36:1-36:131\"\u003e\u003ciframe id=\"qiita-embed-content__c8e7b24d53f6ef047c5cac9ffc1e872a\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__c8e7b24d53f6ef047c5cac9ffc1e872a\" data-content=\"https%3A%2F%2Fdevblogs.microsoft.com%2Fifdef-windows%2Fannouncing-the-winapp-vs-code-extension-run-debug-and-package-windows-apps-in-vs-code%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"38:1-38:235\"\u003eMicrosoft が WinApp VS Code 拡張機能を公開プレビューで発表したもので、VS Code から直接 Windows アプリの実行、デバッグ、パッケージ化ができるようになる機能を紹介しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"39:1-40:0\"\u003e\n\u003ch3 data-sourcepos=\"41:1-41:15\"\u003e\n\u003cspan id=\"claude-code\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#claude-code\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eClaude Code\u003c/h3\u003e\n\u003cp data-sourcepos=\"43:1-43:87\"\u003e\u003ciframe id=\"qiita-embed-content__e9159707a139b889c412a633fa19d8e2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__e9159707a139b889c412a633fa19d8e2\" data-content=\"https%3A%2F%2Fdev.classmethod.jp%2Farticles%2Fclaude-code-env-scrub-secret-leak-defense-in-depth%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"45:1-45:225\"\u003eClaude Code v2.1.83で追加されたCLAUDE_CODE_SUBPROCESS_ENV_SCRUB機能による環境変数の漏洩防止について検証し、secretlintやgitleaksと組み合わせた多層防御の構成を紹介しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"46:1-47:0\"\u003e\n\u003ch3 data-sourcepos=\"48:1-48:10\"\u003e\n\u003cspan id=\"github\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#github\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eGitHub\u003c/h3\u003e\n\u003cp data-sourcepos=\"50:1-50:65\"\u003e\u003ciframe id=\"qiita-embed-content__848d6d12e7e886033936edb78056bec1\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__848d6d12e7e886033936edb78056bec1\" data-content=\"https%3A%2F%2Fdev.classmethod.jp%2Farticles%2Fgithub-cli-telemetry-opt-out%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"52:1-52:237\"\u003eGitHub CLI v2.91.0でコマンド利用状況の擬似匿名テレメトリ収集がデフォルトで有効になったため、環境変数の設定やコマンドラインによるオプトアウト方法をまとめた記事です。\u003c/p\u003e\n\u003chr data-sourcepos=\"53:1-54:0\"\u003e\n\u003cp data-sourcepos=\"55:1-55:132\"\u003e\u003ciframe id=\"qiita-embed-content__ed88600574c2a5b2ccb2751100a23868\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__ed88600574c2a5b2ccb2751100a23868\" data-content=\"https%3A%2F%2Fgithub.blog%2Fchangelog%2F2026-05-05-deprecation-notice-code_scanning_upload-field-will-be-removed-from-rate_limit-api-endpoint%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003chr data-sourcepos=\"57:1-58:0\"\u003e\n\u003cp data-sourcepos=\"59:1-59:132\"\u003e\u003ciframe id=\"qiita-embed-content__070297f9500585adda19f3cf383f7b71\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__070297f9500585adda19f3cf383f7b71\" data-content=\"https%3A%2F%2Fgithub.blog%2Fchangelog%2F2026-05-05-code-to-cloud-risk-visibility-with-microsoft-defender-for-cloud-is-now-generally-available%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003chr data-sourcepos=\"61:1-62:0\"\u003e\n\u003cp data-sourcepos=\"63:1-63:105\"\u003e\u003ciframe id=\"qiita-embed-content__57fc50d974df18dafd4dd878185e28c9\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__57fc50d974df18dafd4dd878185e28c9\" data-content=\"https%3A%2F%2Fgithub.blog%2Fchangelog%2F2026-05-05-dependency-scanning-with-github-mcp-server-is-in-public-preview%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003chr data-sourcepos=\"65:1-66:0\"\u003e\n\u003cp data-sourcepos=\"67:1-67:107\"\u003e\u003ciframe id=\"qiita-embed-content__caa5fb3a50e8b2052b1acc8497840797\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__caa5fb3a50e8b2052b1acc8497840797\" data-content=\"https%3A%2F%2Fgithub.blog%2Fchangelog%2F2026-05-05-secret-scanning-with-github-mcp-server-is-now-generally-available%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"69:1-69:327\"\u003eGitHub MCP サーバーのシークレットスキャン機能が一般提供となり、GitHub Copilot CLI や Visual Studio Code などの AI コーディングエージェントで、コミット前に認証情報などの漏洩シークレットをスキャンできるようになったことが紹介されています。\u003c/p\u003e\n\u003chr data-sourcepos=\"70:1-71:0\"\u003e\n\u003ch3 data-sourcepos=\"72:1-72:10\"\u003e\n\u003cspan id=\"docker\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#docker\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eDocker\u003c/h3\u003e\n\u003cp data-sourcepos=\"74:1-74:72\"\u003e\u003ciframe id=\"qiita-embed-content__adc7a0790d1bc8c159d82f35f720a268\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__adc7a0790d1bc8c159d82f35f720a268\" data-content=\"https%3A%2F%2Fwww.docker.com%2Fblog%2Fblog-generate-images-locally-dmr-open-webui%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"76:1-76:271\"\u003eDocker Model Runnerを使用することで、Open WebUIと組み合わせてローカルマシン上で画像生成モデルを実行し、クラウドサービスに依存することなくプライベートな環境でAI画像生成ができるようになります。\u003c/p\u003e\n\u003chr data-sourcepos=\"77:1-78:0\"\u003e\n\u003ch3 data-sourcepos=\"79:1-79:7\"\u003e\n\u003cspan id=\"zed\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#zed\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eZed\u003c/h3\u003e\n\u003cp data-sourcepos=\"81:1-81:50\"\u003e\u003ciframe id=\"qiita-embed-content__2ec91d49146feda4809330e11f00d571\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__2ec91d49146feda4809330e11f00d571\" data-content=\"https%3A%2F%2Fzed.dev%2Fblog%2Fnot-building-ai-for-the-money\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"83:1-83:265\"\u003eZed開発チームが、AI機能を収益目的ではなく開発体験の理想を実現するために構築しており、人間とAIエージェントが協力して働けるコラボレーティブな環境を目指していることを説明しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"84:1-85:0\"\u003e\n\u003ch3 data-sourcepos=\"86:1-86:13\"\u003e\n\u003cspan id=\"ツール\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%84%E3%83%BC%E3%83%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eツール\u003c/h3\u003e\n\u003cp data-sourcepos=\"88:1-88:57\"\u003e\u003ciframe id=\"qiita-embed-content__12f09ab533ef323e1261788081facaea\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__12f09ab533ef323e1261788081facaea\" data-content=\"https%3A%2F%2Fforest.watch.impress.co.jp%2Fdocs%2Fnews%2F2106292.html\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"90:1-90:277\"\u003eMicrosoftが開発したCLIテキストエディター「Edit」v2.0.0がリリースされ、待望の構文ハイライト機能が追加されるとともに、行の移動や複数行のインデント操作などの便利なキーボード機能も実装されました。\u003c/p\u003e\n\u003cp data-sourcepos=\"92:1-93:252\"\u003e\u003cstrong\u003e感想:\u003c/strong\u003e\u003cbr\u003e\n実はARM版Mac用のバイナリもGitHubでは公開されている。現状一度起動したあと、コンソールのバッファが少しおかしくなることは確認しているが、大きな問題は自分の環境では出ていない。\u003c/p\u003e\n\u003chr data-sourcepos=\"94:1-95:0\"\u003e\n\u003cp data-sourcepos=\"96:1-96:33\"\u003e\u003ciframe id=\"qiita-embed-content__6619f18543d24bfd93b8f80703d4f797\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__6619f18543d24bfd93b8f80703d4f797\" data-content=\"https%3A%2F%2Fgithub.com%2Fbruin-data%2Fdac\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"98:1-98:298\"\u003eDACはYAMLとTSXを使用してインタラクティブなダッシュボードを構築できるダッシュボード・アズ・コード・ツールであり、AI エージェントが信頼性の高いレビュー可能なダッシュボードを作成するために設計されています。\u003c/p\u003e\n\u003chr data-sourcepos=\"99:1-100:0\"\u003e\n\u003ch2 data-sourcepos=\"101:1-101:5\"\u003e\n\u003cspan id=\"ai\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#ai\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eAI\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"103:1-103:13\"\u003e\n\u003cspan id=\"jetbrains\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#jetbrains\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eJetBrains\u003c/h3\u003e\n\u003cp data-sourcepos=\"105:1-105:90\"\u003e\u003ciframe id=\"qiita-embed-content__d332e59fd863ec5ce476391e875d996f\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__d332e59fd863ec5ce476391e875d996f\" data-content=\"https%3A%2F%2Fblog.jetbrains.com%2Fai%2F2026%2F05%2Fstop-sending-ide-catchable-ai-code-errors-to-review%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"107:1-107:499\"\u003eAI コーディング ツールは生産性を向上させる一方で、コード レビュー プロセスに新たな問題をもたらしています。AI が生成したコードには、構造エラーや硬化された値など、人間が書いたコードにはない独特のエラー パターンが含まれています。レビュアーの判断能力は有限であり、毎日より多くのコードがレビューに到達しているため、その負担が増加しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"109:1-109:488\"\u003e解決策として、自動化された構造および静的分析チェックを、プルリクエスト前の開発環境で実施することが効果的です。全プロジェクトの包括的な解析を行う IDE を使用することで、AI が生成したコードを含むすべてのコードを検証できます。これにより、レビュアーの貴重な時間と判断力を、自動化では対応できない重要な問題に集中させることができます。\u003c/p\u003e\n\u003chr data-sourcepos=\"110:1-111:0\"\u003e\n\u003ch3 data-sourcepos=\"112:1-112:10\"\u003e\n\u003cspan id=\"openai\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#openai\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eOpenAI\u003c/h3\u003e\n\u003cp data-sourcepos=\"114:1-114:69\"\u003e\u003cstrong\u003eGPT-5.5 Instant: smarter, clearer, and more personalized | OpenAI\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"116:1-116:41\"\u003e\u003ciframe id=\"qiita-embed-content__24c8191db1fa9d635c99fc0f6ed1c06b\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__24c8191db1fa9d635c99fc0f6ed1c06b\" data-content=\"https%3A%2F%2Fopenai.com%2Findex%2Fgpt-5-5-instant%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"118:1-118:866\"\u003eOpenAIは、ChatGPTのデフォルトモデルを「GPT-5.5 Instant」に更新しました。このモデルは、より正確で信頼性の高い回答を提供し、前のバージョンと比べて幻覚的な主張を52.5%削減しています。応答がより簡潔で自然な会話調になり、不要な冗長性が減少しました。また、ユーザーの過去のチャットやファイル、Gmailなどのコンテキストを活用して、より個人的で関連性の高い提案ができるようになりました。新しく「メモリソース」機能が導入され、ユーザーはどの情報が回答をカスタマイズするために使用されたかを確認し、管理することができます。有料ユーザーにはこれらの高度なパーソナライゼーション機能が段階的に展開されています。\u003c/p\u003e\n\u003chr data-sourcepos=\"119:1-120:0\"\u003e\n\u003cp data-sourcepos=\"121:1-121:40\"\u003e\u003cstrong\u003eNew ways to buy ChatGPT ads | OpenAI\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"123:1-123:53\"\u003e\u003ciframe id=\"qiita-embed-content__b43a524445aa9dbdf5228e2f068fa1b2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__b43a524445aa9dbdf5228e2f068fa1b2\" data-content=\"https%3A%2F%2Fopenai.com%2Findex%2Fnew-ways-to-buy-chatgpt-ads%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"125:1-125:316\"\u003eOpenAIはChatGPTの広告プラットフォームを拡充し、パートナーや新しいセルフサーブ広告マネージャーを通じた購入方法、クリック単価（CPC）入札、測定ツールの充実により、企業がより柔軟にキャンペーンを管理できるようにしました。\u003c/p\u003e\n\u003chr data-sourcepos=\"126:1-127:0\"\u003e\n\u003ch3 data-sourcepos=\"128:1-128:13\"\u003e\n\u003cspan id=\"microsoft\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#microsoft\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eMicrosoft\u003c/h3\u003e\n\u003cp data-sourcepos=\"130:1-130:129\"\u003e\u003ciframe id=\"qiita-embed-content__133085b7d20dd9d626f0243c7116a44e\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__133085b7d20dd9d626f0243c7116a44e\" data-content=\"https%3A%2F%2Ftechcommunity.microsoft.com%2Fblog%2Fazure-ai-foundry-blog%2Fintroducing-openais-newest-chat-model-in-microsoft-foundry%2F4516848\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"132:1-132:335\"\u003eOpenAIの最新チャットモデルGPT-5.5 Instantが「GPT-chat-latest」という名称でMicrosoft Foundryでの提供が開始され、幻覚の削減、ツール呼び出しの改善、より効率的な応答生成など、従来のモデルと比較して複数の面で性能が向上したことが紹介されています。\u003c/p\u003e\n\u003chr data-sourcepos=\"133:1-134:0\"\u003e\n\u003ch3 data-sourcepos=\"135:1-135:10\"\u003e\n\u003cspan id=\"google\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#google\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eGoogle\u003c/h3\u003e\n\u003cp data-sourcepos=\"137:1-137:88\"\u003e\u003ciframe id=\"qiita-embed-content__98c4905833c59b8ca4dd718b4a692cac\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__98c4905833c59b8ca4dd718b4a692cac\" data-content=\"https%3A%2F%2Fblog.google%2Finnovation-and-ai%2Ftechnology%2Fdevelopers-tools%2Fevent-driven-webhooks%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"139:1-139:331\"\u003eGoogle Gemini APIは、長時間実行されるタスクの完了を効率的に追跡するため、ポーリングの代わりにイベント駆動型Webhooksを導入しました。これにより、ジョブ完了時にサーバーにリアルタイムのHTTP POSTペイロードがプッシュされるようになります。\u003c/p\u003e\n\u003chr data-sourcepos=\"140:1-141:0\"\u003e\n\u003cp data-sourcepos=\"142:1-142:97\"\u003e\u003ciframe id=\"qiita-embed-content__0a85a7ad6f0e950ce6934bde3ba38a4b\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__0a85a7ad6f0e950ce6934bde3ba38a4b\" data-content=\"https%3A%2F%2Fblog.google%2Finnovation-and-ai%2Ftechnology%2Fdevelopers-tools%2Fmulti-token-prediction-gemma-4%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"144:1-144:748\"\u003eGemma 4は、マルチトークン予測（MTP）ドラフタを導入することで、推論速度を最大3倍高速化しました。標準的なLLM推論はメモリ帯域幅に制限されており、大きなモデルがトークンを1つ生成する間に軽量なドラフタが複数のトークンを予測し、ターゲットモデルがそれらを並列に検証する仕組みです。このアーキテクチャにより、チャット応答の遅延を削減し、オンデバイス実行でバッテリー寿命を延長しながら、出力品質は完全に維持されます。開発者はHugging FaceやKaggleからモデルを入手し、Transformers、MLX、vLLMなど複数のフレームワークで利用できます。\u003c/p\u003e\n\u003chr data-sourcepos=\"145:1-146:0\"\u003e\n\u003ch3 data-sourcepos=\"147:1-147:13\"\u003e\n\u003cspan id=\"anthropic\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#anthropic\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eAnthropic\u003c/h3\u003e\n\u003cp data-sourcepos=\"149:1-149:66\"\u003e\u003ciframe id=\"qiita-embed-content__7d014f6d4331c22dd6dc1f81b0eb0ba7\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__7d014f6d4331c22dd6dc1f81b0eb0ba7\" data-content=\"https%3A%2F%2Fclaude.com%2Fblog%2Fdeploying-claude-across-financial-services\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"151:1-151:168\"\u003e金融サービス業界の企業がClaudeを様々なビジネスプロセスに活用している実例と、段階的な導入戦略を紹介するガイドです。\u003c/p\u003e\n\u003chr data-sourcepos=\"152:1-153:0\"\u003e\n\u003ch3 data-sourcepos=\"154:1-154:22\"\u003e\n\u003cspan id=\"論文その他\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E8%AB%96%E6%96%87%E3%81%9D%E3%81%AE%E4%BB%96\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e論文・その他\u003c/h3\u003e\n\u003cp data-sourcepos=\"156:1-156:40\"\u003e\u003ciframe id=\"qiita-embed-content__d3b5ac3f20e63892f45eb4d4e0288d6e\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__d3b5ac3f20e63892f45eb4d4e0288d6e\" data-content=\"https%3A%2F%2Fai-data-base.com%2Farchives%2F108080\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"158:1-158:418\"\u003eAIコーディングエージェントに与えるルール設計について、25,532個のルール分析と5,000回以上の実行検証を行った研究から、「何をするな」という負の制約が「何をせよ」という正の指示よりも有効であり、ルールの内容より「存在する」こと自体のプライミング効果が重要であることが明らかにされています。\u003c/p\u003e\n\u003chr data-sourcepos=\"159:1-160:0\"\u003e\n\u003cp data-sourcepos=\"161:1-161:40\"\u003e\u003ciframe id=\"qiita-embed-content__600114112167e384a1c9752f8a0c0f87\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__600114112167e384a1c9752f8a0c0f87\" data-content=\"https%3A%2F%2Fai-data-base.com%2Farchives%2F108074\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"163:1-163:320\"\u003e欧州の3社による実務調査から、ソフトウェア開発現場でLLMを「アシスタント」として責任を持って活用するための7つの推奨事項が導き出されており、その中核は人間中心の運用体制、検証の仕組み、組織的な学び合いの構築にあります。\u003c/p\u003e\n\u003chr data-sourcepos=\"164:1-165:0\"\u003e\n\u003cp data-sourcepos=\"166:1-166:61\"\u003e\u003ciframe id=\"qiita-embed-content__1d0850d6fff8d4b1d038a0346d81e38b\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__1d0850d6fff8d4b1d038a0346d81e38b\" data-content=\"https%3A%2F%2Fwww.oreilly.com%2Fradar%2Fradar-trends-to-watch-may-2026%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"168:1-168:223\"\u003e2026年5月のテック業界のトレンドを、AI、ソフトウェア開発、セキュリティ、インフラ、ウェブ、生物学、ロボットなどの広範なジャンルから紹介している記事です。\u003c/p\u003e\n\u003chr data-sourcepos=\"169:1-171:0\"\u003e\n\u003ch2 data-sourcepos=\"172:1-172:18\"\u003e\n\u003cspan id=\"エンジニア\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eエンジニア\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"174:1-174:18\"\u003e\n\u003cspan id=\"aiとお仕事\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#ai%E3%81%A8%E3%81%8A%E4%BB%95%E4%BA%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eAIとお仕事\u003c/h3\u003e\n\u003cp data-sourcepos=\"176:1-176:54\"\u003e\u003ciframe id=\"qiita-embed-content__24d92368e245b40a36270a9dee5c4958\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__24d92368e245b40a36270a9dee5c4958\" data-content=\"https%3A%2F%2Flarsfaye.com%2Farticles%2Fagentic-coding-is-a-trap\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"178:1-178:911\"\u003eAI主導のコーディング業務は、一見効率的に見えますが、深刻な落とし穴を内包しています。開発者がAIエージェントに全面依存すると、批判的思考力やコーディングスキルが急速に低下することが研究で示されています。特に、AIを効果的に管理するにはコーディング技術が必須なのに、その使用が逆にそのスキルを損なわせるという矛盾が生じています。また、特定のモデル提供企業への依存は「ベンダーロックイン」につながり、トークンコストの不確実性も問題です。著者は、AIをあくまで補助的な道具として位置づけ、開発者自身が実装に関わることの重要性を強調しています。スキル維持と品質向上のバランスを取ることが、長期的な成功の鍵だと指摘しています。\u003c/p\u003e\n\u003cp data-sourcepos=\"180:1-180:11\"\u003e\u003cstrong\u003e感想:\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"182:1-182:422\"\u003e普通の開発者は複数のAIベンダーのサブスクを契約したりはしないし、結局ツールなので使い方のノウハウが資産化するのは仕方がないので、ベンダーロックインされるのはそう。個人的にはCLIベースのツールにはあまり深入りしないで、IDE拡張でめんどくさいことだけに適当に使うのが良いかなと思っています。\u003c/p\u003e\n\u003chr data-sourcepos=\"183:1-184:0\"\u003e\n\u003cp data-sourcepos=\"185:1-185:57\"\u003e\u003ciframe id=\"qiita-embed-content__5eed243ac0c6d357dfe6a2f0cd95bdd9\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__5eed243ac0c6d357dfe6a2f0cd95bdd9\" data-content=\"https%3A%2F%2Fsyu-m-5151.hatenablog.com%2Fentry%2F2026%2F05%2F05%2F172501\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"187:1-187:841\"\u003eこの記事は、AIエージェントとの開発時代における要件管理の新しいアプローチについて論じています。従来のドキュメントベースの要件定義では、書いた瞬間から陳腐化し読まれなくなるという課題がありました。そこで著者は、要件をドキュメントだけでなく、型定義やテスト、CI、lintルール、スキーマなど、実装全体に散らして組み込むべきだと主張しています。こうすることで要件が毎日実行・検証されるようになり、AIに対しても明示的で検証可能な形で伝わるようになります。決定論的に守るべき事柄は自動化で強制し、確率的な部分はプロンプトで導くというハイブリッドアプローチが有効だと述べています。\u003c/p\u003e\n\u003chr data-sourcepos=\"188:1-189:0\"\u003e\n\u003ch2 data-sourcepos=\"190:1-190:15\"\u003e\n\u003cspan id=\"クラウド\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eクラウド\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"192:1-192:9\"\u003e\n\u003cspan id=\"azure\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#azure\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eAzure\u003c/h3\u003e\n\u003cp data-sourcepos=\"194:1-194:92\"\u003e\u003ciframe id=\"qiita-embed-content__a3a90559866b8d22663d11eef2a095f6\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__a3a90559866b8d22663d11eef2a095f6\" data-content=\"https%3A%2F%2Fdeveloper.microsoft.com%2Fblog%2Fazure-cosmos-db-conf-2026-recap-lessons-from-production\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"196:1-196:343\"\u003eAzure Cosmos DB Conf 2026では、OpenAIやVercelなどの企業が実運用から得た知見を共有し、スケーリングの問題はほぼすべてが設計上の欠陥であり、パーティションキーの適切な設計やクエリ最適化によってスケール課題が解決されることが一貫して示されました。\u003c/p\u003e\n\u003chr data-sourcepos=\"197:1-198:0\"\u003e\n\u003cp data-sourcepos=\"199:1-199:32\"\u003e\u003ciframe id=\"qiita-embed-content__9e1e8c2745cd4e3d07262b9c020b2c7c\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__9e1e8c2745cd4e3d07262b9c020b2c7c\" data-content=\"https%3A%2F%2Fkogelog.com%2F20260506-01%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"201:1-201:246\"\u003eAzure PowerShell の Az モジュール v15.6.0 がリリースされ、複数のサブモジュールの更新、新機能の追加、パラメーターの改善など、様々な機能拡張と重大な変更の通知が含まれています。\u003c/p\u003e\n\u003chr data-sourcepos=\"202:1-203:0\"\u003e\n\u003ch2 data-sourcepos=\"204:1-204:5\"\u003e\n\u003cspan id=\"os\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#os\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eOS\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"206:1-206:9\"\u003e\n\u003cspan id=\"macos\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#macos\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003emacOS\u003c/h3\u003e\n\u003cp data-sourcepos=\"208:1-208:41\"\u003e\u003ciframe id=\"qiita-embed-content__344890c329982b2247c92145dc92b9c2\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__344890c329982b2247c92145dc92b9c2\" data-content=\"https%3A%2F%2Fgithub.com%2Fdarrylmorley%2Fwhatcable\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"210:1-210:310\"\u003eこのプロジェクトは、Macに接続されたUSB-Cケーブルの性能を分かりやすく表示するメニューバーアプリで、ケーブルの通信速度や給電能力、接続デバイスの情報をIOKitから取得して、充電が遅い原因などを診断することができます。\u003c/p\u003e\n\u003chr data-sourcepos=\"211:1-212:0\"\u003e\n\u003cp data-sourcepos=\"213:1-213:76\"\u003e\u003ciframe id=\"qiita-embed-content__5899d9efb5be305bad830bc9ba81d89b\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__5899d9efb5be305bad830bc9ba81d89b\" data-content=\"https%3A%2F%2Fapplech2.com%2Farchives%2F20260505-macos-26-5-tahoe-and-ios-26-5-rc.html\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"215:1-215:209\"\u003eAppleが開発者向けに「macOS 26.5 Tahoe」や「iOS/iPadOS 26.5」などの複数のOSのリリース候補版を公開し、来週中にリリースされる予定であることを報じています。\u003c/p\u003e\n\u003chr data-sourcepos=\"216:1-217:0\"\u003e\n\u003ch3 data-sourcepos=\"218:1-218:11\"\u003e\n\u003cspan id=\"windows-1\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#windows-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eWindows\u003c/h3\u003e\n\u003cp data-sourcepos=\"220:1-220:103\"\u003e\u003ciframe id=\"qiita-embed-content__112f9644acde06c82bc48f8368d37964\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__112f9644acde06c82bc48f8368d37964\" data-content=\"https%3A%2F%2Ftechcommunity.microsoft.com%2Fblog%2Fwindows-itpro-blog%2Fwindows-news-you-can-use-april-2026%2F4516352\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"222:1-222:239\"\u003e2026年4月のWindows更新情報をまとめた記事で、Windows 11やWindows Serverの新機能、セキュリティ改善、AI機能の強化、生産性向上に関連した最新のアップデート内容を紹介しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"223:1-224:0\"\u003e\n\u003ch2 data-sourcepos=\"225:1-225:45\"\u003e\n\u003cspan id=\"アプリケーションソフトウェア\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eアプリケーションソフトウェア\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"227:1-227:20\"\u003e\n\u003cspan id=\"google-workspace\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#google-workspace\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eGoogle Workspace\u003c/h3\u003e\n\u003cp data-sourcepos=\"229:1-229:102\"\u003e\u003ciframe id=\"qiita-embed-content__3408b5bc68059b682607e56e4bbcd2bd\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__3408b5bc68059b682607e56e4bbcd2bd\" data-content=\"https%3A%2F%2Fworkspaceupdates.googleblog.com%2F2026%2F05%2Fset-custom-instructions-for-gemini-in-Google-Docs.html\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"231:1-231:239\"\u003eGoogle Docsで、ユーザーがGeminiに対して独自の指示を保存できるようになり、スタイルや文体、フォーマットの好みを一度設定すれば、毎回の会話で繰り返す必要がなくなります。\u003c/p\u003e\n\u003chr data-sourcepos=\"232:1-233:0\"\u003e\n\u003cp data-sourcepos=\"234:1-234:146\"\u003e\u003ciframe id=\"qiita-embed-content__1f24a815d0fe61c1e60afe6f3391b886\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__1f24a815d0fe61c1e60afe6f3391b886\" data-content=\"https%3A%2F%2Fworkspaceupdates.googleblog.com%2F2026%2F04%2Frequire-explicit-consent-for-take-notes-with-Gemini-recordings-and-transcripts-in-Google-Meet.html\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"236:1-236:233\"\u003eGoogle Meetで、管理者が会議参加者から自動的なメモ記録、録画、文字起こしについての明示的な同意を求めることができる新機能が導入されることについて紹介しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"237:1-238:0\"\u003e\n\u003ch2 data-sourcepos=\"239:1-239:21\"\u003e\n\u003cspan id=\"ネットワーク\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eネットワーク\u003c/h2\u003e\n\u003ch3 data-sourcepos=\"241:1-241:13\"\u003e\n\u003cspan id=\"tailscale\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#tailscale\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eTailscale\u003c/h3\u003e\n\u003cp data-sourcepos=\"243:1-243:34\"\u003e\u003ciframe id=\"qiita-embed-content__520ae79baa005b3de8c31989fe87143c\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__520ae79baa005b3de8c31989fe87143c\" data-content=\"https%3A%2F%2Fmq1.dev%2Fentry%2Fj7zvrsp48lb%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"245:1-245:410\"\u003eTailscaleを実際に使ってきた著者が、DNS設定の不具合、RFC違反の実装、ネットワークアドレスの強制削除、iptablesの乱雑な管理、MTUのハードコード、ACL機能の不足、SSO強制など、複数の技術的な問題点を詳細に指摘しながらも、結局は便利さのために使い続けざるを得ないというジレンマを綴った記事です。\u003c/p\u003e\n\u003chr data-sourcepos=\"246:1-248:0\"\u003e\n\u003ch2 data-sourcepos=\"249:1-249:24\"\u003e\n\u003cspan id=\"業界動向時事\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E6%A5%AD%E7%95%8C%E5%8B%95%E5%90%91%E6%99%82%E4%BA%8B\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e業界動向・時事\u003c/h2\u003e\n\u003cp data-sourcepos=\"251:1-251:60\"\u003e\u003ciframe id=\"qiita-embed-content__625fd8cdfdb00b9ef8845fbaa2ef9c88\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__625fd8cdfdb00b9ef8845fbaa2ef9c88\" data-content=\"https%3A%2F%2Fwww.nikkei.com%2Farticle%2FDGXZQOUC174GL0X10C26A4000000%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"253:1-253:336\"\u003eアンソロピックの新型AI「Mythos」による脆弱性検知の急増で、米国立標準技術研究所（NIST）が従来のすべての脆弱性分析を取りやめ、緊急度の高い案件に限定する方針に転換したことで、企業は自ら優先順位をつけて対応する必要が生まれています。\u003c/p\u003e\n\u003cp data-sourcepos=\"255:1-255:11\"\u003e\u003cstrong\u003e感想:\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"257:1-257:239\"\u003e人間がボトルネックになっている。現状AIが見つけてくるのは「脆弱性らしきもの」にすぎないので、それが本当に修正するべき脆弱性かは結局人間が判断しなければならない。\u003c/p\u003e\n\u003chr data-sourcepos=\"258:1-259:0\"\u003e\n\u003cp data-sourcepos=\"260:1-260:41\"\u003e\u003ciframe id=\"qiita-embed-content__f2f035ac14ae6994d612896c38fc4d89\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__f2f035ac14ae6994d612896c38fc4d89\" data-content=\"https%3A%2F%2Fcourrier.jp%2Fnews%2Farchives%2F444570%2F\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"262:1-262:235\"\u003eアンソロピック社のCEOダリオ・アモデイが、AIが持つ可能性とリスク、および民主主義を守るための責任あるAI開発の必要性についての信念を詳述したインタビュー記事です。\u003c/p\u003e\n\u003chr data-sourcepos=\"263:1-264:0\"\u003e\n\u003cp data-sourcepos=\"265:1-265:45\"\u003e\u003ciframe id=\"qiita-embed-content__240a7f2cd760a4ca2d50d7bb1d647a07\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__240a7f2cd760a4ca2d50d7bb1d647a07\" data-content=\"https%3A%2F%2Fforbesjapan.com%2Farticles%2Fdetail%2F96885\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"267:1-267:249\"\u003eアンソロピックが評価額9000億ドル（約141兆円）での資金調達を実施し、OpenAIを上回る世界最高値のAI企業となる見通しについて、その成長の実績と根拠について詳しく解説しています。\u003c/p\u003e\n\u003chr data-sourcepos=\"268:1-269:0\"\u003e\n\u003cp data-sourcepos=\"270:1-270:95\"\u003e\u003cstrong\u003eアップル､プロセッサー製造でインテルとサムスン採用検討-関係者\u003c/strong\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"272:1-272:187\"\u003e\u003ciframe id=\"qiita-embed-content__c2da1d870c0d19fccedc45b3762b7e62\" src=\"https://qiita.com/embed-contents/link-card#qiita-embed-content__c2da1d870c0d19fccedc45b3762b7e62\" data-content=\"https%3A%2F%2Fwww.bloomberg.com%2Fjp%2Fnews%2Farticles%2F2026-05-05%2FTEJMI4T9NJLS00%3Ftaid%3D69f95d33563ff90001ebd53d%26utm_campaign%3Dtrueanthem%26utm_content%3Djapan%26utm_medium%3Dsocial%26utm_source%3Dtwitter%23gsc.tab%3D0\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\" style=\"width:100%;\" height=\"29\"\u003e\n\u003c/iframe\u003e\n\u003c/p\u003e\n\u003cp data-sourcepos=\"274:1-274:91\"\u003eまぁ、TSMCの製造余裕がなくなってきているという事なのでしょう。\u003c/p\u003e\n\u003chr data-sourcepos=\"275:1-275:3\"\u003e\n","body":"![プログラミング雑記2026年5月6日.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27037/35fe578f-cff5-437f-8403-bfbfad99cd3e.jpeg)\n\n本日も、ネットに流れるトピックから個人的に興味を引かれたものを拾っていきます。\n\n連休も気がつけば最終日。シティスカ2やっていたら無駄に時間が溶けていました…\n\nこの記事への感想等コメントで頂けるとありがたいです。\n\n## プログラミング\n\n### Java\n\nhttps://devblogs.microsoft.com/java/java-openjdk-april-2026-patch-security-update/\n\nMicrosoftは2026年4月のOpenJDKパッチとセキュリティアップデートをリリースし、複数のバージョン（25、21、17、11）で修正と改善を提供しています。\n___\n\nhttps://blog.jetbrains.com/idea/2026/05/java-annotated-monthly-may-2026/\n\n2026年5月の「Java Annotated Monthly」は、業界を大きく変えているAIコーディングエージェントの採用を中心に取り扱っています。EmillyBache氏による特集では、AI支援コーディング設計における「ハーネス」の重要性や、テスト駆動開発への影響について論じられています。Java26のリリース情報やKotlinの最新動向、Spring関連の技術更新など、実践的なコンテンツが豊富に提供されています。また、JAX、Devoxx UK、GeeCon、JNationなど複数の国際的なカンファレンスの情報が掲載されており、AI開発における信頼性や倫理的課題についても重要なテーマとして取り上げられています。開発者がこれらのツールを効果的に活用するための知識と最新情報が充実した内容となっています。\n___\n\n### JavaScript\n\nhttps://nodejs.org/en/blog/release/v26.0.0\n\nNode.js 26.0.0がリリースされました。このバージョンは現在のリリース版として、10月に長期サポート版になるまで今後6ヶ月間利用できます。主な機能追加としまして、Temporal APIがデフォルトで有効になりました。これは従来のDateオブジェクトに代わる、より堅牢で機能豊富な現代的な日時APIです。また、JavaScriptエンジンのV8が14.6にアップデートされており、Chromium 134に含まれるバージョンとなっています。HTTP クライアント実装であるUndiciも8.0.2へアップデートされています。\n\n複数の非推奨機能が廃止されました。例えば、`http.Server.prototype.writeHeader()`は完全に削除されましたので、`writeHead()`を使用してください。従来の`_stream_*`モジュールも廃止されています。さらに、GCC要件が13.2以上に引き上げられ、Python 3.9のサポートも終了しました。\n\nこのバージョンでは多くのセキュリティ関連の改善とバグ修正が含まれており、開発者に新機能の活用と既存アプリケーションへの影響を評価することが推奨されています。\n___\n\n### Windows\n\nhttps://devblogs.microsoft.com/ifdef-windows/announcing-the-winapp-vs-code-extension-run-debug-and-package-windows-apps-in-vs-code/\n\nMicrosoft が WinApp VS Code 拡張機能を公開プレビューで発表したもので、VS Code から直接 Windows アプリの実行、デバッグ、パッケージ化ができるようになる機能を紹介しています。\n___\n\n### Claude Code\n\nhttps://dev.classmethod.jp/articles/claude-code-env-scrub-secret-leak-defense-in-depth/\n\nClaude Code v2.1.83で追加されたCLAUDE_CODE_SUBPROCESS_ENV_SCRUB機能による環境変数の漏洩防止について検証し、secretlintやgitleaksと組み合わせた多層防御の構成を紹介しています。\n___\n\n### GitHub\n\nhttps://dev.classmethod.jp/articles/github-cli-telemetry-opt-out/\n\nGitHub CLI v2.91.0でコマンド利用状況の擬似匿名テレメトリ収集がデフォルトで有効になったため、環境変数の設定やコマンドラインによるオプトアウト方法をまとめた記事です。\n___\n\nhttps://github.blog/changelog/2026-05-05-deprecation-notice-code_scanning_upload-field-will-be-removed-from-rate_limit-api-endpoint/\n\n___\n\nhttps://github.blog/changelog/2026-05-05-code-to-cloud-risk-visibility-with-microsoft-defender-for-cloud-is-now-generally-available/\n\n___\n\nhttps://github.blog/changelog/2026-05-05-dependency-scanning-with-github-mcp-server-is-in-public-preview/\n\n___\n\nhttps://github.blog/changelog/2026-05-05-secret-scanning-with-github-mcp-server-is-now-generally-available/\n\nGitHub MCP サーバーのシークレットスキャン機能が一般提供となり、GitHub Copilot CLI や Visual Studio Code などの AI コーディングエージェントで、コミット前に認証情報などの漏洩シークレットをスキャンできるようになったことが紹介されています。\n___\n\n### Docker\n\nhttps://www.docker.com/blog/blog-generate-images-locally-dmr-open-webui/\n\nDocker Model Runnerを使用することで、Open WebUIと組み合わせてローカルマシン上で画像生成モデルを実行し、クラウドサービスに依存することなくプライベートな環境でAI画像生成ができるようになります。\n___\n\n### Zed\n\nhttps://zed.dev/blog/not-building-ai-for-the-money\n\nZed開発チームが、AI機能を収益目的ではなく開発体験の理想を実現するために構築しており、人間とAIエージェントが協力して働けるコラボレーティブな環境を目指していることを説明しています。\n___\n\n### ツール\n\nhttps://forest.watch.impress.co.jp/docs/news/2106292.html\n\nMicrosoftが開発したCLIテキストエディター「Edit」v2.0.0がリリースされ、待望の構文ハイライト機能が追加されるとともに、行の移動や複数行のインデント操作などの便利なキーボード機能も実装されました。\n\n**感想:**\n実はARM版Mac用のバイナリもGitHubでは公開されている。現状一度起動したあと、コンソールのバッファが少しおかしくなることは確認しているが、大きな問題は自分の環境では出ていない。\n___\n\nhttps://github.com/bruin-data/dac\n\nDACはYAMLとTSXを使用してインタラクティブなダッシュボードを構築できるダッシュボード・アズ・コード・ツールであり、AI エージェントが信頼性の高いレビュー可能なダッシュボードを作成するために設計されています。\n___\n\n## AI\n\n### JetBrains\n\nhttps://blog.jetbrains.com/ai/2026/05/stop-sending-ide-catchable-ai-code-errors-to-review/\n\nAI コーディング ツールは生産性を向上させる一方で、コード レビュー プロセスに新たな問題をもたらしています。AI が生成したコードには、構造エラーや硬化された値など、人間が書いたコードにはない独特のエラー パターンが含まれています。レビュアーの判断能力は有限であり、毎日より多くのコードがレビューに到達しているため、その負担が増加しています。\n\n解決策として、自動化された構造および静的分析チェックを、プルリクエスト前の開発環境で実施することが効果的です。全プロジェクトの包括的な解析を行う IDE を使用することで、AI が生成したコードを含むすべてのコードを検証できます。これにより、レビュアーの貴重な時間と判断力を、自動化では対応できない重要な問題に集中させることができます。\n___\n\n### OpenAI\n\n**GPT-5.5 Instant: smarter, clearer, and more personalized | OpenAI**\n\nhttps://openai.com/index/gpt-5-5-instant/\n\nOpenAIは、ChatGPTのデフォルトモデルを「GPT-5.5 Instant」に更新しました。このモデルは、より正確で信頼性の高い回答を提供し、前のバージョンと比べて幻覚的な主張を52.5%削減しています。応答がより簡潔で自然な会話調になり、不要な冗長性が減少しました。また、ユーザーの過去のチャットやファイル、Gmailなどのコンテキストを活用して、より個人的で関連性の高い提案ができるようになりました。新しく「メモリソース」機能が導入され、ユーザーはどの情報が回答をカスタマイズするために使用されたかを確認し、管理することができます。有料ユーザーにはこれらの高度なパーソナライゼーション機能が段階的に展開されています。\n___\n\n**New ways to buy ChatGPT ads | OpenAI**\n\nhttps://openai.com/index/new-ways-to-buy-chatgpt-ads/\n\nOpenAIはChatGPTの広告プラットフォームを拡充し、パートナーや新しいセルフサーブ広告マネージャーを通じた購入方法、クリック単価（CPC）入札、測定ツールの充実により、企業がより柔軟にキャンペーンを管理できるようにしました。\n___\n\n### Microsoft\n\nhttps://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/introducing-openais-newest-chat-model-in-microsoft-foundry/4516848\n\nOpenAIの最新チャットモデルGPT-5.5 Instantが「GPT-chat-latest」という名称でMicrosoft Foundryでの提供が開始され、幻覚の削減、ツール呼び出しの改善、より効率的な応答生成など、従来のモデルと比較して複数の面で性能が向上したことが紹介されています。\n___\n\n### Google\n\nhttps://blog.google/innovation-and-ai/technology/developers-tools/event-driven-webhooks/\n\nGoogle Gemini APIは、長時間実行されるタスクの完了を効率的に追跡するため、ポーリングの代わりにイベント駆動型Webhooksを導入しました。これにより、ジョブ完了時にサーバーにリアルタイムのHTTP POSTペイロードがプッシュされるようになります。\n___\n\nhttps://blog.google/innovation-and-ai/technology/developers-tools/multi-token-prediction-gemma-4/\n\nGemma 4は、マルチトークン予測（MTP）ドラフタを導入することで、推論速度を最大3倍高速化しました。標準的なLLM推論はメモリ帯域幅に制限されており、大きなモデルがトークンを1つ生成する間に軽量なドラフタが複数のトークンを予測し、ターゲットモデルがそれらを並列に検証する仕組みです。このアーキテクチャにより、チャット応答の遅延を削減し、オンデバイス実行でバッテリー寿命を延長しながら、出力品質は完全に維持されます。開発者はHugging FaceやKaggleからモデルを入手し、Transformers、MLX、vLLMなど複数のフレームワークで利用できます。\n___\n\n### Anthropic\n\nhttps://claude.com/blog/deploying-claude-across-financial-services\n\n金融サービス業界の企業がClaudeを様々なビジネスプロセスに活用している実例と、段階的な導入戦略を紹介するガイドです。\n___\n\n### 論文・その他\n\nhttps://ai-data-base.com/archives/108080\n\nAIコーディングエージェントに与えるルール設計について、25,532個のルール分析と5,000回以上の実行検証を行った研究から、「何をするな」という負の制約が「何をせよ」という正の指示よりも有効であり、ルールの内容より「存在する」こと自体のプライミング効果が重要であることが明らかにされています。\n___\n\nhttps://ai-data-base.com/archives/108074\n\n欧州の3社による実務調査から、ソフトウェア開発現場でLLMを「アシスタント」として責任を持って活用するための7つの推奨事項が導き出されており、その中核は人間中心の運用体制、検証の仕組み、組織的な学び合いの構築にあります。\n___\n\nhttps://www.oreilly.com/radar/radar-trends-to-watch-may-2026/\n\n2026年5月のテック業界のトレンドを、AI、ソフトウェア開発、セキュリティ、インフラ、ウェブ、生物学、ロボットなどの広範なジャンルから紹介している記事です。\n___\n\n\n## エンジニア\n\n### AIとお仕事\n\nhttps://larsfaye.com/articles/agentic-coding-is-a-trap\n\nAI主導のコーディング業務は、一見効率的に見えますが、深刻な落とし穴を内包しています。開発者がAIエージェントに全面依存すると、批判的思考力やコーディングスキルが急速に低下することが研究で示されています。特に、AIを効果的に管理するにはコーディング技術が必須なのに、その使用が逆にそのスキルを損なわせるという矛盾が生じています。また、特定のモデル提供企業への依存は「ベンダーロックイン」につながり、トークンコストの不確実性も問題です。著者は、AIをあくまで補助的な道具として位置づけ、開発者自身が実装に関わることの重要性を強調しています。スキル維持と品質向上のバランスを取ることが、長期的な成功の鍵だと指摘しています。\n\n**感想:**\n\n普通の開発者は複数のAIベンダーのサブスクを契約したりはしないし、結局ツールなので使い方のノウハウが資産化するのは仕方がないので、ベンダーロックインされるのはそう。個人的にはCLIベースのツールにはあまり深入りしないで、IDE拡張でめんどくさいことだけに適当に使うのが良いかなと思っています。\n___\n\nhttps://syu-m-5151.hatenablog.com/entry/2026/05/05/172501\n\nこの記事は、AIエージェントとの開発時代における要件管理の新しいアプローチについて論じています。従来のドキュメントベースの要件定義では、書いた瞬間から陳腐化し読まれなくなるという課題がありました。そこで著者は、要件をドキュメントだけでなく、型定義やテスト、CI、lintルール、スキーマなど、実装全体に散らして組み込むべきだと主張しています。こうすることで要件が毎日実行・検証されるようになり、AIに対しても明示的で検証可能な形で伝わるようになります。決定論的に守るべき事柄は自動化で強制し、確率的な部分はプロンプトで導くというハイブリッドアプローチが有効だと述べています。\n___\n\n## クラウド\n\n### Azure\n\nhttps://developer.microsoft.com/blog/azure-cosmos-db-conf-2026-recap-lessons-from-production\n\nAzure Cosmos DB Conf 2026では、OpenAIやVercelなどの企業が実運用から得た知見を共有し、スケーリングの問題はほぼすべてが設計上の欠陥であり、パーティションキーの適切な設計やクエリ最適化によってスケール課題が解決されることが一貫して示されました。\n___\n\nhttps://kogelog.com/20260506-01/\n\nAzure PowerShell の Az モジュール v15.6.0 がリリースされ、複数のサブモジュールの更新、新機能の追加、パラメーターの改善など、様々な機能拡張と重大な変更の通知が含まれています。\n___\n\n## OS\n\n### macOS\n\nhttps://github.com/darrylmorley/whatcable\n\nこのプロジェクトは、Macに接続されたUSB-Cケーブルの性能を分かりやすく表示するメニューバーアプリで、ケーブルの通信速度や給電能力、接続デバイスの情報をIOKitから取得して、充電が遅い原因などを診断することができます。\n___\n\nhttps://applech2.com/archives/20260505-macos-26-5-tahoe-and-ios-26-5-rc.html\n\nAppleが開発者向けに「macOS 26.5 Tahoe」や「iOS/iPadOS 26.5」などの複数のOSのリリース候補版を公開し、来週中にリリースされる予定であることを報じています。\n___\n\n### Windows\n\nhttps://techcommunity.microsoft.com/blog/windows-itpro-blog/windows-news-you-can-use-april-2026/4516352\n\n2026年4月のWindows更新情報をまとめた記事で、Windows 11やWindows Serverの新機能、セキュリティ改善、AI機能の強化、生産性向上に関連した最新のアップデート内容を紹介しています。\n___\n\n## アプリケーションソフトウェア\n\n### Google Workspace\n\nhttps://workspaceupdates.googleblog.com/2026/05/set-custom-instructions-for-gemini-in-Google-Docs.html\n\nGoogle Docsで、ユーザーがGeminiに対して独自の指示を保存できるようになり、スタイルや文体、フォーマットの好みを一度設定すれば、毎回の会話で繰り返す必要がなくなります。\n___\n\nhttps://workspaceupdates.googleblog.com/2026/04/require-explicit-consent-for-take-notes-with-Gemini-recordings-and-transcripts-in-Google-Meet.html\n\nGoogle Meetで、管理者が会議参加者から自動的なメモ記録、録画、文字起こしについての明示的な同意を求めることができる新機能が導入されることについて紹介しています。\n___\n\n## ネットワーク\n\n### Tailscale\n\nhttps://mq1.dev/entry/j7zvrsp48lb/\n\nTailscaleを実際に使ってきた著者が、DNS設定の不具合、RFC違反の実装、ネットワークアドレスの強制削除、iptablesの乱雑な管理、MTUのハードコード、ACL機能の不足、SSO強制など、複数の技術的な問題点を詳細に指摘しながらも、結局は便利さのために使い続けざるを得ないというジレンマを綴った記事です。\n___\n\n\n## 業界動向・時事\n\nhttps://www.nikkei.com/article/DGXZQOUC174GL0X10C26A4000000/\n\nアンソロピックの新型AI「Mythos」による脆弱性検知の急増で、米国立標準技術研究所（NIST）が従来のすべての脆弱性分析を取りやめ、緊急度の高い案件に限定する方針に転換したことで、企業は自ら優先順位をつけて対応する必要が生まれています。\n\n**感想:**\n\n人間がボトルネックになっている。現状AIが見つけてくるのは「脆弱性らしきもの」にすぎないので、それが本当に修正するべき脆弱性かは結局人間が判断しなければならない。\n___\n\nhttps://courrier.jp/news/archives/444570/\n\nアンソロピック社のCEOダリオ・アモデイが、AIが持つ可能性とリスク、および民主主義を守るための責任あるAI開発の必要性についての信念を詳述したインタビュー記事です。\n___\n\nhttps://forbesjapan.com/articles/detail/96885\n\nアンソロピックが評価額9000億ドル（約141兆円）での資金調達を実施し、OpenAIを上回る世界最高値のAI企業となる見通しについて、その成長の実績と根拠について詳しく解説しています。\n___\n\n**アップル､プロセッサー製造でインテルとサムスン採用検討-関係者**\n\nhttps://www.bloomberg.com/jp/news/articles/2026-05-05/TEJMI4T9NJLS00?taid=69f95d33563ff90001ebd53d\u0026utm_campaign=trueanthem\u0026utm_content=japan\u0026utm_medium=social\u0026utm_source=twitter#gsc.tab=0\n\nまぁ、TSMCの製造余裕がなくなってきているという事なのでしょう。\n___\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:42:07+09:00","group":null,"id":"a9b97381d6759fe13f37","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"プログラミング","versions":[]},{"name":"Cloud","versions":[]},{"name":"AI","versions":[]},{"name":"エンジニア","versions":[]}],"title":"プログラミング雑記 2026年5月6日","updated_at":"2026-05-06T10:42:07+09:00","url":"https://qiita.com/ishisaka/items/a9b97381d6759fe13f37","user":{"description":"プログラマです。","facebook_id":"tadahiro.ishisaka","followees_count":37,"followers_count":42,"github_login_name":"ishisaka","id":"ishisaka","items_count":175,"linkedin_id":"tadahiroishisaka","location":"Shizuoka, JAPAN","name":"Tadahiro ishisaka","organization":"㈱BALEEN STUDIO","permanent_id":27037,"profile_image_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/27037/cd9fe48d68c9844d92ce9197241714af5c4f4b09/x_large.png?1764632518","team_only":false,"twitter_screen_name":"ishisaka","website_url":"http://opcdiary.net"},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false},{"rendered_body":"\u003cp data-sourcepos=\"1:1-2:142\"\u003e「遠くに行かなくてもワーケーションはできる」——2026年現在、「都市型ワーケーション」という考え方が広がっています。自宅から電車で30〜60分、特別な手配なしに仕事と観光を融合できる東京近郊スポットが注目を集めています。本記事では、都内・近郊でワーケーションができるスポットを環境・仕事環境・観光資源の三軸で徹底比較します。\u003cbr\u003e\n\u003ca href=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4420079%2F9ff0def1-c8c3-497d-91ef-2b68d35221ca.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=064bb1bf69545ac3959b235b73535d7b\" target=\"_blank\" rel=\"nofollow noopener\"\u003e\u003cimg src=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4420079%2F9ff0def1-c8c3-497d-91ef-2b68d35221ca.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;s=064bb1bf69545ac3959b235b73535d7b\" alt=\"20250413.Floor.03.01 (5).jpg\" srcset=\"https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F4420079%2F9ff0def1-c8c3-497d-91ef-2b68d35221ca.jpeg?ixlib=rb-4.0.0\u0026amp;auto=format\u0026amp;gif-q=60\u0026amp;q=75\u0026amp;w=1400\u0026amp;fit=max\u0026amp;s=574ca0953d6a6eb65665a8f6900d51ed 1x\" data-canonical-src=\"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4420079/9ff0def1-c8c3-497d-91ef-2b68d35221ca.jpeg\" loading=\"lazy\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"4:1-31:33\"\u003e都市型ワーケーションとは：新しい働き方の定義\u003cbr\u003e\n従来のワーケーションとの違い\u003cbr\u003e\n従来の「ワーケーション」は、リゾート地・地方・海外での長期滞在型が主流でした。しかし2025〜2026年にかけて「日帰りまたは1〜2泊で、普段と異なる環境で仕事する」都市型ワーケーションが台頭しています。\u003cbr\u003e\nタイプ\u003cbr\u003e\n期間\u003cbr\u003e\n移動\u003cbr\u003e\nコスト\u003cbr\u003e\n特徴\u003cbr\u003e\nリゾート型\u003cbr\u003e\n3日〜1週間\u003cbr\u003e\n飛行機・新幹線\u003cbr\u003e\n高（5〜30万円）\u003cbr\u003e\n完全リフレッシュ・非日常体験\u003cbr\u003e\n地方型\u003cbr\u003e\n1〜3日\u003cbr\u003e\n新幹線・車\u003cbr\u003e\n中（2〜10万円）\u003cbr\u003e\n地域文化・自然との融合\u003cbr\u003e\n都市型（近郊）\u003cbr\u003e\n日帰り〜1泊\u003cbr\u003e\n電車（1時間以内）\u003cbr\u003e\n低（5,000〜20,000円）\u003cbr\u003e\n手軽・定期利用可能\u003cbr\u003e\n吉祥寺型\u003cbr\u003e\n日帰り\u003cbr\u003e\n電車（〜30分）\u003cbr\u003e\n最低（1,000〜5,000円）\u003cbr\u003e\n日常的・繰り返し使える\u003c/p\u003e\n\u003cp data-sourcepos=\"34:1-39:81\"\u003e東京近郊ワーケーションスポット比較\u003cbr\u003e\n吉祥寺：最高の都市型ワーケーション拠点\u003cbr\u003e\n「普段と違う場所で仕事したい」というワーケーションの本質的なニーズを、最も手軽に・高品質に満たせるのが吉祥寺です。都心から電車1本・30〜40分圏内でありながら、井の頭公園の自然・充実したカフェ文化・コワーキングスペースが揃っています。\u003cbr\u003e\nOVER COFFEE HUBは吉祥寺型ワーケーションの「ベースキャンプ」として最適です。午前中は2F・3Fのコワーキングで集中作業、昼食は1Fオーガニックカフェまたは吉祥寺の飲食店でランチ、午後は井の頭公園を散歩してリフレッシュ、夕方にまたコワーキングに戻って仕上げ——1日で仕事もワーケーション体験も完結します。\u003cbr\u003e\n吉祥寺ワーケーションのベースキャンプ：\u003cbr\u003e\nOVER COFFEE HUB（吉祥寺駅徒歩3分）｜\u003ca href=\"https://www.overcoffee.jp/coworking\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://www.overcoffee.jp/coworking\u003c/a\u003e\u003c/p\u003e\n\u003cp data-sourcepos=\"42:1-61:219\"\u003e鎌倉：歴史と自然のリゾート型ワーケーション\u003cbr\u003e\n東京から約1時間。鎌倉市では2022年からワーケーション誘致に積極的で、江ノ電沿いのカフェや古民家コワーキングが整備されています。ただしWi-Fi品質にばらつきがあり、重要な業務には事前確認が必要です。\u003cbr\u003e\n箱根：温泉リゾート×テレワーク\u003cbr\u003e\n東京から約80〜100分。温泉旅館でのワーケーションプランが複数展開されており、1泊2万〜5万円程度。仕事よりリフレッシュ比重が高く、「完全オフ日」が混在する中長期ワーケーションに向いています。\u003cbr\u003e\n三浦・葉山：海とローカルカフェ\u003cbr\u003e\n東京から約70〜90分。サーファーや海好きリモートワーカーに人気。カフェのWi-Fi品質は吉祥寺より劣りますが、海の景色という非日常感はトップクラスです。\u003cbr\u003e\n吉祥寺ワーケーション完全活用術\u003cbr\u003e\nモデルスケジュール：日帰り吉祥寺ワーケーション\u003cbr\u003e\n8:30 吉祥寺駅着→OVER COFFEE HUBで朝のオーガニックコーヒー\u003cbr\u003e\n9:00〜12:00 コワーキングフロアで集中作業（ディープワーク3時間）\u003cbr\u003e\n12:00〜13:30 吉祥寺グルメランチ（サンロード・井の頭通りエリア）\u003cbr\u003e\n13:30〜14:30 井の頭公園散策・ボートで30分リフレッシュ\u003cbr\u003e\n14:30〜17:30 コワーキングフロアで続き作業（3時間）\u003cbr\u003e\n17:30〜 1Fカフェで夕方コーヒー→吉祥寺ハモニカ横丁で夕食\u003cbr\u003e\n1泊2日ワーケーションプランへの拡張\u003cbr\u003e\n1泊する場合は、吉祥寺周辺のビジネスホテル（東横イン吉祥寺・アパホテル等・1泊8,000〜12,000円）と組み合わせます。2日目は午前中をコワーキングで仕事し、午後は井の頭動物園・吉祥寺アーケード・ジブリ美術館（三鷹）などの観光を満喫する設計です。\u003cbr\u003e\nワーケーション生産性を高める3つの原則\u003cbr\u003e\n仕事時間を決める：「この時間は仕事・この時間は観光」と事前に決めることで、どちらも中途半端にならない\u003cbr\u003e\nツールを事前準備：クラウドストレージ・VPN・タスク管理ツールを事前に整備し、外出先でもシームレスに作業できる状態にする\u003cbr\u003e\n場所の力を信じる：非日常の環境は創造性・意思決定力を高めることが研究で確認されている。普段の自分では出てこないアイデアを引き出す機会として活用する\u003c/p\u003e\n\u003cp data-sourcepos=\"64:1-64:172\"\u003e▶ 吉祥寺で都市型ワーケーションを体験しようOVER COFFEE HUB（吉祥寺駅徒歩3分）｜ドロップイン大歓迎\u003ca href=\"https://www.overcoffee.jp/coworking\" class=\"autolink\" rel=\"nofollow noopener\" target=\"_blank\"\u003ehttps://www.overcoffee.jp/coworking\u003c/a\u003e\u003c/p\u003e\n","body":"「遠くに行かなくてもワーケーションはできる」——2026年現在、「都市型ワーケーション」という考え方が広がっています。自宅から電車で30〜60分、特別な手配なしに仕事と観光を融合できる東京近郊スポットが注目を集めています。本記事では、都内・近郊でワーケーションができるスポットを環境・仕事環境・観光資源の三軸で徹底比較します。\n![20250413.Floor.03.01 (5).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4420079/9ff0def1-c8c3-497d-91ef-2b68d35221ca.jpeg)\n\n都市型ワーケーションとは：新しい働き方の定義\n従来のワーケーションとの違い\n従来の「ワーケーション」は、リゾート地・地方・海外での長期滞在型が主流でした。しかし2025〜2026年にかけて「日帰りまたは1〜2泊で、普段と異なる環境で仕事する」都市型ワーケーションが台頭しています。\nタイプ\n期間\n移動\nコスト\n特徴\nリゾート型\n3日〜1週間\n飛行機・新幹線\n高（5〜30万円）\n完全リフレッシュ・非日常体験\n地方型\n1〜3日\n新幹線・車\n中（2〜10万円）\n地域文化・自然との融合\n都市型（近郊）\n日帰り〜1泊\n電車（1時間以内）\n低（5,000〜20,000円）\n手軽・定期利用可能\n吉祥寺型\n日帰り\n電車（〜30分）\n最低（1,000〜5,000円）\n日常的・繰り返し使える\n\n\n東京近郊ワーケーションスポット比較\n吉祥寺：最高の都市型ワーケーション拠点\n「普段と違う場所で仕事したい」というワーケーションの本質的なニーズを、最も手軽に・高品質に満たせるのが吉祥寺です。都心から電車1本・30〜40分圏内でありながら、井の頭公園の自然・充実したカフェ文化・コワーキングスペースが揃っています。\nOVER COFFEE HUBは吉祥寺型ワーケーションの「ベースキャンプ」として最適です。午前中は2F・3Fのコワーキングで集中作業、昼食は1Fオーガニックカフェまたは吉祥寺の飲食店でランチ、午後は井の頭公園を散歩してリフレッシュ、夕方にまたコワーキングに戻って仕上げ——1日で仕事もワーケーション体験も完結します。\n吉祥寺ワーケーションのベースキャンプ：\nOVER COFFEE HUB（吉祥寺駅徒歩3分）｜https://www.overcoffee.jp/coworking\n\n\n鎌倉：歴史と自然のリゾート型ワーケーション\n東京から約1時間。鎌倉市では2022年からワーケーション誘致に積極的で、江ノ電沿いのカフェや古民家コワーキングが整備されています。ただしWi-Fi品質にばらつきがあり、重要な業務には事前確認が必要です。\n箱根：温泉リゾート×テレワーク\n東京から約80〜100分。温泉旅館でのワーケーションプランが複数展開されており、1泊2万〜5万円程度。仕事よりリフレッシュ比重が高く、「完全オフ日」が混在する中長期ワーケーションに向いています。\n三浦・葉山：海とローカルカフェ\n東京から約70〜90分。サーファーや海好きリモートワーカーに人気。カフェのWi-Fi品質は吉祥寺より劣りますが、海の景色という非日常感はトップクラスです。\n吉祥寺ワーケーション完全活用術\nモデルスケジュール：日帰り吉祥寺ワーケーション\n8:30 吉祥寺駅着→OVER COFFEE HUBで朝のオーガニックコーヒー\n9:00〜12:00 コワーキングフロアで集中作業（ディープワーク3時間）\n12:00〜13:30 吉祥寺グルメランチ（サンロード・井の頭通りエリア）\n13:30〜14:30 井の頭公園散策・ボートで30分リフレッシュ\n14:30〜17:30 コワーキングフロアで続き作業（3時間）\n17:30〜 1Fカフェで夕方コーヒー→吉祥寺ハモニカ横丁で夕食\n1泊2日ワーケーションプランへの拡張\n1泊する場合は、吉祥寺周辺のビジネスホテル（東横イン吉祥寺・アパホテル等・1泊8,000〜12,000円）と組み合わせます。2日目は午前中をコワーキングで仕事し、午後は井の頭動物園・吉祥寺アーケード・ジブリ美術館（三鷹）などの観光を満喫する設計です。\nワーケーション生産性を高める3つの原則\n仕事時間を決める：「この時間は仕事・この時間は観光」と事前に決めることで、どちらも中途半端にならない\nツールを事前準備：クラウドストレージ・VPN・タスク管理ツールを事前に整備し、外出先でもシームレスに作業できる状態にする\n場所の力を信じる：非日常の環境は創造性・意思決定力を高めることが研究で確認されている。普段の自分では出てこないアイデアを引き出す機会として活用する\n\n\n▶ 吉祥寺で都市型ワーケーションを体験しようOVER COFFEE HUB（吉祥寺駅徒歩3分）｜ドロップイン大歓迎https://www.overcoffee.jp/coworking\n","coediting":false,"comments_count":0,"created_at":"2026-05-06T10:41:40+09:00","group":null,"id":"8291710a286004dd167f","likes_count":0,"private":false,"reactions_count":0,"stocks_count":0,"tags":[{"name":"コワーキングスペース","versions":[]}],"title":"ワーケーション東京近郊おすすめスポット完全ガイド2026年版｜都内から1時間圏内で仕事も観光も楽しむ","updated_at":"2026-05-06T10:41:40+09:00","url":"https://qiita.com/kazamitoru/items/8291710a286004dd167f","user":{"description":null,"facebook_id":null,"followees_count":1,"followers_count":0,"github_login_name":null,"id":"kazamitoru","items_count":1,"linkedin_id":null,"location":null,"name":"","organization":null,"permanent_id":4420079,"profile_image_url":"https://lh3.googleusercontent.com/a/ACg8ocIaQl1r6NI4K3wjCehtl7y1FrisEjjlnKownED7Vn29AbDVGv5A=s96-c","team_only":false,"twitter_screen_name":null,"website_url":null},"page_views_count":null,"team_membership":null,"organization_url_name":null,"slide":false}]