この記事は ZOZO #3 Advent Calendar 2021 14日目の記事になります。
まず最初に怖い話をします。
あるところに、一つのプロジェクトがありました。
そのプロジェクトでは過去の反省から「ちゃんと単体テスト書いていこうね」と申し合わせた上ではじまったプロジェクトでした。しかし、終わってみればガバレッジ率が60%台でした。
おぉぉ怖っ!!!
もちろんガバレッジ率の高さ=品質というわけではないのはわかっています。
***カバレッジが低いと品質が「わからない」状態であること。***ということなのはわかってます。
そして、テストがあるということは、品質を上げるきっかけがあるということ。 ***品質を上げるのはソースコードと設計ということ。***というのもわかっています。
なのでこの現状を言語化すると、
・ 品質がわからない。
・ ソースコードや設計をリファクタリングできるきっかけがない。もしくはしずらい状態
なのでこの現状を放置していくと
・ ソースコードの状態が変わる度に品質が落ちていく可能性が高い。
・ さらにガバレッジ率が下がればますます品質が測れない
ということになるかと思います。
というのを理解した上で、今回の問題。
「ちゃんと単体テスト書いていこうね」と言っていたのにカバレッジが低い理由は?
というところです。
思うに、
・ 重複した、似たようなソースコードに対してテストを省いた。
・ テストを書きにくい箇所に対してテストを省いた。
・ テスト書く前に動作確認できてしまったのでテストを省いた。
・ とても単純なソースコードだったのでテストを省いた。
・ 期限に焦ってテストを省いた。
そんなところでしょうか?
しかしこれはエンジニアを責められるものではなく、もうエンジニアの習慣だということだと思います。
要するに「テストを書けと言われてかけるもんじゃなぁない」ということです。真理です。
もう普通は書かないのです。
しかし、これを放おっておいていいわけではなく、なんとかしたいと考えたときにピンと来たのが、、、
テスト駆動開発再入門
テスト駆動開発という有名な本があります。
過去ピアソン社から出版されていて、ピアソン社消滅の後和田さんによって再翻訳され、再出版されたものです。
出版当時その和田さんの解説が話題にもなり、自分も買ってパラパラ読んでみましたが、その後忘れて今に至っていました。
今回の件を踏まえ、改めて読んでみることにしました。再入門です。
『付録C 訳者解説:テスト駆動開発の現在』 の読み方
この項は訳者である和田さんが再出版に伴って寄稿されたもので、ユニットテストとテスト駆動開発の歴史を紐解く内容となっています。
xUnitからはじまり、Mockオブジェクトに対する解釈、テスト駆動開発における2派の分裂(モック主義TDD派とモック主義以前の古典的TDD派)。モック主義TDD派によって整理された『実践テスト駆動開発』のダブルループ。テスト駆動開発のテストは、本当にテストだろうか問題、「テスト」の4つの象限、BDD、RSpec 、Cucumber、TDD is Deadなどの批判。。。といった流れが丁寧に描かれててすごく参考になります。
この和田さんの記事を改めて読んでみて考えたことは3点ありました。
TDD is Dead. を誤解しないこと
これタイトルだけ読んで、あっ、、、TDDってやっても意味ないんだ。というのは完全な誤解です。
それを理解するためには先ほど紹介したユニットテストとテスト駆動開発の歴史を追うとよくわかります。
このブログ記事で批判していることは「すべての依存関係をモック化」してテストするような教条主義的なテストを批判しているのであって、決してテストコードを同時に書いていきながら開発・改善を回していくという本来のTDDの姿を否定しているわけではないということです。
テストファーストを誤解しないこと
TDDとは「テストを先にかかされる」テスト手法ではないということです。
もうひとつ、「先にテストを全部書いて」通す手法でもないです。
TDDの本質はテストコードを同時に書いていきながら、開発と、改善。。。つまりリファクタリングによる改善と再設計のループを回していくことです。
そしてテストファーストは「失敗する」テストを先に書いて通していく手法です。
こういったことができるようになり、リファクタリングができる環境が整ってくるわけです。
テストを書いても設計を改善しないのであれば、それはただの回帰テストであり、現状の追認でしかありません。テスト駆動開発における質の向上の手段は、リファクタリングによる継続的でインクリメンタルな設計であり、「単なるテストファースト」と「テスト駆動開発」の違いはそこにあります。
KentBeck. テスト駆動開発 (Japanese Edition) (pp.520-521). Kindle 版.
写経再入門
最初に述べたとおり、テストを書かないことはエンジニアの習慣です。逆に言えば、TDDを実践するのも習慣であるわけで、和田さんに言わせれば「個人のスキル」であるわけです。
というわけで、この項の最後に、TDDを身につけるための写経が紹介されているわけですが、興味深い写経のやり方を紹介いただいています。
- ローカルで使えるバージョン管理システム(Git等)を用意。
- 書見台などで対象の本を固定(あるいは、電子書籍を開く)。
- ひたすらサンプルコードを写して実行。
- 実行するたびにコミット(コミットログにページ番号を含める)。
- 疑問点があったらコミットログや本に書き込む。
- 章ごとにタグを打つ。
KentBeck. テスト駆動開発 (Japanese Edition) (pp.517-518). Kindle 版.
Gitを使うってなかなか新鮮で面白そうです。
確かにコミットログに残したり、章ごとにタグを打てばあとから見やすい。かつ差分ログみればそこで何をやったか一目瞭然。
いいやり方だなと思いました。
写経して理解したTDDのリファクタリング
ということでやってみました。写経。
ここで気づくことは人それぞれだと思うのでぜひやってみてほしいのですが、個人的気づいたことを最後に。
public class MoneyTest {
@Test
public void testMultiplication(){
Dollar five = new Dollar(5);
five.times(2);
Assertions.assertEquals(10, five.amount);
}
}
このテストに対して
public class Dollar {
int amount = 5 * 2;
public void Doller(int amount){
}
}
こんな実装コードを書いてGreenにするわけですが、もちろんこれでいいわけないのでリファクタリングするわけです。
ただ、どうリファクタリングするのか???
着目するのはテストの「5」「2」とソースコードの「5」「2」です。これを重複と見なすんですよね。テストコードとソースコードの重複に着目します。
なのでここがリファクタリング対象(ソースコードの重複)。
それでまず「5」の重複をなくすために以下のようになっていくという流れですね。
public class Dollar {
int amount
Dollar(int amount){
this.amount=amount;
}
void times(int multiplier){
amount=amount*2;
}
}
実際こんな細かくやらないと思いますが、TDDにおけるリファクタリングの理解は写経してみなかったらわからないままだったかもしれません。
TDDと写経。おすすめです。
明日は @zzt-osamuhanzawa さんの記事になりますね。自分も ZOZO #4 Advent Calendar 2021のほうに書きます。