+ 作る
図を読み込んでいます…
1 / 10
テスト駆動開発とは / 全5章

ユニットテストとは

発行
かえで
版
第2版
改訂
分量
10コマ・2,201字

アプリを小さな部品に分けて、1つずつ確かめるユニットテストの基本。入力と期待する答え、Arrange→Act→Assertの書き方、モックによる切り離し、そしてユニットテストだけでは見つからない問題まで、図が動く形で解説します。

ユニットテストとは

アプリは一つの大きなプログラムに見えますが、実はたくさんの小さな部品(関数やクラス)が組み合わさってできています。ユニットテストは、その部品を一つずつ取り出して「ちゃんと動くか」を確かめるための、小さなプログラムです。

この章では、ネットショップの「割引を計算する部品」を例に、ユニットテストが何を確かめているのか、どう書くのか、そしてなぜ役に立つのかを、図を動かしながら見ていきます。

  • ユニットテストとは
  • アプリを小さな部品に分けて、1つずつ確かめる
  • アプリ

アプリは小さな部品の組み合わせ

ネットショップのアプリを分解してみると、合計金額を計算する部品、割引を計算する部品、税を計算する部品、在庫を確認する部品……というように、役割ごとの部品に分かれています。コードの上では、これらはたいてい calcTotal() のような関数や、クラスとして書かれています。

アプリ全体をいきなり確かめようとすると、おかしな動きをしたときに、どこが悪いのか分かりにくくなります。そこで、まずは部品の単位で確かめよう、というのがユニットテストの考え方です。

  • アプリは小さな部品の組み合わせ
  • ネットショップのアプリ
  • 合計を計算
  • 割引を計算
  • 税を計算
  • calcTotal()
  • calcDiscount()
  • calcTax()
  • 在庫を確認
  • 決済する
  • 画面に表示
  • checkStock()
  • pay()
  • render()

ユニット=テストする最小の部品

「ユニット(unit)」は英語で「単位」という意味です。ユニットテストでは、アプリの中から部品を一つだけ取り出して、それ単体で正しく動くかを確かめます。

ここでは「割引を計算する部品」、つまり calcDiscount(price, rate) という関数を取り出しました。値段と割引率を渡すと、割引後の金額を返してくれる関数です。ほかの部品のことはいったん忘れて、この関数だけに集中するのがポイントです。

  • ユニット=テストする最小の部品
  • アプリ
  • 取り出す
  • 合計を計算
  • 税を計算
  • 割引を計算
  • calcDiscount(price, rate)
  • 在庫を確認
  • 決済する
  • 画面に表示
  • この1つの部品(関数)だけを確かめるのが、ユニットテスト

テスト=入力を入れて、答えを確かめる

ユニットテストがやっていることは、とてもシンプルです。部品に入力を渡し、返ってきた答えが、あらかじめ決めておいた「期待する答え」と同じかどうかを比べるだけです。

たとえば「1000円の商品に10%の割引をかけたら、900円になるはず」と決めておきます。実際に関数を動かして900円が返ってくれば合格(緑)、違う金額が返ってくれば不合格(赤)です。人間が電卓で確かめていた作業を、プログラムに代わりにやってもらうイメージです。

  • テスト=入力を入れて、答えを確かめる
  • 入力
  • 1000円
  • 実際の答え
  • 割引を計算
  • 900円
  • 10%
  • calcDiscount()
  • =
  • OK
  • 900円
  • 期待する答え
  • 実際の答えが期待どおりなら合格(緑)、違えば不合格(赤)

書き方の型:Arrange → Act → Assert

ユニットテストは、たいてい3つの段階で書きます。まず Arrange(準備) で、テストに使う値をそろえます。次に Act(実行) で、調べたい関数を1回だけ呼び出します。最後に Assert(確認) で、返ってきた結果が期待どおりかを確かめます。

前のページの「入力」「部品」「答え」が、そのままこの3段に入れ替わったのが分かるでしょうか。expect(result).toBe(900) は「resultは900であるはずだ」と読みます。どんなに複雑なテストでも、この型に当てはめると読みやすくなります。

  • 書き方の型:Arrange → Act → Assert
  • discount.test.ts
  • const price = 1000
  • ① Arrange(準備)
  • const rate = 0.1
  • 材料をそろえる
  • const result = calcDiscount(price, rate)
  • ② Act(実行)
  • 調べたい関数を1回呼ぶ
  • expect(result).toBe(900)
  • OK
  • ③ Assert(確認)
  • 結果を期待値と比べる
  • どんなユニットテストも、この3段で書ける

すべての部品にテストを付けておく

同じようにして、合計の計算、税の計算、在庫の確認……と、部品ごとにユニットテストを書いておきます。右上の緑の丸は、その部品のテストが全部合格していることを表しています。

ユニットテストは一つあたり数ミリ秒で終わるので、何百個あっても数秒で全部を実行できます。コードを少し変えるたびに全部流しても、ほとんど苦になりません。

  • すべての部品にテストを付けておく
  • ネットショップのアプリ
  • 合計を計算
  • 割引を計算
  • 税を計算
  • calcTotal()
  • calcDiscount()
  • calcTax()
  • 在庫を確認
  • 決済する
  • 画面に表示
  • checkStock()
  • pay()
  • render()
  • 緑の丸=その部品のテストが全部合格

部品を壊すと、その部品のテストだけが赤くなる

ある日、誰かが割引の計算式をうっかり書き間違えたとします。テストを実行すると、割引を計算する部品のテストだけが赤くなりました。ほかの部品は緑のままです。

これがユニットテストのいちばんうれしいところです。「どこかがおかしい」ではなく「この部品のこのケースがおかしい」と、壊れた場所をピンポイントで教えてくれます。だから、コードを直したり整理したりするのが怖くなくなります。

  • 部品を壊すと、その部品のテストだけが赤くなる
  • ネットショップのアプリ
  • 修理
  • 合計を計算
  • 割引を計算
  • 税を計算
  • calcTotal()
  • calcDiscount()
  • calcTax()
  • 在庫を確認
  • 決済する
  • 画面に表示
  • checkStock()
  • pay()
  • render()
  • 壊れた場所が一目で分かる → 直すのも、整理するのも怖くない

部品が外の世界に頼っていると……

実際の部品は、データベースや外部のサービスなど「外の世界」に問い合わせながら動くことがよくあります。たとえば割引の部品が、クーポンコードの割引率をデータベースに聞きに行く場合です。

このままテストすると、データベースへの接続に時間がかかったり、誰かがデータを書き換えたせいで結果が変わったり、ネットワークがないと動かなかったりします。部品そのものは正しいのに、テストが遅く、不安定になってしまうのです。

  • 部品が「外の世界」に頼っていると…
  • データベース
  • 割引を計算
  • SAVE10の割引率は?
  • 本物のクーポンDB
  • calcDiscount(code)
  • 10%
  • 遅い・中身が変わる・ ネットが必要
  • 外につながったままだと、テストが遅く・不安定になる

外の部分はニセモノ(モック)に差し替える

そこでユニットテストでは、外の世界の部分を「ニセモノ」に差し替えます。これをモック(mock)と呼びます。モックは「SAVE10と聞かれたら10%と答える」のように、決まった答えだけを返す簡単な代役です。

こうすると、テストは一瞬で終わり、何度実行しても同じ結果になります。確かめたいのはあくまで「割引の計算が正しいか」なので、データベースが本物かどうかは、この段階では関係ないのです。

  • 外の部分はニセモノ(モック)に差し替える
  • 荷物
  • 割引を計算
  • SAVE10の割引率は?
  • ニセモノ(モック)
  • calcDiscount(code)
  • 10%(いつも同じ)
  • 「SAVE10なら10%」と 決まった答えを返すだけ
  • 部品だけを、速く・毎回同じ結果で確かめられる

でも、部品どうしをつなぐと壊れることがある

ユニットテストには弱点もあります。割引の部品は「割引額(100円)」を返すつもりで作られ、決済の部品は「割引後の支払額(900円)」が渡ってくるつもりで作られていたとします。それぞれのユニットテストは、どちらも合格します。

ところが二つをつなぐと、決済の部品は100円を支払額だと思って処理してしまいます。部品ひとつひとつは正しくても、つなぎ目の「約束」がずれていると壊れるのです。この問題を見つけるのが、次の章で紹介する統合テストです。

  • でも、部品どうしをつなぐと壊れることがある
  • NG
  • 割引を計算
  • 決済する
  • 「割引額 100円」を返す
  • 100円
  • 「支払額」が届くと思っている
  • どちらも単体テストは合格。なのに、つなぐと金額がおかしくなる
  • → 次の章「統合テスト」で、つなぎ目を確かめる

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]