ユーザーストーリーとは、「だれが・何をしたいか・それはなぜか」を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. 役割を洗い出す:システムを使う立場を並べます(社員、備品管理者、上長、システム管理者)。
  2. 役割ごとに「やりたいこと」を書き出す:画面や機能の名前ではなく、その人の行動で書きます。
  3. 理由を1つ添える:理由が書けないものは、だれの役にも立たない可能性があるので持ち主に確かめます。
  4. 受け入れ基準を Given-When-Then で書く:うまくいく場合を1つ、止める場合(入力もれ、重複、権限なし)を1〜2つ書きます。
  5. 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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?