- マネジメント
ユーザーストーリーの書き方と例30選|受け入れ基準Given-When-Then
ユーザーストーリーとは、「だれが・何をしたいか・それはなぜか」を1〜2文で書いた、利用者の立場から見た要求の単位です。 「〈役割〉として、〈やりたいこと〉をしたい。〈理由〉のためだ。」という形で書き、できあがりの判定には受け入れ基準(その条件を満たせば完成と言える条件)を添えます。
この記事では、架空の「社内の備品予約システム」を題材に、機能の種類別のユーザーストーリー30例と、その全部に付けた Given-When-Then 形式の受け入れ基準を一覧にしました。INVEST の6項目を使った書き直し、大きなストーリーの分割パターン9種、Gherkin の feature ファイルを pytest-bdd で動かすコードと実行結果、課題管理ツールに貼れるひな形までを1本にまとめています。
確認環境:Ubuntu 24.04、Python 3.14.7、pytest 9.1.1、pytest-bdd 9.0.0(記事中のコードと出力はこの環境で実行したものです)。
ユーザーストーリーの早見表
| 知りたいこと | 答え |
|---|---|
| 基本の形 | 〈役割〉として、〈やりたいこと〉をしたい。〈理由〉のためだ。 |
| 受け入れ基準の形 | Given(前提の状態)/When(1つの操作や出来事)/Then(目で確かめられる結果) |
| 1つのストーリーに付ける受け入れ基準の数 | 3〜5個が目安。8個を超えたら分割を考える |
| 良いストーリーの点検 | INVEST の6項目(独立・交渉できる・価値がある・見積もれる・小さい・テストできる) |
| 大きすぎるときの分け方 | 9つの分割パターン(手順、操作、ルール、データ、入力方法、手間の山、基本と応用、性能、調査) |
| 「役割」に書くもの | 「ユーザー」ではなく、社員・備品管理者・上長など立場が分かる名前 |
| 「理由」に書くもの | 機能の言い換えではなく、その人が得たい結果 |
| 受け入れ基準を自動テストにする方法 | Gherkin の feature ファイルに書き、pytest-bdd などで実行する |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ユーザーストーリーの書き方と例30選|受け入れ基準Given-When-Then」
ユーザーストーリーの基本の形
3つの部品
| 部品 | 書くこと | 例 |
|---|---|---|
| 役割 | だれの要求か。立場や権限が分かる名前 | 社員として |
| やりたいこと | その人が行う操作や、受け取りたいもの | 備品を日時指定で予約したい |
| 理由 | それで何がうれしいか | 会議の当日に備品が足りない事態を防ぐためだ |
理由まで書いておくと、作る側が別の方法を提案できるようになります。たとえば「前日に知らせを受け取りたい。備品の受け取りを忘れないようにするためだ」という理由が分かっていれば、メール以外にチャットへの通知という案も検討できます。
書く手順
- 役割を洗い出す:システムを使う立場を並べます(社員、備品管理者、上長、システム管理者)。
- 役割ごとに「やりたいこと」を書き出す:画面や機能の名前ではなく、その人の行動で書きます。
- 理由を1つ添える:理由が書けないものは、だれの役にも立たない可能性があるので持ち主に確かめます。
- 受け入れ基準を Given-When-Then で書く:うまくいく場合を1つ、止める場合(入力もれ、重複、権限なし)を1〜2つ書きます。
- INVEST で点検し、大きければ分割する:後述の対比表と分割パターンを使います。
受け入れ基準を Given-When-Then で書く
Given-When-Then は、「どんな状態で」「何をしたら」「どうなるか」を分けて書く形式です。 箇条書きの受け入れ基準と比べて、前提がそろっているので、読んだ人がそのまま操作して確かめられます。
| 句 | 書くこと | 書き方のこつ |
|---|---|---|
| Given | 操作の前にそろっている状態 | データの件数や値を具体的に書く(「予約が1件ある」) |
| When | 利用者の操作、または時刻などの出来事 | 1つだけ書く。2つあるならシナリオを分ける |
| Then | 画面・データ・通知に表れる結果 | 表示される文、件数、状態の名前など目で確かめられるものを書く |
箇条書きから書き直す例
| 直す前(箇条書き) | 気になる点 | 直した後(Given-When-Then) |
|---|---|---|
| 重複予約ができないこと | どんな重なり方か、どう知らせるかが決まっていない | Given 鈴木さんが10〜11時で予約している/When 佐藤さんが同じ備品を10〜12時で予約する/Then 「この時間帯は予約済みです」と表示され、予約は保存されない |
| 検索が速いこと | 「速い」の基準が人によって違う | Given 備品が1,000件登録されている/When 名前の一部で検索する/Then 2秒以内に結果が表示される |
| エラーが分かりやすいこと | どのエラーで何が表示されるかが分からない | Given 終了時刻が空欄である/When 「予約する」を押す/Then 終了時刻の欄の下に「終了時刻を入力してください」と表示される |
| 管理者だけが取り消せること | 管理者以外が操作したときの見え方が無い | Given 社員の役割でログインしている/When 他人の予約の詳細を開く/Then 「取り消す」は表示されない |
境目の値を忘れずに書く
時間帯・件数・文字数のように範囲がある条件は、境目ちょうどの値を受け入れ基準に入れておくと、作る人と確かめる人の解釈がそろいます。備品予約なら「10〜11時の予約があるとき、11〜12時は予約できるのか」がその例です。後半の pytest-bdd の例では、この境目を表にして3通りを一度に確かめています。
機能の種類別ユーザーストーリー30例(受け入れ基準つき)
題材は社内の備品予約システムです。種類は10個で、それぞれ3例ずつあります。自分のシステムに合わせて、役割・備品名・数値を置き換えて使ってください。
| No | 種類 | ユーザーストーリー | Given(前提) | When(操作) | Then(結果) |
|---|---|---|---|---|---|
| 1 | 検索 | 社員として、備品を名前の一部で検索したい。正式な名前を覚えていなくても探せるようにするためだ。 | 「プロジェクター1」「プロジェクター2」「延長コード」が登録されている | 検索欄に「プロジェ」と入力して検索する | 「プロジェクター1」「プロジェクター2」の2件だけが名前順に表示される |
| 2 | 検索 | 社員として、使いたい日時に空いている備品だけを絞り込みたい。予約できない備品を開いて確かめる手間を減らすためだ。 | プロジェクター1は10/8の10〜11時が予約済みで、プロジェクター2は空いている | 日時に10/8の10〜11時を指定して絞り込む | プロジェクター2だけが表示され、プロジェクター1は表示されない |
| 3 | 検索 | 社員として、検索結果が0件のときに次の手がかりを知りたい。条件を変えて探し直せるようにするためだ。 | 「三脚」という名前の備品は登録されていない | 「三脚」で検索する | 「該当する備品はありません」という文と、条件を消すリンクが表示される |
| 4 | 登録 | 社員として、備品を日時指定で予約したい。会議の当日に備品が足りない事態を防ぐためだ。 | プロジェクター1の10/8の10〜11時が空いている | その時間帯で予約を登録する | 予約が「確定」で保存され、自分の予約一覧が1件増える |
| 5 | 登録 | 社員として、埋まっている時間帯を予約しようとしたら、その場で知らせてほしい。別の時間や備品にすぐ切り替えるためだ。 | 鈴木さんがプロジェクター1を10〜11時で予約している | 佐藤さんが同じ備品を10〜12時で予約する | 「この時間帯は予約済みです」と表示され、予約は保存されない |
| 6 | 登録 | 社員として、入力もれがあれば保存の前に気づきたい。直す場所で迷わないようにするためだ。 | 予約画面で終了時刻が空欄になっている | 「予約する」を押す | 終了時刻の欄の下に「終了時刻を入力してください」と表示され、入力済みの値は消えない |
| 7 | 更新・取消 | 社員として、自分の予約の時間を変更したい。会議の時間が動いたときに取り直さずに済ませるためだ。 | 自分の予約(10〜11時)があり、13〜14時は空いている | 予約の時間を13〜14時に変えて保存する | 予約が13〜14時に変わり、10〜11時は空きに戻る |
| 8 | 更新・取消 | 社員として、使わなくなった予約を取り消したい。ほかの人がその時間に使えるようにするためだ。 | 開始前の自分の予約が1件ある | その予約で「取り消す」を押し、確認で「はい」を選ぶ | 予約の状態が「取消」になり、その時間帯が空きとして検索に出る |
| 9 | 更新・取消 | 社員として、開始時刻を過ぎた予約は変更できないようにしてほしい。利用の記録が後から書き換わらないようにするためだ。 | 開始時刻を過ぎた自分の予約がある | その予約の詳細を開く | 「変更」「取り消す」のボタンは表示されず、内容は読み取りだけできる |
| 10 | 通知 | 社員として、予約が確定したらメールで知らせてほしい。画面を開き直さずに確定を確かめるためだ。 | 佐藤さんのメールアドレスが登録されている | 佐藤さんが予約を確定する | 1分以内に、備品名・日時・予約番号を書いたメールが佐藤さんに1通届く |
| 11 | 通知 | 社員として、予約の前日に知らせを受け取りたい。備品の受け取りを忘れないようにするためだ。 | 10/8の10時開始の予約がある | 10/7の9:00になる | 予約者に前日の知らせが1通届き、同じ予約で2通目は送られない |
| 12 | 通知 | 備品管理者として、返却が遅れている予約を知りたい。次の予約者が待たずに済むよう声をかけるためだ。 | 終了時刻を過ぎても「返却済み」になっていない予約がある | 終了時刻から30分が過ぎる | 管理者あてに、備品名・予約者・終了時刻を書いた知らせが届く |
| 13 | 権限 | 社員として、ほかの人の予約は見るだけにしたい。誤って他人の予約を変えないようにするためだ。 | 鈴木さんの予約がある | 佐藤さんがその予約の詳細を開く | 内容は表示されるが、「変更」「取り消す」は表示されない |
| 14 | 権限 | 備品管理者として、どの予約でも取り消せるようにしたい。備品が修理で貸し出せなくなったときに対応するためだ。 | 備品管理者の役割を持つ利用者でログインしている | 鈴木さんの予約を理由つきで取り消す | 予約が「取消」になり、鈴木さんに理由を書いた知らせが届く |
| 15 | 権限 | システム管理者として、利用者に役割を割り当てたい。備品の管理を担当者に任せるためだ。 | 佐藤さんの役割が「社員」である | 役割を「備品管理者」に変えて保存する | 佐藤さんが次に画面を開いたとき、備品の登録メニューが表示される |
| 16 | 帳票・出力 | 備品管理者として、月ごとの利用回数をCSVで出力したい。買い足す備品を判断する材料にするためだ。 | 9月にプロジェクター1が12回、プロジェクター2が3回予約されている | 対象月に9月を選んでCSVを出力する | 備品名・回数の2列で2行(12と3)が、文字コードUTF-8で出力される |
| 17 | 帳票・出力 | 社員として、自分の予約一覧を印刷用の形で出したい。会議の準備表に貼るためだ。 | 今週の自分の予約が3件ある | 「印刷用を表示」を押す | メニューを除いた1ページに、3件が日時順で並ぶ |
| 18 | 帳票・出力 | 備品管理者として、今日の貸出予定を一覧で出力したい。朝のうちに備品を用意しておくためだ。 | 今日の予約が5件、取消が1件ある | 「今日の貸出予定」を出力する | 取消を除く5件が開始時刻順に並び、予約者名と備品名が載る |
| 19 | 承認 | 社員として、高額な備品は上長の承認を受けてから予約を確定したい。貸出のルールに沿って使うためだ。 | 「承認が必要」に設定された備品がある | その備品を予約する | 予約は「承認待ち」になり、上長に承認依頼が届く |
| 20 | 承認 | 上長として、承認依頼を一覧から承認したい。依頼をためずにすぐ返すためだ。 | 承認待ちの依頼が2件ある | 1件を選んで「承認」を押す | その予約が「確定」になり、承認待ちの一覧は1件になる |
| 21 | 承認 | 上長として、承認しないときは理由を添えて差し戻したい。申請者が直して出し直せるようにするためだ。 | 承認待ちの依頼が1件ある | 理由を空欄のまま「差し戻す」を押す | 「理由を入力してください」と表示され、状態は「承認待ち」のまま変わらない |
| 22 | 一覧・表示 | 社員として、自分の予約を開始日時の近い順に見たい。次に使う予定をすぐ確かめるためだ。 | これからの予約が3件、終わった予約が2件ある | 「自分の予約」を開く | これからの3件が開始の近い順に表示され、終わった2件は「履歴」タブに分かれる |
| 23 | 一覧・表示 | 社員として、備品ごとの1週間の空き状況をカレンダーで見たい。空いている時間を一目で選ぶためだ。 | プロジェクター1に今週2件の予約がある | 備品の詳細で「週表示」を選ぶ | 予約済みの2枠に色が付き、空いている枠を押すと予約画面が開く |
| 24 | 一覧・表示 | 社員として、件数が多い一覧はページを分けて表示してほしい。読み込みを待たずに目的の行へ進むためだ。 | 備品が45件登録されている | 備品一覧を開く | 1ページに20件が表示され、「全45件・3ページ」と表示される |
| 25 | 取込・連携 | 備品管理者として、備品をCSVからまとめて登録したい。数十件を1件ずつ入力する手間を省くためだ。 | 正しい形式の10行のCSVがある | CSVを取り込む | 10件が登録され、「10件を登録しました」と表示される |
| 26 | 取込・連携 | 備品管理者として、取り込みで誤りのある行を行番号つきで知りたい。直す場所をすぐ見つけるためだ。 | 10行のうち3行目の備品名が空のCSVがある | CSVを取り込む | 1件も登録されず、「3行目:備品名は必須です」と表示される |
| 27 | 取込・連携 | 社員として、予約を自分の予定表に取り込めるファイルで受け取りたい。予定を二重に管理しないで済ませるためだ。 | 確定した予約が1件ある | 予約の詳細で「予定表用ファイル」を押す | 件名に備品名、開始・終了に予約日時が入った .ics ファイルが保存される |
| 28 | 管理・設定 | 備品管理者として、備品を新しく登録したい。買い足した備品をすぐ貸し出せるようにするためだ。 | 「プロジェクター3」は登録されていない | 名前と置き場所を入力して登録する | 備品一覧に「プロジェクター3」が増え、社員の検索にも出る |
| 29 | 管理・設定 | 備品管理者として、修理中の備品を一時的に貸出停止にしたい。使えない備品が予約されるのを防ぐためだ。 | プロジェクター1に明日以降の予約が無い | 状態を「貸出停止」に変える | 検索結果に「貸出停止」と表示され、予約ボタンが押せなくなる |
| 30 | 管理・設定 | システム管理者として、1回の予約の上限時間を設定したい。長時間の予約でほかの人が使えなくなるのを防ぐためだ。 | 上限が4時間に設定されている | 社員が9〜14時(5時間)で予約する | 「1回の予約は4時間までです」と表示され、予約は保存されない |
表計算ソフトに貼れるタブ区切り版
同じ30例をタブ区切りにしたものです。コピーして表計算ソフトのA1セルに貼ると、4列の表になります。
No 種類 ユーザーストーリー 受け入れ基準
1 検索 社員として、備品を名前の一部で検索したい。正式な名前を覚えていなくても探せるようにするためだ。 Given 「プロジェクター1」「プロジェクター2」「延長コード」が登録されている/When 検索欄に「プロジェ」と入力して検索する/Then 「プロジェクター1」「プロジェクター2」の2件だけが名前順に表示される
2 検索 社員として、使いたい日時に空いている備品だけを絞り込みたい。予約できない備品を開いて確かめる手間を減らすためだ。 Given プロジェクター1は10/8の10〜11時が予約済みで、プロジェクター2は空いている/When 日時に10/8の10〜11時を指定して絞り込む/Then プロジェクター2だけが表示され、プロジェクター1は表示されない
3 検索 社員として、検索結果が0件のときに次の手がかりを知りたい。条件を変えて探し直せるようにするためだ。 Given 「三脚」という名前の備品は登録されていない/When 「三脚」で検索する/Then 「該当する備品はありません」という文と、条件を消すリンクが表示される
4 登録 社員として、備品を日時指定で予約したい。会議の当日に備品が足りない事態を防ぐためだ。 Given プロジェクター1の10/8の10〜11時が空いている/When その時間帯で予約を登録する/Then 予約が「確定」で保存され、自分の予約一覧が1件増える
5 登録 社員として、埋まっている時間帯を予約しようとしたら、その場で知らせてほしい。別の時間や備品にすぐ切り替えるためだ。 Given 鈴木さんがプロジェクター1を10〜11時で予約している/When 佐藤さんが同じ備品を10〜12時で予約する/Then 「この時間帯は予約済みです」と表示され、予約は保存されない
6 登録 社員として、入力もれがあれば保存の前に気づきたい。直す場所で迷わないようにするためだ。 Given 予約画面で終了時刻が空欄になっている/When 「予約する」を押す/Then 終了時刻の欄の下に「終了時刻を入力してください」と表示され、入力済みの値は消えない
7 更新・取消 社員として、自分の予約の時間を変更したい。会議の時間が動いたときに取り直さずに済ませるためだ。 Given 自分の予約(10〜11時)があり、13〜14時は空いている/When 予約の時間を13〜14時に変えて保存する/Then 予約が13〜14時に変わり、10〜11時は空きに戻る
8 更新・取消 社員として、使わなくなった予約を取り消したい。ほかの人がその時間に使えるようにするためだ。 Given 開始前の自分の予約が1件ある/When その予約で「取り消す」を押し、確認で「はい」を選ぶ/Then 予約の状態が「取消」になり、その時間帯が空きとして検索に出る
9 更新・取消 社員として、開始時刻を過ぎた予約は変更できないようにしてほしい。利用の記録が後から書き換わらないようにするためだ。 Given 開始時刻を過ぎた自分の予約がある/When その予約の詳細を開く/Then 「変更」「取り消す」のボタンは表示されず、内容は読み取りだけできる
10 通知 社員として、予約が確定したらメールで知らせてほしい。画面を開き直さずに確定を確かめるためだ。 Given 佐藤さんのメールアドレスが登録されている/When 佐藤さんが予約を確定する/Then 1分以内に、備品名・日時・予約番号を書いたメールが佐藤さんに1通届く
11 通知 社員として、予約の前日に知らせを受け取りたい。備品の受け取りを忘れないようにするためだ。 Given 10/8の10時開始の予約がある/When 10/7の9:00になる/Then 予約者に前日の知らせが1通届き、同じ予約で2通目は送られない
12 通知 備品管理者として、返却が遅れている予約を知りたい。次の予約者が待たずに済むよう声をかけるためだ。 Given 終了時刻を過ぎても「返却済み」になっていない予約がある/When 終了時刻から30分が過ぎる/Then 管理者あてに、備品名・予約者・終了時刻を書いた知らせが届く
13 権限 社員として、ほかの人の予約は見るだけにしたい。誤って他人の予約を変えないようにするためだ。 Given 鈴木さんの予約がある/When 佐藤さんがその予約の詳細を開く/Then 内容は表示されるが、「変更」「取り消す」は表示されない
14 権限 備品管理者として、どの予約でも取り消せるようにしたい。備品が修理で貸し出せなくなったときに対応するためだ。 Given 備品管理者の役割を持つ利用者でログインしている/When 鈴木さんの予約を理由つきで取り消す/Then 予約が「取消」になり、鈴木さんに理由を書いた知らせが届く
15 権限 システム管理者として、利用者に役割を割り当てたい。備品の管理を担当者に任せるためだ。 Given 佐藤さんの役割が「社員」である/When 役割を「備品管理者」に変えて保存する/Then 佐藤さんが次に画面を開いたとき、備品の登録メニューが表示される
16 帳票・出力 備品管理者として、月ごとの利用回数をCSVで出力したい。買い足す備品を判断する材料にするためだ。 Given 9月にプロジェクター1が12回、プロジェクター2が3回予約されている/When 対象月に9月を選んでCSVを出力する/Then 備品名・回数の2列で2行(12と3)が、文字コードUTF-8で出力される
17 帳票・出力 社員として、自分の予約一覧を印刷用の形で出したい。会議の準備表に貼るためだ。 Given 今週の自分の予約が3件ある/When 「印刷用を表示」を押す/Then メニューを除いた1ページに、3件が日時順で並ぶ
18 帳票・出力 備品管理者として、今日の貸出予定を一覧で出力したい。朝のうちに備品を用意しておくためだ。 Given 今日の予約が5件、取消が1件ある/When 「今日の貸出予定」を出力する/Then 取消を除く5件が開始時刻順に並び、予約者名と備品名が載る
19 承認 社員として、高額な備品は上長の承認を受けてから予約を確定したい。貸出のルールに沿って使うためだ。 Given 「承認が必要」に設定された備品がある/When その備品を予約する/Then 予約は「承認待ち」になり、上長に承認依頼が届く
20 承認 上長として、承認依頼を一覧から承認したい。依頼をためずにすぐ返すためだ。 Given 承認待ちの依頼が2件ある/When 1件を選んで「承認」を押す/Then その予約が「確定」になり、承認待ちの一覧は1件になる
21 承認 上長として、承認しないときは理由を添えて差し戻したい。申請者が直して出し直せるようにするためだ。 Given 承認待ちの依頼が1件ある/When 理由を空欄のまま「差し戻す」を押す/Then 「理由を入力してください」と表示され、状態は「承認待ち」のまま変わらない
22 一覧・表示 社員として、自分の予約を開始日時の近い順に見たい。次に使う予定をすぐ確かめるためだ。 Given これからの予約が3件、終わった予約が2件ある/When 「自分の予約」を開く/Then これからの3件が開始の近い順に表示され、終わった2件は「履歴」タブに分かれる
23 一覧・表示 社員として、備品ごとの1週間の空き状況をカレンダーで見たい。空いている時間を一目で選ぶためだ。 Given プロジェクター1に今週2件の予約がある/When 備品の詳細で「週表示」を選ぶ/Then 予約済みの2枠に色が付き、空いている枠を押すと予約画面が開く
24 一覧・表示 社員として、件数が多い一覧はページを分けて表示してほしい。読み込みを待たずに目的の行へ進むためだ。 Given 備品が45件登録されている/When 備品一覧を開く/Then 1ページに20件が表示され、「全45件・3ページ」と表示される
25 取込・連携 備品管理者として、備品をCSVからまとめて登録したい。数十件を1件ずつ入力する手間を省くためだ。 Given 正しい形式の10行のCSVがある/When CSVを取り込む/Then 10件が登録され、「10件を登録しました」と表示される
26 取込・連携 備品管理者として、取り込みで誤りのある行を行番号つきで知りたい。直す場所をすぐ見つけるためだ。 Given 10行のうち3行目の備品名が空のCSVがある/When CSVを取り込む/Then 1件も登録されず、「3行目:備品名は必須です」と表示される
27 取込・連携 社員として、予約を自分の予定表に取り込めるファイルで受け取りたい。予定を二重に管理しないで済ませるためだ。 Given 確定した予約が1件ある/When 予約の詳細で「予定表用ファイル」を押す/Then 件名に備品名、開始・終了に予約日時が入った .ics ファイルが保存される
28 管理・設定 備品管理者として、備品を新しく登録したい。買い足した備品をすぐ貸し出せるようにするためだ。 Given 「プロジェクター3」は登録されていない/When 名前と置き場所を入力して登録する/Then 備品一覧に「プロジェクター3」が増え、社員の検索にも出る
29 管理・設定 備品管理者として、修理中の備品を一時的に貸出停止にしたい。使えない備品が予約されるのを防ぐためだ。 Given プロジェクター1に明日以降の予約が無い/When 状態を「貸出停止」に変える/Then 検索結果に「貸出停止」と表示され、予約ボタンが押せなくなる
30 管理・設定 システム管理者として、1回の予約の上限時間を設定したい。長時間の予約でほかの人が使えなくなるのを防ぐためだ。 Given 上限が4時間に設定されている/When 社員が9〜14時(5時間)で予約する/Then 「1回の予約は4時間までです」と表示され、予約は保存されない
種類ごとの受け入れ基準の観点
30例を書くときに使った観点です。自分で書くときの抜けの点検に使えます。
| 種類 | うまくいく場合に書くこと | 止める場合・境目で書くこと |
|---|---|---|
| 検索 | 一致の仕方(部分・前方)、並び順 | 0件のときの表示、条件の組み合わせ |
| 登録 | 保存後の状態、一覧への反映 | 入力もれ、重複、入力値が残るか |
| 更新・取消 | 変更前の枠がどうなるか | 変更できる期限、確認の有無 |
| 通知 | 送る相手、時間、本文に入れる項目 | 2通目を送らない条件 |
| 権限 | 役割ごとにできる操作 | 権限が無いときの見え方(非表示か、文を出すか) |
| 帳票・出力 | 列、並び順、文字コード | 対象外にするデータ(取消など) |
| 承認 | 状態の移り変わり、依頼の届け先 | 差し戻しの理由、承認後の変更 |
| 一覧・表示 | 並び順、1ページの件数 | 件数が多いとき、0件のとき |
| 取込・連携 | 取り込み件数の表示、ファイル形式 | 誤りのある行の扱い(全部止めるか、一部だけ入れるか) |
| 管理・設定 | 設定が効き始める時点 | 設定を超えたときの表示 |
INVESTの6項目で点検する(直す前・直した後)
INVEST は、良いストーリーの条件を6つの頭文字にまとめた点検表です。 Bill Wake 氏が2003年の記事で示したもので、Independent・Negotiable・Valuable・Estimable・Small・Testable の6語から成ります。
| 項目 | 意味 | 直す前 | 気になる点 | 直した後 |
|---|---|---|---|---|
| I:独立している | ほかのストーリーと重ならず、順番を入れ替えられる | 社員として、予約の登録画面を使いたい(先に共通部品のストーリーが終わっていること) | 別のストーリーが終わるまで着手も確認もできない | 社員として、備品を日時指定で予約したい(画面から保存までを1本に含める) |
| N:交渉できる | 細部は会話で決める余地がある | 社員として、画面右上の青いボタンを押すと予約ダイアログが開いてほしい | 見た目まで固定され、ほかの案を選べない | 社員として、備品の詳細からすぐ予約を始めたい。探してから予約するまでの手数を減らすためだ |
| V:価値がある | 利用者や依頼者にとっての値打ちがある | 開発者として、予約テーブルに索引を追加したい | 利用者から見た変化が書かれていない | 社員として、備品が1,000件あっても2秒以内に検索結果を見たい。会議の直前でも待たずに探すためだ |
| E:見積もれる | 大きさの見当が付く程度に内容が分かっている | 社員として、ほかの予定表と連携したい | 連携の相手も向きも決まっておらず、見当が付かない | 社員として、予約を .ics ファイルで受け取りたい(相手の仕様を調べる調査を別に1本立てる) |
| S:小さい | 1スプリントの中で余裕を持って終わる | 備品管理者として、備品を管理したい | 登録・変更・停止・削除・取込が全部入っている | 備品管理者として、備品を新しく登録したい(変更・停止・取込は別のストーリー) |
| T:テストできる | 完成したかどうかを確かめる方法がある | 社員として、使いやすい検索がほしい | 「使いやすい」を確かめる方法が無い | 社員として、備品を名前の一部で検索したい(受け入れ基準:「プロジェ」で2件が名前順に出る) |
6項目のうち、書き直しの効果が大きいのは S(小さい)と T(テストできる) です。S は次の分割パターンで、T は Given-When-Then の受け入れ基準で満たせます。
大きすぎるストーリーの分割パターン9種
分割のねらいは、どのかけらも利用者にとって意味のある動きを持つように、縦に薄く切ることです。 画面・処理・データベースといった層で横に切ると、全部がそろうまで確かめられないかけらができます。次の9種は Richard Lawrence 氏らが整理した分割パターンで、名前の日本語は本記事での言い換えです。
分割パターンの早見表
| No | パターン | こんなときに使う | 分け方 |
|---|---|---|---|
| 1 | 手順で分ける(Workflow Steps) | 申請→承認→確定のように段階がある | まず最短の流れを端から端まで通し、途中の段階を後から足す |
| 2 | 操作で分ける(Operations) | 「管理したい」と書かれている | 登録・参照・変更・削除に分ける |
| 3 | ルールの違いで分ける(Business Rule Variations) | 同じ操作に複数の決まりがある | 決まり1つを1本にする |
| 4 | データの違いで分ける(Variations in Data) | 扱うデータの種類が多い | 単純な種類から先に対応する |
| 5 | 入力方法で分ける(Data Entry Methods) | 入力の画面が凝っている | 最も単純な入力で先に作り、便利な入力を後から足す |
| 6 | 手間の山で分ける(Major Effort) | 最初の1種類に手間が集中する | 「どれか1種類に対応」と「残りに対応」に分ける |
| 7 | 基本と応用で分ける(Simple/Complex) | 話すたびに条件が増えていく | 中心の動きを1本にし、追加の条件を別にする |
| 8 | 性能を後に回す(Defer Performance) | 速さや件数の条件が重い | 「動く」と「速く動く」に分ける |
| 9 | 調査を切り出す(Break Out a Spike) | 作り方が分からず見積もれない | 時間を区切った調査を先に行う(ほかの8つが使えないときに選ぶ) |
9パターンの具体例
| No | 分割前 | 分割後 |
|---|---|---|
| 1 | 社員として、承認が必要な備品を予約したい | (a) 予約すると「承認待ち」になり上長に依頼が届く/(b) 上長が承認すると「確定」になる/(c) 上長が理由を添えて差し戻せる |
| 2 | 備品管理者として、備品を管理したい | (a) 備品を登録する/(b) 備品の名前と置き場所を変更する/(c) 備品を貸出停止にする/(d) 備品をCSVからまとめて登録する |
| 3 | 社員として、ルールに沿って予約したい | (a) 重なる時間帯は予約できない/(b) 1回の予約は4時間まで/(c) 予約できるのは30日先まで |
| 4 | 社員として、いろいろな備品を予約したい | (a) 1台ずつ貸し出す備品(プロジェクター)を予約する/(b) 数量のある備品(延長コード10本)を本数指定で予約する |
| 5 | 社員として、カレンダーから予約したい | (a) 日付と時刻を入力欄に打って予約する/(b) 週表示の空き枠を押して予約する |
| 6 | 社員として、メール・チャット・画面の3つで知らせを受け取りたい | (a) どれか1つの方法で知らせを受け取る/(b) 残り2つの方法でも受け取る |
| 7 | 社員として、備品を検索したい | (a) 名前の一部で検索する/(b) 日時の空きで絞り込む/(c) 置き場所で絞り込む/(d) 条件を保存して呼び出す |
| 8 | 備品管理者として、月ごとの利用回数を出力したい | (a) 1か月分を出力できる(待ち時間は問わない)/(b) 1年分でも10秒以内に出力できる |
| 9 | 社員として、予約を自分の予定表に自動で反映したい | (a) 調査:予定表側の取り込み方法を2日で調べ、できること・できないことを書き出す/(b) 調査の結果を受けてストーリーを書き直す |
パターン6は少し分かりにくいので補足します。3つの通知方法のうち、最初の1つで「送る時点を決める・本文を組み立てる・送った記録を残す」という土台ができ、残り2つは送り先を足すだけになります。どれを最初にするかを決めずに「どれか1つ」と書いておくと、着手の時点で最も都合のよいものを選べます。
分割した後の点検
| 点検 | 見るところ |
|---|---|
| どのかけらにも「役割・やりたいこと・理由」が書けるか | 書けないものは層で切っている可能性がある |
| かけらの大きさがそろっているか | 1本だけ大きいなら、もう一度分ける |
| 後回しにできるかけらがあるか | あれば分割はうまくいっている。優先度を付けやすくなる |
| かけらごとに受け入れ基準が書けるか | 書けないかけらは、単独では確かめられない |
Gherkinのfeatureファイルをpytest-bddで動かす
Gherkin は、Given-When-Then を決まったキーワードで書く記法です。 feature ファイルに書いた受け入れ基準を、pytest-bdd で Python の関数に結び付けると、そのまま自動テストになります。ここでは No.4・No.5 のストーリーを題材に、日本語のキーワードで書きます。
ファイルの構成と導入
bdd/
├── pytest.ini
├── reservation.py # テスト対象(備品予約の最小実装)
├── features/
│ └── reservation.feature # 受け入れ基準
└── tests/
└── test_reservation.py # ステップの定義
python3 -m venv .venv
.venv/bin/pip install pytest pytest-bdd
featureファイル(features/reservation.feature)
1行目の # language: ja で、キーワードを日本語にできます。
# language: ja
機能: 備品の予約
社員として、会議で使う備品を前もって押さえたい。
当日に備品が足りずに会議が止まることを防ぐためだ。
背景:
前提 備品 "プロジェクター1" が登録されている
シナリオ: 空いている時間帯を予約できる
前提 "プロジェクター1" の 10 時から 11 時は空いている
もし 社員 "佐藤" が "プロジェクター1" を 10 時から 11 時で予約する
ならば 予約は "確定" になる
かつ "プロジェクター1" の予約件数は 1 件である
シナリオ: 重なる時間帯は予約できない
前提 社員 "鈴木" が "プロジェクター1" を 10 時から 11 時で予約している
もし 社員 "佐藤" が "プロジェクター1" を 10 時から 12 時で予約する
ならば 予約は "重複のため不可" になる
かつ "プロジェクター1" の予約件数は 1 件である
シナリオアウトライン: 開始と終了の境目
前提 社員 "鈴木" が "プロジェクター1" を 10 時から 11 時で予約している
もし 社員 "佐藤" が "プロジェクター1" を <開始> 時から <終了> 時で予約する
ならば 予約は "<結果>" になる
例:
| 開始 | 終了 | 結果 |
| 9 | 10 | 確定 |
| 11 | 12 | 確定 |
| 9 | 11 | 重複のため不可 |
| 英語のキーワード | 日本語のキーワード | 役割 |
|---|---|---|
| Feature | 機能 | ファイル全体の題名。下にストーリーの文を書ける |
| Background | 背景 | すべてのシナリオの前に行う前提 |
| Scenario | シナリオ | 受け入れ基準1つ |
| Scenario Outline | シナリオアウトライン | 値だけを変えて繰り返すシナリオ |
| Examples | 例 | 繰り返す値の表 |
| Given | 前提 | 操作の前の状態 |
| When | もし | 操作や出来事 |
| Then | ならば | 確かめる結果 |
| And | かつ | 直前と同じ種類の行を続ける |
テスト対象(reservation.py)
"""備品予約の最小実装(テスト対象)"""
class ReservationBook:
def __init__(self):
self.items = {} # 備品名 -> [(開始, 終了, 社員名), ...]
def add_item(self, name):
self.items.setdefault(name, [])
def reserve(self, item, start, end, who):
for s, e, _ in self.items[item]:
if start < e and s < end: # 時間帯が重なる
return "重複のため不可"
self.items[item].append((start, end, who))
return "確定"
def count(self, item):
return len(self.items[item])
ステップの定義(tests/test_reservation.py)
feature ファイルの1行1行を、同じ文を持つ関数に結び付けます。{start:d} のように書いた部分は、数値として関数の引数に渡されます。
from pytest_bdd import given, parsers, scenarios, then, when
import pytest
from reservation import ReservationBook
scenarios("../features/reservation.feature")
@pytest.fixture
def book():
return ReservationBook()
@given(parsers.parse('備品 "{item}" が登録されている'))
def item_exists(book, item):
book.add_item(item)
@given(parsers.parse('"{item}" の {start:d} 時から {end:d} 時は空いている'))
def slot_is_free(book, item, start, end):
assert book.count(item) == 0
@given(parsers.parse('社員 "{who}" が "{item}" を {start:d} 時から {end:d} 時で予約している'))
def already_reserved(book, who, item, start, end):
assert book.reserve(item, start, end, who) == "確定"
@when(
parsers.parse('社員 "{who}" が "{item}" を {start:d} 時から {end:d} 時で予約する'),
target_fixture="result",
)
def do_reserve(book, who, item, start, end):
return book.reserve(item, start, end, who)
@then(parsers.parse('予約は "{expected}" になる'))
def check_result(result, expected):
assert result == expected
@then(parsers.parse('"{item}" の予約件数は {n:d} 件である'))
def check_count(book, item, n):
assert book.count(item) == n
設定(pytest.ini)
[pytest]
pythonpath = .
testpaths = tests
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true
3行目は、テスト名に含まれる日本語をそのまま表示するための pytest の設定です。
実行結果
.venv/bin/python -m pytest -v
collecting ... collected 5 items
tests/test_reservation.py::test_空いている時間帯を予約できる PASSED [ 20%]
tests/test_reservation.py::test_重なる時間帯は予約できない PASSED [ 40%]
tests/test_reservation.py::test_開始と終了の境目[9-10-確定] PASSED [ 60%]
tests/test_reservation.py::test_開始と終了の境目[11-12-確定] PASSED [ 80%]
tests/test_reservation.py::test_開始と終了の境目[9-11-重複のため不可] PASSED [100%]
============================== 5 passed in 0.06s ===============================
シナリオ2つと、シナリオアウトラインの表3行が、合わせて5件のテストとして実行されました。
境目の決まりを取り違えたときの出力
reservation.py の重なりの判定を start <= e and s <= end に変えると、「10〜11時の予約があるとき、11〜12時も予約できない」という動きになります。このとき、境目のシナリオが次のように知らせてくれます(要点の行を抜き出しています)。
E AssertionError: assert '重複のため不可' == '確定'
E
E - 確定
E + 重複のため不可
FAILED tests/test_reservation.py::test_開始と終了の境目[9-10-確定] - Assertio...
FAILED tests/test_reservation.py::test_開始と終了の境目[11-12-確定] - Asserti...
========================= 2 failed, 3 passed in 0.10s ==========================
どの受け入れ基準が満たされていないかが、テスト名で分かるのが利点です。
ステップの定義が無い行があるときの出力
feature ファイルに「もし 社員 "鈴木" が予約を取り消す」という行を足し、対応する関数を書かずに実行すると、次のエラーになります(ファイルのパスは短くしています)。
E pytest_bdd.exceptions.StepDefinitionNotFoundError: Step definition is not found: When "社員 "鈴木" が予約を取り消す". Line 34 in scenario "予約を取り消せる" in the feature "…/features/reservation.feature"
========================= 1 failed, 5 passed in 0.28s ==========================
受け入れ基準を先に feature ファイルへ書き、このエラーが出た行から順に関数と実装を足していくと、受け入れ基準と作るものがずれにくくなります。
課題管理ツールに貼れるMarkdownひな形
Markdown が使える課題管理ツールの説明欄に、そのまま貼れる形です。
## ユーザーストーリー
〈役割〉として、〈やりたいこと〉をしたい。
〈理由〉のためだ。
## 受け入れ基準
### 1. 〈うまくいく場合の名前〉
- Given 〈前提の状態〉
- When 〈操作または出来事〉
- Then 〈目で確かめられる結果〉
### 2. 〈止める場合の名前〉
- Given 〈前提の状態〉
- When 〈操作または出来事〉
- Then 〈表示される文と、変わらないもの〉
### 3. 〈境目の値の名前〉
- Given 〈前提の状態〉
- When 〈境目ちょうどの値での操作〉
- Then 〈結果〉
## 対象外(このストーリーでは扱わないこと)
- 〈別のストーリーに回したこと〉
## メモ
- 画面の案・関連する資料:〈リンク〉
- 決まっていないこと:〈質問と、答える人〉
## 点検(INVEST)
- [ ] I:ほかのストーリーを待たずに着手できる
- [ ] N:見た目や作り方を固定していない
- [ ] V:理由に、利用者が得たい結果が書いてある
- [ ] E:大きさの見当が付く
- [ ] S:1スプリントに余裕を持って収まる
- [ ] T:受け入れ基準が Given-When-Then で書いてある
記入例(No.5 のストーリー)
## ユーザーストーリー
社員として、埋まっている時間帯を予約しようとしたら、その場で知らせてほしい。
別の時間や備品にすぐ切り替えるためだ。
## 受け入れ基準
### 1. 重なる時間帯は予約できない
- Given 鈴木さんがプロジェクター1を10〜11時で予約している
- When 佐藤さんが同じ備品を10〜12時で予約する
- Then 「この時間帯は予約済みです」と表示され、予約は保存されない
### 2. 終了と開始が同じ時刻なら予約できる
- Given 鈴木さんがプロジェクター1を10〜11時で予約している
- When 佐藤さんが同じ備品を11〜12時で予約する
- Then 予約が「確定」で保存される
### 3. 入力した値は残る
- Given 重なりの知らせが表示されている
- When 画面をそのまま見る
- Then 備品・日付・時刻の入力値は消えていない
## 対象外(このストーリーでは扱わないこと)
- 空いている別の備品の提案(別のストーリーにする)
書くときにつまずきやすい点と直し方
| つまずきやすい点 | そのままだとこうなる | こうするとよい |
|---|---|---|
| 役割が「ユーザー」だけ | だれの要求か分からず、権限の条件が抜ける | 社員・備品管理者・上長など立場の名前で書く |
| 理由が機能の言い換え(「予約したい。予約するためだ」) | 別の案を出す手がかりが無い | 「それができると何がうれしいか」をもう一度たずねる |
| 作り方を書いている(「テーブルに列を足したい」) | 利用者から見た変化が分からない | 利用者に起こる変化に書き直す。作り方はタスクに書く |
| When に操作が2つ以上ある | どの操作で結果が変わったか分からない | シナリオを分けるか、前の操作を Given に移す |
| Then が「正しく表示される」 | 人によって「正しい」が違う | 表示される文・件数・状態の名前を書く |
| 受け入れ基準が10個以上ある | 1スプリントに収まりにくい | 分割パターンで分ける |
| うまくいく場合しか書いていない | 入力もれ・重複・権限なしの動きが後から問題になる | 止める場合を1〜2つ足す |
よくある質問
Q. ユーザーストーリーと要件定義書の機能一覧は何が違いますか? A. 機能一覧は「システムが何を持つか」を並べたもので、ユーザーストーリーは「だれが何をしたくて、それはなぜか」を書いたものです。同じ内容を、作る側から見るか、使う側から見るかの違いです。機能一覧の1行を、役割ごとのストーリーに書き直すこともできます。
Q. 受け入れ基準と完成の定義は同じものですか? A. 別のものです。受け入れ基準はストーリー1本ごとの条件で、完成の定義は「レビュー済み」「テストが通っている」のように、すべてのストーリーに共通する条件です。
Q. Given-When-Then は必ず自動テストにする必要がありますか? A. 必要はありません。文章として書くだけでも、前提・操作・結果がそろうので、手で確かめる手順として使えます。繰り返し確かめたい基準から順に、feature ファイルに移していく進め方が取り入れやすいです。
Q. 性能やセキュリティのような条件はどう書きますか? A. 特定のストーリーに関わるものは、そのストーリーの受け入れ基準に数値つきで書きます(「1,000件で2秒以内」)。すべてに共通するものは、完成の定義にまとめます。
まとめ
- ユーザーストーリーは「〈役割〉として、〈やりたいこと〉をしたい。〈理由〉のためだ。」の3つの部品で書く
- 受け入れ基準は Given(前提)・When(操作1つ)・Then(目で確かめられる結果)に分ける
- うまくいく場合に加えて、止める場合と境目の値を1〜2つ書く
- INVEST の6項目で点検し、特に S(小さい)と T(テストできる)を満たす
- 大きなストーリーは9つの分割パターンで、どのかけらにも利用者にとっての意味が残るように分ける
- Gherkin の feature ファイルは pytest-bdd でそのまま自動テストになり、満たされていない基準がテスト名で分かる
参考
- Bill Wake「INVEST in Good Stories, and SMART Tasks」(2003年)https://xp123.com/invest-in-good-stories-and-smart-tasks/
- Richard Lawrence、Peter Green「The Humanizing Work Guide to Splitting User Stories」https://www.humanizingwork.com/the-humanizing-work-guide-to-splitting-user-stories/
- pytest-bdd ドキュメント https://pytest-bdd.readthedocs.io/en/stable/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ユーザーストーリーの書き方と例30選|受け入れ基準Given-When-Then」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。