JaSST Review'21の見どころ 〜価値を実現するためのレビューとは何か〜 #jasstreview

JaSST Reviewを今年も開催します。4回目の開催です!

本記事では今回の見どころをざっと紹介していきます。読んだ上で興味がある発表がありましたら、ぜひイベントに参加登録をお願いします!

www.jasst.jp

目次

JaSST Reviewとは何か

JaSSTとはソフトウェアテストシンポジウム(JaSST)のことで、2003年から全国各地で開催されているテストのイベントです。全国で年間10回以上開催されています。

そしてJaSST ReviewはJaSSTの一つで、ソフトウェアレビューに特化したイベントです。

詳しくは以前書いた下記記事を参照してください。

nihonbuson.hatenadiary.jp

今回のJaSST Reviewのテーマ

今回のテーマは「価値を実現するためにレビューができること」です。

以下、JaSST Review'21のページから抜粋。

実際に現場ではこんな事態が起こることがあります。

・「こうして欲しい」とお客様に言われた通り作ったが、「欲しいのはこんなものではなかった」と言われてしまった。

・利用者の要望に応えて実装したが、システム運用が大変な手間になってしまった。

結果として、いくら頑張って開発していても、開発リソースなどの投資に対しての(経済的・非経済的)効果が得られず終わってしまうこともあるのが現実ではないでしょうか。

これらの課題に対して、レビューでできることは何でしょうか?

うまくいっているチームではどんなレビューがされているのでしょうか?

うまくいっている開発チームではどのようなレビューがされているのか、 うまくいっている理由は何なのかについて、今年のシンポジウムで掘り下げていきます。

今回、掘り下げていきたいものを「顧客が本当に必要だったもの」の絵を例にして話したいと思います。

顧客が本当に必要だったもの

皆さんは「顧客が本当に必要だったもの」という絵をご存知でしょうか?

f:id:nihonbuson:20210909105032p:plain
引用元:プロジェクトの姿~顧客が本当に必要だったもの

これは、システム開発プロジェクトの姿を風刺した絵です。本当に必要だったものは、開発に関わる人だけでなく、顧客自身も気づいていないかもしれないことを表しています。

この絵を前提として、レビューでできることとはどんなことでしょうか?

まず、設計書作成者が以下の認識を持っていたとします。

f:id:nihonbuson:20210909105807p:plain
設計書作成者の認識

それに対して、レビュアーは欠陥の指摘ができるかもしれません。

f:id:nihonbuson:20210909105859p:plain
欠陥の指摘

もしくはプロジェクトリーダーとの認識のズレを指摘できるかもしれません。

f:id:nihonbuson:20210909105952p:plain
プロジェクトリーダーとの認識ズレの指摘

もしかしたら、顧客の認識のズレを指摘できるかもしれません。

f:id:nihonbuson:20210909110058p:plain
顧客との認識ズレの指摘

ただし、顧客が持っていた認識が必ずしも正しいとは限りません。本当に必要だったものを伝えられれば、必要最低限の実装で満足いくものができたかもしれません。

f:id:nihonbuson:20210909110240p:plain
本当に必要だったものとのズレの指摘

とはいえ、必ずしもレビュアーが全てを認識できているわけではありません。なので、もしかしたら確認する質問をレビュアーが投げかけることで、新たに見えてくるものがあるかもしれません。

f:id:nihonbuson:20210909110352p:plain
認識ズレを確認する質問

今回のJaSST Reviewで掘り下げていきたいところ

今回は、色々な情報がある中で、レビューで「顧客が本当に必要だったもの(=価値)」を見極める、もしくはそこに立ち返るにはどうすれば良いかを掘り下げていきたいと思っています。

今回講演をお願いした2組(4名)は、普段の業務の中で「顧客が本当に必要だったもの」を見極めたり立ち返ったりしている方々だと思っています。(講演を依頼した詳しい経緯は別記事で紹介する予定です)

追記:講演依頼の経緯を記事にしました!

nihonbuson.hatenadiary.jp

nihonbuson.hatenadiary.jp

イベントでは、普段どのようなきっかけで、価値に立ち返ったりできるのかお話が聞けることを期待しています。

おわりに

今回のJaSST Reviewでは上記のようなことを深く考えていきたいと思います。

本イベントに参加して、普段の皆さんのレビュー活動(レビューミーティングだけでなく、同僚へのフィードバックなども)がさらに有意義になればと思います。

現在、参加登録募集中です。気になる方がいましたら、参加申し込みをお願いします!

www.jasst.jp

テスト駆動開発の題材を目的別で紹介する

はじめに

本記事はテスト駆動開発 Advent Calendar 2021の3日目の記事です。8月に書いた記事であり、新作ではありませんが、アドベントカレンダーがスカスカなので穴埋めします。

アドベントカレンダーへのご参加をお待ちしております!

qiita.com

この記事を書いた目的、読んでもらいたい対象

書籍『テスト駆動開発』には下記のように書かれています。

テスト駆動開発の良さ、強みは手を動かせば分かります。

結果ではなく過程に本質があります。

テスト駆動開発とは練習によって獲得できる技術です。

ですが、その練習の題材として何があるのか、知らない人も多いかもしれません。

そこで本記事では、テスト駆動開発(以下、TDD)の練習題材になりそうなものを紹介していきます。また、私なりの題材のポイントも合わせて紹介します。

FizzBuzzの次に何をしよう…?」と悩んでいる人に参考にしてもらえればと思います。

目次

題材を紹介する前に 〜題材の使い方〜

TDDで題材を選ぶポイントは以下の2つがあります。

TDDを練習するには、この2つのバランスを考えた方が良いです。

例えば、新しく学ぶプログラミング言語のTDDを練習しようと思ったら、簡単な題材を選びましょう。

一方、ある程度業務でも使っているプログラミング言語のTDDを練習しようと思ったら、少し応用の題材でも良いかもしれません。

基本問題

ここからは、題材になるようなものをどんどん紹介していきます。

まずは基本問題です。新しいプログラミング言語を使ったり、初めてTDDを練習してみる時は、この基本問題に書いている題材から始めると良いでしょう。


題材1. FizzBuzz

みんな大好きFizzBuzzです。

ja.wikipedia.org

この題材を用いて、t_wadaさんがライブコーディングしている講演が既にYoutubeに上がってます。この動画を見ながら、写経してみるのも良いかもしれません。

www.youtube.com


題材2. ローマ数字変換

ローマ数字を数値に変換するプログラムです。

github.com

例えば、下記のようになります。

◆入力値
MCMXLIV
◆出力値
1944

毎回FizzBuzzばかりで飽きた人向けです。

ただし、単純作業が多く発生するため、途中で飽きる可能性があります。


題材3. ボウリング

ボウリングの点数を算出するプログラムです。

codingdojo.org

例えば、下記のようになります。

◆入力値
X 12 9/ 12
※「X」がストライク、「/」がスペア
◆出力値
30

最初は単純な足し算をやった後に、ストライクやスペアの計算についての仕様を追加していく形になるでしょう。


題材4. 格子点

x座標とy座標からなる格子点についての問題です。

devtesting.jp

入試数学のように導かれるような順番で出題されているので、ステップバイステップでTDDを練習することができます。

この題材を用いた、いのうえさんととーますさんのペアプロ動画が既にYoutubeに上がってます。この動画も参考にしてください。

www.youtube.com

設計まで踏み込む問題

基本問題から少し発展した問題です。言語、TDDに少し慣れてきた人が解いてみると良いでしょう。


題材5. 火星探索機

初期位置(x,y)とステージの広さを定義した後、「90°右回転」「90°左回転」「前進」「後進」の指示を元に、現在位置(x',y')を求める問題です。「題材4. 格子点」の応用のような位置付けです。

danilsuits.github.io

火星は球体なので、ステージの広さが(5,5)の広さの時、初期位置(1,1)の北向きで90°左回転して前進すると、現在位置は(5,1)になるのがポイントです。

例えば、下記のようになります。

◆入力値
初期位置(1,3)、ステージの広さ(5,5)、北向き
「90°左回転」「前進」「90°右回転」「前進」
◆出力値
現在位置(5,4)、北向き

題材6. ポーカー

ポーカーの手札の役判定などを行う問題。

devtesting.jp

上記のTDDBC Sendaiの出題がステップバイステップで分かりやすいので、入出力例についてはそちらを見てください。

題材にも馴染みがあるので、取り組みやすいかもしれません。

ボトムアップで取り組む問題

一定のルールに基づいているので、まずは小さい範囲で考えて、だんだんとその範囲を広げて解いていく形式の問題です。


題材7. ラングトンのアリ

正方形が敷き詰められた地面にアリがいて、下記のルールに従ってアリが進む問題です。

  • 現在位置が白マスだったら黒マスに変更して右に進む
  • 現在位置が黒マスだったら白マスに変更して左に進む

ja.wikipedia.org

シンプルなルールですが、下記画像のように面白い動きになります。

f:id:nihonbuson:20210510171430g:plain
Wikipediaより

プログラム入出力例は下記のようになります。

◆入力値
ステージの広さ(3,3)、初期位置(2,2)
◆出力値(※「-」が白マス、「*」が黒マス)
0
---
-a-
---

1
-a-
-*-
---

2
-*a
-*-
---

3
-**
-*a
---

4
-**
-a*
---

5
-**
--*
-a-

題材8. ライフゲーム

正方形に敷き詰めた地面に対して、条件によってセルが生きたり死んだりします。 条件は下記の通りです。ここでいう「隣接するセル」とは、自身のセルの上下左右斜めの8つが対象です。

  • 死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。(誕生)
  • 生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。(生存)
  • 生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。(過疎)
  • 生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。(過密)

ja.wikipedia.org

この題材も面白い動きになります。

f:id:nihonbuson:20210510173158g:plain
Wikipedia(英語版)より

プログラム入出力例は下記のようになります。

◆入力値
ステージの広さ(5,5)、初期生存セル(2,3)(3,3)(4,3)
◆出力値(※「-」が死滅セル、「*」が生存セル)
0
-----
-----
-***-
-----
-----

1
-----
--*--
--*--
--*--
-----

2
-----
-----
-***-
-----
-----

トップダウンで取り組む問題

途中で仕様が追加されていく問題です。問題を進めるに従ってコードが変化していくはずです。


題材9. Potter

ルールに従って、同じシリーズまとめ買いによる値段の割引が行われる時、その最安値を求める問題です。

codingdojo.org

ルールは下記の通りです。

  • 1冊1000円
  • 同じシリーズの別の巻を2冊買うと、それぞれ5%引き
  • 同じシリーズの別の巻を3冊買うと、それぞれ10%引き
  • 同じシリーズの別の巻を4冊買うと、それぞれ15%引き
  • 同じシリーズの別の巻を5冊買うと、それぞれ20%引き

例えば、下記のようになります。

◆入力値
第1巻を1冊、第2巻を1冊
◆出力値
(1000*2)*0.95=1900円

◆入力値
第1巻を2冊
◆出力値
1000 * 2 = 2000円
※同じ巻なので割引なし

◆入力値
第1巻を2冊、第2巻を1冊
◆出力値
(1000*2)*0.95+1000=2900円

◆入力値
第1巻を2冊、第2巻を1冊、第3巻を1冊
◆出力値
(1000*2)*0.95+(1000*2)*0.95=3800円
(1000*3)*0.9+1000=3700円
※(第1巻+第2巻)の5%引き+(第1巻+第3巻)の5%引きのパターンと、
(第1巻+第2巻+第3巻)の10%引き+第1巻のパターンが存在する

題材10.LCD表示

数値を与えられると、それに対するLCD表示(デジタル表示)を求める問題です。

codingdojo.org

例えば、下記のようになります。

◆入力値
2
◆出力値
 _ 
 _|
|_

問題が進むと、途中で仕様変更が発生し…というのがミソ。ネタバレ防止のために、ここには記載しません。


題材11.自動販売

自動販売機の動きをプログラミングする問題です。

題材: プログラミングのお題: 自動販売機 (設計進化重視バージョン) · GitHub

仕様が提示されている形式なので、単なるTDDではなく、cucumberなどを用いたATDDで解くのもオススメです。

またこの題材を元に、TODOリストの整理の仕方について説明した発表を以前に行いました。

speakerdeck.com

レガシーコードのリファクタリングの問題

ここからは既存のコード(テストコード無し)に対して、リファクタリングをしていく問題です。


題材12. テニスゲーム

テニスの点数表示を題材とした問題です。詳しくは、リンク先参照。

github.com

同じ仕様に対して3パターンの実装があるので、1つの言語に対してリファクタリングを3回練習できます。

テニスの計算方法を知っていれば、仕様の読み込みを行う必要がなく、すぐに問題に取り組めるので便利な題材です。

ソースにはテストコードが既にありますが、既存のテストコードを消して、レガシーコードのリファクタリングの練習として使うのがオススメです。

先日公開した書籍『テストコードの注入から始めるレガシーコードのリファクタリング』では、この題材のリファクタリング過程を載せて解説しています。

leanpub.com


題材13. GildedRose

仕様もあって面白い問題です。詳しくは、リンク先参照。

github.com

今年のJaSST Tokyoでも題材にして、実際にリファクタリングする様子をライブコーディングしました。

www.jasst.jp

また、先日公開した書籍『テストコードの注入から始めるレガシーコードのリファクタリング』では、この題材のリファクタリング過程を載せて解説しています。

leanpub.com


題材14. trivia

クイズゲームを題材とした問題です。詳しくはリンク先参照。

github.com

この題材をリファクタリングしている動画が既にYoutube上にあったりします。

www.youtube.com

さいごに 〜参考文献など〜

TDDの題材になりそうなものをざっと紹介してみました。

ご自身のプログラミング言語のスキルレベル、TDDのスキルレベルに合わせて、適切な難易度かつ楽しそうな問題を選んで取り組んでみてください!

もしも「他の題材も探したい!」という人は、下記のサイトがオススメです。


TDD Boot Camp(TDDBC)

devtesting.jp

おそらくTDDに関して日本で一番充実しているサイト、コミュニティです。

年に数回、ワークショップ形式のイベントも開催しています。

本文中に紹介した、t_wadaさんのライブコーディング動画や、いのうえさんととーますさんのペアプロ動画はこのコミュニティのイベントによるものです。


All Kata

All Katas

今回紹介した題材の多くが載っているサイトです。

Kataの用途ごとにTopicが分かれているので、行いたい内容を元に題材を探すにも最適です。


Coding Dojo

codingdojo.org

今回紹介した題材の多くが載っているサイトその2です。

All Kataに比べると、目的にあった題材を探すのは少し難しいです。


Cyber Dojo

www.cyber-dojo.org

オンライン上でTDDを練習できるサイトです。

現時点で58種類の問題が用意されています。

ただし一覧になっているので、難易度や目的に沿ったお題なのかは分かりづらいかも。


Philippe Bourgau's XP Coaching Blog - A coding dojo exercises plan towards refactoring legacy code

philippe.bourgau.net

題材集ではないですが、今回のブログを書くにあたって大いに参考にしたサイトです。

題材を区切った種別の仕方(ボトムアップトップダウンなど)は、このサイトを参考にしました。


最後に、書籍『テスト駆動開発』の文章をもう一度載せます。

テスト駆動開発の良さ、強みは手を動かせば分かります。
結果ではなく過程に本質があります。
テスト駆動開発とは練習によって獲得できる技術です。

ぜひ、たくさん練習して、TDDを身に付けてくださいね!

最近更新した翻訳or執筆書籍の紹介

ここ最近、いずれもLeanpub上に公開している3冊の書籍を公開or更新しましたので、その内容をご紹介します。

f:id:nihonbuson:20210804092654p:plain

書籍『A Practical Guide to Testing in DevOps Japanese Edition』の公開

書籍『A Practical Guide to Testing in DevOps』の日本語版を公開しました!

leanpub.com

実装前や実装中でのテストだけでなく、本番環境でのテストについて、数多くの事例を用いて語られています。

書籍内容について詳しくは、先日書いた以下の記事をご覧ください。

nihonbuson.hatenadiary.jp

書籍『Agile Testing Condensed Japanese Edition』第4版の公開&まとめ買い用のオプションを追加

8月1日に書籍『Agile Testing Condensed Japanese Edition』の第4版を公開しました!

leanpub.com

今回は、読者の皆さんから頂いた指摘を反映しております。

ご指摘の連絡をしていただいた方々、本当にありがとうございました!

また、まとめ買いできるようにオプションを追加しました。社内勉強会のため社員数分だけ購入したいなどの際にご活用ください。

Leanpubページに移動したら、赤枠部分を「10冊セット」に変更することで、10冊分のまとめ買いが可能となります。また、最低販売価格も10%OFFの$135($13.5×10)となっております。

f:id:nihonbuson:20210803092252p:plain
まとめ買いの方法

書籍『テストコードの注入から始めるレガシーコードのリファクタリング』の公開

7月26日に書籍『テストコードの注入から始めるレガシーコードのリファクタリング』を公開しました!

nihonbuson.booth.pm

leanpub.com

Paypal(※PayPayではありません!)での支払いが可能な方は、価格が安いleanpubでの購入をオススメします。

レガシーコードのリファクタリングの書籍ではあまり記載がない「テストコードをとりあえず書く」という部分を中心に、実際のリファクタリング過程を言語化して書籍にしました。

この書籍の第1章の途中までは、上記のLeanpubページにサンプルとして無料ダウンロードできるようになっています。また、以下の記事にも掲載しております。購入判断の参考にしてみてください。

nihonbuson.hatenadiary.jp

また、第3章は今年のJaSST'21 Tokyoでの講演でも題材にしたものを、解説を増やして紹介しています。

www.jasst.jp

さらに第3章については、付録としてライブコーディングの動画も載せています。(これもJaSST'21 Tokyoの時よりもさらに詳しく説明しています)

謝辞

書籍公開に向けてはたくさんの人にご協力いただきました。

翻訳本の2冊については、原著者の協力はもちろんのこと、書籍内容について多くの人に見ていただきました。

また、『テストコードの注入から始めるレガシーコードのリファクタリング』については、TDDBCの皆さんに実際にリファクタリングの様子を見ていただき、さらなるアドバイスを頂きました。

本当にありがとうございました!

『A Practical Guide to Testing in DevOps』を翻訳して公開しました!

2017年8月に出版された『A Practical Guide to Testing in DevOps』を日本語に翻訳してLeanpubにて公開しました!

表紙はこんな感じ。*1

f:id:nihonbuson:20210802234628p:plain:w200

書籍紹介および購入はこちらから。

leanpub.com

現在は電子版のみ提供する形になっています。形式はpdfです。

本書籍について

出版ページにも書いている紹介文を引用して紹介します。

強力なコラボレーションと素早いデリバリー文化へ組織が移行するにつれて、テスターへの期待は変化します。

自動化されたビルドパイプラインおよびデプロイメントパイプラインがある環境でのテストはどのように見えるのでしょうか?

本番環境でテストできるようになると、リスクに対する欲求はどのように変化するのでしょうか?

テスターは組織全体で誰とつながる必要があり、どのように効果的に連携して高品質のソフトウェアをデリバリーできるのでしょうか?

この本は、DevOpsでのテストに携わるすべての人へ、方向性とアドバイスを示します。

私は以前に、『Agile Testing Condensed』を翻訳し、公開しました。

leanpub.com

Agile Testing Condensed』は主にシフトレフトのテスト*2について書いているのに対し、『A Practical Guide to Testing in DevOps』は主にシフトライトのテスト*3について書いています。

個人的な本書籍の見どころ

本番環境を用いたテストプラクティスの用語を解説している

例えば下記の用語などを、きちんと区別して説明しています。

  • A/B テスト
  • ベータテスト
  • カナリアリリース
  • ドックフーディング
  • ダークローンチ

事例がふんだんに書かれている

セクション5では、下記の会社の事例が紹介されています。

  • King社(ゲーム「Candy Crush Saga」を開発している会社)
  • Capital One 社
  • TheGuardian社
  • ニュージーランド銀行
  • Etsy社
  • Spotify
  • PagerDuty社

また、他にも様々な事例や発表を紹介しています。

グラフィカルなテスト戦略が紹介されている

セクション6では、著者が作成したグラフィカルなテスト戦略の図が紹介されています。

特にDevOpsのバグフィルターは非常に興味深く、共同翻訳者のMarkさんや、翻訳に協力していただいた山口さんもそれぞれブログ内で紹介しています。

f:id:nihonbuson:20210803000411p:plain:w200
DevOpsのバグフィルター

note.com

teyamagu.hatenablog.com

また、8月4日にはDevOpsのバグフィルターをトークテーマにしたイベントをMarkさんが開催しますので、ご都合の合う方はこちらへのご参加もどうぞ!

markin-quality.connpass.com

価格について

本書籍は価格を買い手が自由に決められます。

推奨価格は$14.99ですが、無料でも購入(ダウンロード)いただけます。

より多くの人に知ってもらいたいという原著者の考えを元に、このような設定をしています。

興味がある方はぜひご一読ください!(そして、お金を払っても良いよという方は、ご購入をよろしくお願いします)

謝辞

本書籍の日本語翻訳版を公開するにあたり、たくさんの方々にサポートいただきました。

特に、著者のKatrina Clokieはもちろんのこと、共同翻訳者のMarkさん、協力者として載せさせていただいた山口鉄平さんには非常に多くのサポートをいただきました。本当にありがとうございました!

そして本を手にとっていただいた皆さんも本当にありがとうございます!

Agile Testing Condensed』と同様に、本書籍に書いてあることが世の中に浸透していくことを願ってます。

*1:できるだけ表紙のイメージを崩さずにしつつも、日本語版であるであることが分かるようにしました

*2:開発プロセスの早い段階でテスト活動を行うこと

*3:開発プロセスの遅い段階でテスト活動を行うこと

「自分の知らない技法・テクニック・プラクティスをAさんが知っている=Aさんは自分よりすごい」ではないという話

はじめに

最近、下記のようになっている人を見るようになりました。

 

「Aさんは私の知らないテスト技法を知っていてすごい!それに比べて私なんか…」(自分への劣等感)

「Aさんは私の知らないプラクティスを教えてくれる!だからAさんはなんでも解決できる神のような人だ…!」(盲目的な信者)

ただ、それは自分の知らないことを知っているだけであって、必ずしもその人(Aさん)が素晴らしいかどうかはちゃんと見分けないといけないと思っています。それはどういうことなのかを、中学数学の例を使って説明してみようと思います。

目次

二次方程式の解き方

中学3年で習う「二次方程式」を思い出してください。

二次方程式を解く方法は下記の2つがあります。*1

因数分解を用いて解く方法

お題の二次方程式因数分解で解ける場合、下記のようにして解答を導き出すことができます。*2

f:id:nihonbuson:20210614110129p:plain
因数分解を用いて解く方法

解の公式を用いて解く方法

解の公式というものがあります。

f:id:nihonbuson:20210614110451p:plain
解の公式

お題の二次方程式の係数をそれぞれa,b,cと置く時(a≠0の時)、上記の解の公式を当てはめることで解答を導き出すことができます。

f:id:nihonbuson:20210614110629p:plain
解の公式を用いて解く方法

3人がとある入試問題を解く

下記の3人「Xさん」「Yさん」「Zさん」がいます。

  • Xさん…因数分解を用いて解く方法、解の公式を用いて解く方法の両方を知っている
  • Yさん…因数分解を用いて解く方法だけ知っている
  • Zさん…解の公式を用いて解く方法だけ知っている

この3人が下記の入試問題を解くことになりました。

f:id:nihonbuson:20210614111242p:plain
入試問題

3人とも、この入試問題を解く第一歩として、

f:id:nihonbuson:20210614111907p:plain

と置いて、

f:id:nihonbuson:20210614112242p:plain

として解くことは気付いたとします。

Xさんが入試問題を解く

因数分解を用いて解く方法、解の公式を用いて解く方法の両方を知っているXさんがこの入試問題を解いた結果、下記の手順になりました。

1.Aの二次方程式とみなして、因数分解を行う

f:id:nihonbuson:20210614112537p:plain

2.仮に置いていたAを元に戻す

f:id:nihonbuson:20210614112934p:plain

3.前半部分は因数分解できるので、因数分解する

f:id:nihonbuson:20210614113013p:plain

4.後半部分は解の公式を用いて解き、解答を記載する

f:id:nihonbuson:20210614113117p:plain

Yさんが入試問題を解く

因数分解を用いて解く方法だけ知っているYさんがこの入試問題を解いた結果、下記の手順になりました。

1.Aの二次方程式とみなして、因数分解を行う

f:id:nihonbuson:20210614112537p:plain

2.仮に置いていたAを元に戻す

f:id:nihonbuson:20210614112934p:plain

3.前半部分は因数分解できるので、因数分解する

f:id:nihonbuson:20210614113013p:plain

4.後半部分は因数分解できないため、因数分解できた前半部分の解答のみ記載した。

f:id:nihonbuson:20210614113302p:plain

Zさんが入試問題を解く

解の公式を用いて解く方法だけ知っているZさんがこの入試問題を解いた結果、下記の手順になりました。

1.Aの二次方程式とみなして、解の公式を行う

f:id:nihonbuson:20210614113912p:plain

2.A=7の場合で、二次方程式を考える

f:id:nihonbuson:20210614113558p:plain

3.解の公式を用いることができる形に値を移行して、解の公式を用いて解く

f:id:nihonbuson:20210614113705p:plain

4.同様に、A=4の場合で、二次方程式を考える

f:id:nihonbuson:20210614113739p:plain

5.解の公式を用いることができる形に値を移行して、解の公式を用いて解く

f:id:nihonbuson:20210614113819p:plain

6.手順3と手順5で解いた値を合わせて解答とする

f:id:nihonbuson:20210614114031p:plain

Yさんからはどのように見えるのか?

さて、今回の3人の解答を見てみましょう。

Yさんの解答

f:id:nihonbuson:20210614113302p:plain

Xさん、Zさんの解答

f:id:nihonbuson:20210614113117p:plain

解答だけ見ると、XさんもZさんも変わりません。

解答への導き方まで見ると、Xさんの方が素晴らしいと個人的には感じます。きっと、解くのにかかった時間もXさんの方が格段と短いでしょう。

ただし、Yさんから見ると、XさんもZさんも同等に素晴らしいと見えてしまうのです。なぜならば、自分が導き出せていない答えまで出すことができているからです。

Zさんの吹聴に騙されてはいけない

ここでYさんがZさんに教えてもらおうとすると、こんなことを言われるかもしれません。

「解の公式っていうやり方があるんだよ」

「これを使うと、どんな二次方程式でも解くことができるんだよ」

Zさんが言いふらしたい気持ちも分かります。実際に自分でもこのやり方で解くことができたという自信もあるので。

しかし、それをYさんが聞いて、「解の公式さえできれば良いんだ!」と思ってはいけません。適材適所を見失うことになります。

日常でも同様のことが起きてないですか?

今回は中学3年の頃の題材を用いて説明しました。

しかし、このようなことは日常でも起きていませんか?

以下のようなことを言われて、「なるほど、そうだよね」と思っていませんか?

 

「テスト設計をする時は、どんな場合でもデシジョンテーブルを使えば良いんだよ」

「テストケースがいっぱいできて困る時は、PICT Masterというツールを使ってAll Pair法で解けば、簡単にテストケースが減らせるよ」

「ソフトウェア設計をする際はドメイン駆動設計を使えば良いんだよ」

Agileに開発していくには自動テストを行えば良いんだよ」

「ScrumでのレトロスペクティブではKPTを使ってふりかえりをすれば良いんだよ」

XさんとZさんを見分ける方法

それでは、YさんはどのようにすればXさんとZさんを見分けることができるのでしょうか?

見分け方1.使い所を聞く

技法・テクニック・プラクティスの紹介だけしている場合、「どんな場合に、その方法は有効ですか?」「どんな場合には適さないですか?」と聞いてみましょう。

有効な場面、適さない場面を回答できる人は、きっと他にも数ある方法から自分なりに見極めて使っていることが想像できます。

一方、「どんな場面でも使えるよ」というような回答をする人は、そのやり方を知っているから使っている可能性があります。

また、今回の例は違いますが、「(使ったことないけど)こんなやり方があるよ」と紹介する人がいます(通称:エアプ)。「○○コーチ」というような人に多い傾向です。そのような人は、使い所に関しての質問をすることで、エアプかどうか判断できるかもしれません。

見分け方2.実際に解く過程を観察する

技法・テクニック・プラクティスを紹介してもらうだけではなく、「ちょっと使っている状況を見せて!」と言って、実際に解いている部分を見ましょう。

紹介された時は「良いかも!」と思っても、実際に使っている状況を見ることで、「あれ?この部分は微妙だな…」と気付くことができるかもしれません。

先ほどの二次方程式の例でいえば、「あれ?この部分は私がやってた因数分解の方法の方が素早くできそうだ…」と思えるかもしれません。

見分け方3.意見をぶつける

ただ紹介してもらったものを聞き入れるだけでなく、意見をぶつけてみましょう。

先ほどの二次方程式の例でいえば、「私は因数分解を使った方法で解いたのですが、因数分解を使わずに解の公式を使った理由ってなんですか?」と質問してみましょう。

質問の回答は下記3パターンに分かれるでしょう。

回答パターン1.「因数分解は使いどころを把握できていないと、使いこなすのが難しいので使いませんでした!」

因数分解を使った方法を知っていた上であえて使わなかったパターンです。

このような人は、自分が得意な部分をきちんと把握して使っているので、どういったところが難しいのかについて今後も話し合える仲になっておくと良いでしょう。

回答パターン2.「因数分解を使った方法は知らなかったです!」

そもそも知らない上で、そのことを正直に話してくれるパターンです。

さらに「因数分解を使った解き方を教えてくれませんか?」とか言ってくれる人であれば最高です。今後も、お互いに切磋琢磨して高め合える良い仲になるでしょう。

回答パターン3.「他の方法もあるかもしれませんが、解の公式ならどんな二次方程式でも解けるから良いのです!」

自分が知っているやり方に絶対の自信を持っているパターンです。

同時に、この人自身のプライドが相当高い可能性があります。

このような人は、強い断定で言い切るため信者を作りやすいですし、プライドが高いため、自分のやり方に同意してくれない人を遠ざける傾向にあります。

お互いを高め合う仲にはなれないので、プラクティスの良い話だけを聞き入れるぐらいにしておきましょう。

おわりに

最後に、最近あきやまさんがツイートしていた内容を共有します。

テスト設計技法についてのツイートですが、テスト設計技法に限らずテクニックやプラクティスにおいても同じことが言えます。

技法やプラクティスを覚えると、仕事をする上で便利になりますが、同時に思考停止を招く可能性があります。

伝えようとする側の人は、やり方を他の人に伝えるだけでなく、考え方も合わせて伝えたいですね。

一方、色々なことを吸収していきたい人は、意見をぶつけられるようにするために、既存の技法・テクニック・プラクティスを知っておくと、より有意義な話ができるかもしれません。そして、分からない部分は素直に「分からない」と伝えることで、周りの人の協力を得ながら、より自らを高めていけるかもしれません。

追記

ど正論。おっしゃる通りです。

「XさんとZさんのどっちがすごいか」は私もそこまで気にしてなくて、言いたかったのはYさんがZさんに掴まっている状況に対して抜け出してほしいという話です。

けど確かにXさんもテクニシャンの可能性だって十分にありますね。その部分は完全に抜けてる文章でしたし、ある意味この文章もXさんの盲目的な信者になっている形ですね。

*1:本当は「平方完成」もありますが、ここでは省略します

*2:このブログは数学を教えるブログではないので、因数分解のやり方の説明は省略します

7つのシチュエーション別、よく共有する記事やツイート

はじめに

業務中の自分の発言やSlackのtimesチャンネルを見返して、「これ、頻繁に伝えているなー」というものを書き出してみます。

自分の仕事上、Agile、Scrum、テストに関する話が多めです。

目次

「進捗に遅れが出ています」という発言が出てきたら

入社直後の人が「進捗に遅れが出ています」という発言をしたら共有している記事。おそらく、今までで一番共有している記事。

irof.hateblo.jp

是非、記事全体を読んでほしいのですが、一部を抜粋して書いておきます。

あるのは遅れではなく、見積もりと現実の差分です。見積もりと現実の差分はただの差分でしかありませんが、きっと「現実が遅れている」よりは「見積もりが違っていた」と捉える方が建設的です。

「プロダクトオーナーの動きを理解してもらえてないなー」と思ったら

プロダクトオーナーについての説明が必要になった時は、この動画を共有してます。

www.youtube.com

動画時間も15分程度と長すぎず、かつPOのエッセンスが随所に散りばめられています。

特に12:30あたりから話している、バーンアップチャートのトレンドラインを用いた、ステークホルダーとPOとのやり取りの話についてはよく共有しています。

f:id:nihonbuson:20210519101509p:plain

心理的安全性ってみんなで仲良くやることでしょ?」みたいな雰囲気になっていたら

角さんの下記ツイートを引用することが多いです。

あとは、下記ツイートを引用することもあります。(特に「頑張って心理的安全性を目指そうぜ」と発言してたら共有します)

「レトロスペクティブで有効なTryが出てこない」と言われたら

直接下記のツイートを共有することはないけど、同じような考え方をもって話してます。

加えて、「個人的には問題だと思ってレトロの議論に乗せた結果、チームとしては別に問題ではなかったという結論になったら、それも重要なことだよ」と伝えます。

これらを意識しやすい形のふりかえり会をやったこともあります。以下の記事の「インタビュー」のプラクティスはまさにそれです。

engineering.visional.inc

「TDDをちゃんとやれば品質を保証できるよね」「TDDってどこまでやるの?」と言われたら

直接下記のツイートや記事を共有することはないけど、同じような考え方をもって話してます。

goyoki.hatenablog.com

個人的には「TDDは開発を加速させるためのもの」という認識です。

「品質保証って、最後にテストすることなんでしょ?」と言われたら

品質保証について説明する必要が出てきたら共有しているスライドです。にしさんの発言を元に私がスライドにまとめたものです。

speakerdeck.com

品質保証には色々な変遷があって…という説明で使ったりします。以下のページは特に伝えます。

f:id:nihonbuson:20210519102249p:plain

「元々書いていたドキュメントと違うからバグチケットを起票する」とQAメンバーから言われたら

「別にドキュメントとの間違い探しをするためにQAがいるわけではない」「あるべき姿(※ドキュメントがあるべき姿とは限らない)との違和感を言語化して伝えることが大切」と伝えながら、以下のスライドを共有します。

speakerdeck.com

TDDの考え方を活用してレガシーコードのリファクタリングに立ち向かう

はじめに

本記事はテスト駆動開発 Advent Calendar 2020 最終日の記事です。このアドベントカレンダーはスカスカなので、今からでもテスト駆動開発経験談などをエントリーしてもらえると嬉しいです!

目次

レガシーコードのリファクタリング

テスト駆動開発(以下、TDD)を学び始めた人が、現場でぶつかる壁の一つは「レガシーコード*1なのでTDDが使えない」だと思います。

「レガシーコードをリファクタリングしたいけど、リファクタリングするためのテストコードがない」「テストコードを書きたいけど、レガシーコードなのでテストコードが書きづらい」というジレンマに陥りがちです。

そこで本記事では、TDDの考え方を活用しつつ、まずは1つだけでもテストコードを注入することで、レガシーコードに立ち向かっていきます。*2

今回の題材

レガシーコードになりがちなコードの1つとして、現在時刻に依存しているなど、テストしづらい状況であるコードがあります。

例えば、以下のようなコード*3です。

//DeliveryDate.java
import java.time.LocalDate;
import java.time.Month;

public class DeliveryDate {
    public LocalDate getDeliveryDate(){
        LocalDate localDate = LocalDate.now();
        int day = localDate.getDayOfMonth();
        Month month = localDate.getMonth();
        int year = localDate.getYear();

        if(day >= 25){
            month.plus(1L);
        } else if (month.equals(Month.DECEMBER) && day >= 20) {
            month.plus(1L);
        }

        int lastDay;
        if(month.equals(Month.APRIL)) {
            lastDay = 30;
        } else if(month.equals(Month.JUNE)){
            lastDay = 30;
        } else if(month.equals(Month.SEPTEMBER)){
            lastDay = 30;
        } else if(month.equals(Month.NOVEMBER)){
            lastDay = 30;
        } else if(month.equals(Month.FEBRUARY)){
            if(year%4 == 0){
                lastDay = 29;
            } else {
                lastDay = 28;
            }
        } else {
            lastDay = 31;
        }
        return LocalDate.of(localDate.getYear(),
            localDate.getMonth(), lastDay);
    }
}

このコードは、配送日を指定するコードです。6行目で現在日時を挿入し、その日時の月末を配送日として設定します。しかし、現在日時が下旬や年末の場合は、配送日が次月に設定されます。

本章ではこのコードに対してリファクタリングを行っていきます。

最初のテストコード

さて、このようなコードに対して、どのようにテストケースを追加していけば良いでしょうか。

最初に作るべきテストコードでは「とりあえず動くテスト」を目指します。

今回の場合は以下のようなコードをとりあえず作ってみましょう。

//DeliveryDateTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class DeliveryDateTest {

    @Test
    void _配送日のテスト() {
        new DeliveryDate(); //DeliveryDateクラスを呼び出す
        assertEquals(1,1);
    }
} 

ただ、DeliveryDateクラスを呼び出しただけ*4です。

この状態でテスト実行をしてみましょう。もしもテスト実行してRedになった*5場合、以下の2つのどちらかが原因でしょう。

  • テストフレームワークの設定自体が間違っている
  • DeliveryDateクラスの呼び出しの際に必要な設定が足りない

このうち、他のクラスで同じテストフレームワークを用いて動いていた場合は、1つ目の原因の可能性は限りなく低いでしょう。つまり、今回のテストコードを実行することで、そもそも実装コードを実行できるのか確認することができます*6

テスト実行してGreenになった場合、次はメソッドを呼び出します。

//DeliveryDateTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class DeliveryDateTest {

    @Test
    void _配送日のテスト() {
        new DeliveryDate().getDeliveryDate(); //getDeliveryDateメソッドを実行する
        assertEquals(1,1);
    }
}

最初に書いたテストコードと同様、これもgetDeliveryDate()メソッドを実行できるのか確認することができます。

このようにテストコードを書くことで、実装コードを手軽に試すことができます。しかも一度書くと、自動で何度も実行することができます。

JaSST'18 Tokyo 招待講演*7 で、柴田芳樹さんは下記の発言をしていますが、今回の過程を写経すると実感ができると思います。

Unitテスト作成は自動でデバッグしている感覚

TDDは常に実装する感覚

仕様を理解してテストを作る

次は実装コードを見ながら期待値を当てはめていきます。

//DeliveryDateTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class DeliveryDateTest {

    @Test
    void _配送日のテスト() {
        LocalDate actualDate = new DeliveryDate().getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate); //期待値を変更する
    }
}

今回の場合、getDeliveryDateメソッドを呼び出すと、今日の日付を元に月末の日付などが返ってきます。このファイルを作成した日付が2020年9月13日だったため、月末である2020年9月30日を期待値として設定しました。

別のテストケースを作る

続いて、実装コードを見て、別のテストケースを作ってみましょう。

//DeliveryDateTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class DeliveryDateTest {

    @Test
    void _小の月の月末になる場合のテスト() {
        LocalDate actualDate = new DeliveryDate().getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

    //大の月のテストケースを追加
    @Test
    void _大の月の月末になる場合のテスト() {
    LocalDate actualDate = new DeliveryDate().getDeliveryDate();
    assertEquals(LocalDate.of(2020,10,31),actualDate);
    }

}

小の月の月末のテストケースを元々作っていたので、大の月の月末の場合のテストケースも作成しようと考えました。

しかし、両方のテストケースにおけるDeliveryDateクラスの呼び出し及びgetDeliveryDateメソッドの呼び出しに違いがない(パラメータの設定などを行っていない)ため、これら2つのテストケースを実行しようとすると、必ずどちらかのテストがRedになります。

つまり、この方法だとうまくいかないことが分かります。このような場合、どうすれば良いのでしょうか。

依存関係を見つける

今回のテストがうまく行かない理由を考えてみましょう。

//DeliveryDate.java
import java.time.LocalDate;
import java.time.Month;

public class DeliveryDate {
    public LocalDate getDeliveryDate(){
        LocalDate localDate = LocalDate.now(); //現在の時刻を代入
        int day = localDate.getDayOfMonth();
        Month month = localDate.getMonth();
        int year = localDate.getYear();

        if(day >= 25){
            month.plus(1L);
        } else if (month.equals(Month.DECEMBER) && day >= 20) {
            month.plus(1L);
        }

        int lastDay;
        if(month.equals(Month.APRIL)) {
            lastDay = 30;
        } else if(month.equals(Month.JUNE)){
            lastDay = 30;
        } else if(month.equals(Month.SEPTEMBER)){
            lastDay = 30;
        } else if(month.equals(Month.NOVEMBER)){
            lastDay = 30;
        } else if(month.equals(Month.FEBRUARY)){
            if(year%4 == 0){
                lastDay = 29;
            } else {
                lastDay = 28;
            }
        } else {
            lastDay = 31;
        }
        return LocalDate.of(localDate.getYear(),
            localDate.getMonth(), lastDay);
    }
}

今回、テストケースがうまく行かない最大の理由は6行目です。 LocalDate.now()で現在の時刻を入れているため、テスト実行日時に依存してしまうのです。

この依存関係を削除する方法を考えましょう。

依存関係を削除する

日時に関する部分をメソッドとして切り出すことで依存関係を削除します。

実際にリファクタリングを行う手順を細かく区切って説明します。それぞれの手順ごとにテストは常に実行し続けた方が良いでしょう。

作業前の状態

作業前のテストコードの状態を改めて記載しておきます。

//DeliveryDateTest.java
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.junit.jupiter.api.Assertions.*;

class DeliveryDateTest {

    @Test
    void _小の月の月末になる場合のテスト() {
        LocalDate actualDate = new DeliveryDate().getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

/* 大の月のテストケースはコメントアウトする
    @Test
    void _大の月の月末になる場合のテスト() {
        LocalDate actualDate = new DeliveryDate().getDeliveryDate();
        assertEquals(LocalDate.of(2020,10,31),actualDate);
    }
*/
}

なお、テストコードは「_大の月の月末になる場合のテスト」のテスト結果がRedになるため、今回はコメントアウトした状態で作業を始めます。

現在作成済みのテストケースが今回の狙いである依存関係部分の実装ロジックを通っているのか確認する

現在作成済みのテストケースを使って、カバレッジを計測します。実行すると、下記画像のようになります。

f:id:nihonbuson:20201210102003p:plain
カバレッジ計測結果

上記画像のうち、赤囲み部分(今回の狙いの部分である6行目)に注目してください。

行番号(6)の右隣にある色を確認します。ここが緑色だと既存のテストケースが実装ロジックを通っていることを示しています。もしも、ここが赤色だとテストケースが実装ロジックを通っていないことを示しているので、赤色の結果のまま、その行のリファクタリングをするのは止めましょう。

テスト実行に影響があるロジックを切り出す

先ほどのカバレッジ計測の結果、6行目はテストしている部分であることが分かったので、LocalDate localDate = LocalDate.now();の部分をメソッド化します。

//DeliveryDate.java
import java.time.LocalDate;
import java.time.Month;

public class DeliveryDate {
    public LocalDate getDeliveryDate(){
        LocalDate localDate = getNow(); //メソッド化
        int day = localDate.getDayOfMonth();
        Month month = localDate.getMonth();
        int year = localDate.getYear();

        if(day >= 25){
            month.plus(1L);
        } else if (month.equals(Month.DECEMBER) && day >= 20) {
            month.plus(1L);
        }

        int lastDay;
        if(month.equals(Month.APRIL)) {
            lastDay = 30;
        } else if(month.equals(Month.JUNE)){
            lastDay = 30;
        } else if(month.equals(Month.SEPTEMBER)){
            lastDay = 30;
        } else if(month.equals(Month.NOVEMBER)){
            lastDay = 30;
        } else if(month.equals(Month.FEBRUARY)){
            if(year%4 == 0){
                lastDay = 29;
            } else {
                lastDay = 28;
            }
        } else {
            lastDay = 31;
        }
        return LocalDate.of(localDate.getYear(),
            localDate.getMonth(), lastDay);
    }

    //メソッドの作成
    private LocalDate getNow() {
        return LocalDate.now();
    }
}

この時点でテスト実行して、テストがGreenを保っていることを確認します*8

テスト実行に影響があるメソッドにアクセスできる範囲を広げる

テストクラスからもアクセスできるように、privateからprotectedに変更します。

//DeliveryDate.java内のgetNowメソッド
    protected LocalDate getNow() {
        return LocalDate.now();
    }

この時点でテスト実行して、Greenを保っていることを確認します。

テスト実行に影響があるメソッドをテストクラス内でOverrideする

まず、テストクラス内にあるDeliveryDateクラスを変数化します。変数化してもテスト実行がGreenになることを確認します。

//DeliveryDateTest.java
    @Test
    void _小の月の月末になる場合のテスト() {
        DeliveryDate deliveryDate = new DeliveryDate(); //DeliveryDateクラスの変数化
        LocalDate actualDate = deliveryDate.getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

続いて、DeliveryDateクラスをFakeDeliveryDateクラスに変更します。この時点では、コンパイルエラーが発生します。

//DeliveryDateTest.java
    @Test
    void _小の月の月末になる場合のテスト() {
        DeliveryDate deliveryDate = new FakeDeliveryDate(); //FakeDeliveryDateクラスに変更
        LocalDate actualDate = deliveryDate.getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

コンパイルエラーの原因となっているFakeDeliveryDateクラスをインナークラスで作成します*9。この時点でテスト実行してGreenになることを確認します。

//DeliveryDateTest.java
    @Test
    void _小の月の月末になる場合のテスト() {
        DeliveryDate deliveryDate = new FakeDeliveryDate();
        LocalDate actualDate = deliveryDate.getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

    //FakeDeliveryDateクラスの作成
    private class FakeDeliveryDate extends DeliveryDate {
    }

インナークラスの中身にOverrideしたgetNow()メソッドを記述します。まずは、DeliveryDateクラスにあるgetNow()を継承します。この時点でテスト実行してGreenになることを確認します。

//DeliveryDateTest.java内にあるインナークラスFakeDeliveryDate
    private class FakeDeliveryDate extends DeliveryDate {
        //getNowメソッドを継承する
        @Override
        protected LocalDate getNow() {
            return super.getNow();
        }
    }

続いて、getNow()メソッドの中身を書き換えます。まずは、DeliveryDateクラスにあるgetNow()メソッドの中身をそのままコピペします*10。この時点でテスト実行してGreenになることを確認します。

//DeliveryDateTest.java内にあるインナークラスFakeDeliveryDate
    private class FakeDeliveryDate extends DeliveryDate {
        @Override
        protected LocalDate getNow() {
            //DeliveryDateクラスのgetNow()メソッドの中身をそのままコピペして持ってくる
            return LocalDate.now();
        }
    }

最後に、getNow()メソッドの中身を日付指定の処理に変更します。この時点でテスト実行してGreenになることを確認します*11

//DeliveryDateTest.java内にあるインナークラスFakeDeliveryDate
    private class FakeDeliveryDate extends DeliveryDate {
        @Override
        protected LocalDate getNow() {
            return LocalDate.of(2020,9,13); //日付指定の処理に変更
        }
    }

このように変更していくことで、実行日付に依存しているgetNow()メソッドのみを排除した形でテスト実行できるようにできました。

テストメソッド内で日付指定できるようにする

現在の状態でテスト実行ができるようになりましたが、このままだと必ず2020年9月13日が指定されてしまいます。そこでテストメソッドごとに日付指定できるようにします。

まずは日付指定ができるようなsetDate()メソッドを作成します。この時点ではsetDate()メソッドが存在しないためコンパイルエラーが発生します*12

//DeliveryDateTest.java内にある小の月の月末テスト
    @Test
    void _小の月の月末になる場合のテスト() {
        FakeDeliveryDate deliveryDate = new FakeDeliveryDate();
        deliveryDate.setDate(LocalDate.of(2020,9,13)); //setDate()メソッドの呼び出し。現在はsetDate()メソッドがないためコンパイルエラーが発生
        LocalDate actualDate = deliveryDate.getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

次に、setDate()メソッドを実装します。コンパイルエラーが解消され、テスト実行すると再びGreenになります。

//DeliveryDateTest.java
    @Test
    void _小の月の月末になる場合のテスト() {
        FakeDeliveryDate deliveryDate = new FakeDeliveryDate();
        deliveryDate.setDate(LocalDate.of(2020,9,13));
        LocalDate actualDate = deliveryDate.getDeliveryDate();
        assertEquals(LocalDate.of(2020,9,30),actualDate);
    }

    private class FakeDeliveryDate extends DeliveryDate {
        @Override
        protected LocalDate getNow() {
            return LocalDate.of(2020,9,13);
        }

        //setDate()メソッドの作成
        public void setDate(LocalDate date) {
        }
    }

setDate()メソッドの引数として入っているdateをフィールド変数に入れます。この時点でもテスト実行がGreenになり続けていることを確認します。

//DeliveryDateTest.java内にあるインナークラスFakeDeliveryDate
    private class FakeDeliveryDate extends DeliveryDate {
        LocalDate date;
        @Override
        protected LocalDate getNow() {
            return LocalDate.of(2020,9,13);
        }

        public void setDate(LocalDate date) {
            this.date = date; //引数をフィールド変数dateに代入する
        }
    }

フィールド変数のdateをgetNow()メソッド時に返すようにします。getNow()メソッドの処理が変わりましたが、この時点でもテスト実行がGreenになり続けていることを確認します。

//DeliveryDateTest.java内にあるインナークラスFakeDeliveryDate
    private class FakeDeliveryDate extends DeliveryDate {
        LocalDate date;
        @Override
        protected LocalDate getNow() {
            return date; //returnする値を変更する
        }

        public void setDate(LocalDate date) {
            this.date = date;
        }
    }

このように変更することで、テストケースの中で日付を設定してテスト実行することができるようになりました。

別のテストケースでもテスト実行ができるようにする

ここまで変更により、「_小の月の月末になる場合のテスト」のテストケースを日付を指定しつつ実行できるようになりました。

ここからは同様に、「_大の月の月末になる場合のテスト」のテストケースも実行できるようにします。

まずは「_大の月の月末になる場合のテスト」のコメントアウトを外します。この時点では(テスト実行日が大の月でない限り)テスト実行するとRedになります。

//DeliveryDateTest.java内にある大の月の月末テスト
    @Test
    void _大の月の月末になる場合のテスト() {
        LocalDate actualDate = new DeliveryDate().getDeliveryDate();
        assertEquals(LocalDate.of(2020,10,31),actualDate);
    }

次に、「_小の月の月末になる場合のテスト」のテストケースをコピペしてsetDate()メソッドの日付を(2020,10,13)に変更します。この時点でテスト実行してGreenになることを確認します。

//DeliveryDateTest.java内にある大の月の月末テスト
    @Test
    void _大の月の月末になる場合のテスト() {
        FakeDeliveryDate deliveryDate = new FakeDeliveryDate();
        deliveryDate.setDate(LocalDate.of(2020,10,13)); //先ほど作成したsetDate()メソッドを呼び出す
        LocalDate actualDate = deliveryDate.getDeliveryDate();
        assertEquals(LocalDate.of(2020,10,31),actualDate);
    }

これにより、「_大の月の月末になる場合のテスト」もテストができるようになりました。

おわりに:今回のレガシーコードのリファクタリングでのポイント

ここまでのリファクタリングによって、テストケースを注入することができました。

このコードはまだまだリファクタリングの余地があります。

  • 小の月と大の月に関するテストケースの拡充(実装コード上、それぞれの小の月を指定したif文で分岐していますが、テストコード上は9月しか小の月をテストできていません)
  • うるう年の場合のテストケースの拡充及びロジックの追加(本来のうるう年のルールはもっと複雑です)
  • もともとの実装コードはロングメソッドになっているので、責務を分割するためのメソッド化
  • 配送日が次月になるテストケースを拡充
  • 実装コードのロジックが間違っている箇所の修正(テストケースを拡充すると分かりますが、ロジックがそもそも間違っています)

テストケースを注入できたことで、最初よりも上記のリファクタリングも行いやすくなっているでしょう。

今回のポイントは以下の2点です。

  • まずはテストケースを1つでも作成して、とりあえずテスト実行をしてみる(頑張って、たくさんのテストを作ろうとしなくてOK)
  • テストしづらい部分を見つけて、テスト実行への障壁を排除する

また、今回に限らず、リファクタリングをする際に重要な点として、常に対象のテストを実行し続けていて、Greenでキープしたままリファクタリングを行っていることにも注目してください。

宣伝

実は本記事は、現在執筆中の技術同人誌『テストコードの注入から始めるレガシーコードのリファクタリング』の第1章の前半部分をブログ記事にしたものになります。

同人誌の中では、もっと複雑な題材も扱っていきたいと思いますので、興味がある方は下記のleanpubページから購入をお願いします*13 。同ページに無料サンプル版も載せています。

leanpub.com

また、Boothでも公開しています。価格はLeanpubよりも高くなります。ご了承ください。

nihonbuson.booth.pm

Boothのページ内にあるサンプル版はこちら。

nihonbuson.booth.pm

さらに、JaSST'21 Tokyoでは、今回よりも少し複雑な題材を用いて、ライブコーディング形式での発表をしました。

詳細は下記ページをご確認ください。

www.jasst.jp

JaSST Tokyoで発表した内容の詳しい解説も、書籍には掲載しています。

*1:ここでは書籍『レガシーコード改善ガイド』( https://www.amazon.co.jp/dp/B01AN97W08 )の考え方を引用し、「テストコードがないコード=レガシーコード」としています

*2:本記事を作成するにあたり、TDDBC Onlineの皆さんのアドバイスが大いに役立ちました。アドバイスをいただき本当にありがとうございました!

*3:このコードを見て「なんだよ!全然複雑じゃないじゃん!現場のコードはもっと複雑だから、こんな記事を見ても意味がないや」と感じた人もいるかもしれません。しかし、本記事で伝えたいことは「複雑なコードに対処すること」ではなく、「テストコードがないものに対処すること」です。テストコードがない状態に対して、一歩でも先に進む方法を本記事では紹介します。

*4:TDDの1周目のRedで行うことと同じ手順です

*5:テスト実行した結果、成功を「Green」、失敗を「Red」と言います。本記事では以降「Green」「Red」と表記します。詳しくは書籍『テスト駆動開発』( https://www.amazon.co.jp/dp/B077D2L69C/ )を参考にしてください。

*6:Redになったとしても、悲観する必要はありません。「設定が足りないことを教えてくれた」と、テストコードを書くことによって新情報を得られたことを喜ぶべきです。

*7:JaSST'18 Tokyo招待講演「私が経験したソフトウェアテストの変遷」 www.jasst.jp

*8:TDDではリファクタリングで変更するたびにGreenになっていることを確認します。レガシーコードのリファクタリングでも同様に、変更するたびにGreenになっていることを確認します。

*9:存在しないクラスをあたかも存在するかのように記述し、コンパイルエラーの内容に従ってクラスを作成するという今回の手順は、TDDでもよく行われるやり方です。

*10:まずはコピペをすることで、論理的にはテスト失敗に変化することはないはずです

*11:今回はステップを丁寧にして、まずは実装コードをコピペして、そこからロジックを変更していっています。これにより、「呼び出しの変更に対応」→「ロジックの変更に対応」と、気にする部分を分けてリファクタリングしています。

*12:TDDサイクルのRedの時の手順と似たようなやり方です

*13:「素早く出版できる」「一度購入してもらえれば、Updateがあれば購入者に無料で提供できる」「購入者がある程度自由に価格を決められる」という点で、私が本を出版するときはLeanpubを選択しちゃいます。Paypalに慣れてない人が多いかもしれないけど。