テスト駆動開発ってどんなの?

2023-12-21

1. はじめに


みなさんはじめまして、jack 3年生のかわちゃんといいます。

普段は名古屋大学 情報学部コンピューター科学科に所属しながらのんびり開発しています。

本日のjack AdventCalenderでは、どこかで聞いたことがあるかもしれない、フロントエンド開発における開発手法の一つ、”テスト駆動開発”についてお話していきます!

⚠️
本ブログでは、僕の体験談を踏まえて話していくので間違った内容が入っている可能性があります。予めご了承ください!

2. ○○駆動開発って何?


本題に入っていく前に、まず「○○駆動開発」って何か、皆さんは知っていますか?

これは”○○”を主軸にして開発を進めていくことを指します。

よくエンジニアがネタにする、「飲酒駆動開発」は、”お酒を飲むこと”を主軸に開発を進めていきます。果たしてお酒を飲んで開発が進むのか…?という野暮な質問はしないでください。

3. テスト駆動開発とは何をするのか


さて、駆動開発が何者か分かったところで本題に入っていきましょう。

先ほどの例をもとに考えると、テスト駆動開発とは「テストを主軸にして開発を進めていく開発法」となります。

ではここでいうテストとは何でしょうか。

プログラムの世界では、”テスト”という概念があります。これは「このプログラムが正常に動作しているのか?」というのを確認するためのものです。

そしてそのテストの中にも、大きく分けて 静的テスト(Static Test)単体テスト(Unit Test)結合テスト(Integration Test)E2Eテスト(End to End Test)の4つの種類があります。

せっかくなので、それぞれについて少し詳しく見ていきましょう

静的テスト(Static Test)

これは、コードにタイプミスがないか、型エラーがないかを確認するためのテストです。

テストの一部に組み込まれますが、基本的に特別なコードを書くというわけではなく、静的型付け言語(TypeScriptなど)でコードを記載することで、静的型解析を行います

単体テスト(Unit Test)

ここでは、Header, Button, AppBarといった個々の独立したコンポーネントやget…()などの関数が正常に動作するのかを確認します。

Reactなどを用いて開発するフロントエンドのテストではよくJestが使われます。

ここでは、例として「数を引数に与えたら、その数の2倍の値を返す関数のテストコード」をJestで書いてみましょう。

まず、テストする関数をtestcode.jsに記載します。

class TestCode{
	doubleNum(num){
		return num*2
	}
}
module.exports = new Test();

続いて、この関数をテストするコードをtestcode.test.jsに

記載します。

const testcode = require("./testcode");
describe('TestCode', () => {
	test('3 times 2 should be 6', () => {
	  const result = service.doubleNum(3);
	  expect(result).toBe(6);
	});
})

肝心のテスト部分は expect(result).toBe(6)の部分です。

これは、”result”の値が”6”であるはずであることを指します。6となっていればテストが通り、6以外であれば弾かれます。

あとは npm test と実行することでテストコードが走ります。

このように各関数・コンポーネントに対してテストを行っていくものになります。

結合テスト(Integration Test)

これは、単体テストで確認してきた各コンポーネントや関数を組み合わせた際に正しく動作をするか確認するためのものです。単体テストと同じく、JestReact Testing Libraryを使用します。

書き方は単体テストとほぼ変わりません。

E2Eテスト(End to End Test)

終わりから終わりまで、という名の通り、最も広範囲なテストになります。

本番環境に準じたブラウザ上などでAPIなどを使用し、正しく動作するのか確認します。

ここではCypressを用いたE2Eテスト実装の簡単な例を見ていきましょう。

まず、package.jsonに起動コマンドを追記します

"scripts":{
  // code
	"cy:open": "cypress open"
  "cy:run": "cypress run",
  "cy:run:chrome": "cypress run --browser chrome",
  "cy:run:firefox": "cypress run --browser firefox"
}

続いて、起動するアプリのURLをcypress.jsonに記載します。

{
  "baseUrl": "http://localhost:3000"
}

最後にテストコードを任意のファイルに記載し、yarn startを実行したらテストが実施できます。テストが通るとReslutが表示され、通らないとその箇所についてのエラーを表示します。

// Cypressテストコードの例
describe('Cypress', () => {
  it('is active', () => {
    expect(true).to.equal(true)
  })
})

ここまででテストとはどういったものか、がわかったと思います。

テスト駆動開発では、開発をする前に”先にテストを書いて、それらを元にコードを書いていこう!”ということをしていきます。

テスト駆動開発の大まかなフローは以下のようになっています。

それぞれの工程について、少し詳しく見ていきましょう。

Red

ここではテスト駆動開発の軸であるテストを書きます。

実装する前に”実装完了後動くことを想定した”テストを書くため、当然エラーを吐いて何もテストが通りません。

そういった状況を指し、Redと呼ばれています。

Green

ここでは、先ほど書いたテストに通るような”(最低限度の)コード”を実装していきます。

後述するRefactoringとGreenを何回もループすることで、どんどんコードを良いものにしていくことが出来ます。

例えば1回目のサイクルでは定数を代入したうえでテストが通るようにして、2回目のサイクルではAPIを叩いて値を取得したうえでテストが通るようにして…といった感じで、どんどん機能を拡充させていく工程になっています。

Refactoring

ここでは、先ほどのGreenで書いたコードをブラッシュアップしていきます。

Greenでは、とりあえずテストに通れば良いので「変数名の命名規則の順守」「インデントをそろえる」「コメントを付ける」などといった読めるコードを書くことに関しては責任を持ちません

なので、このRefactoringの工程では、書いたコードを”読めるコード”に変えていきます。

これらの工程を繰り返すことで、テスト駆動開発は進んでいきます。

では、テストを軸に開発を進めることにどのようなメリット・デメリットがあるのでしょうか。簡単に触れていきたいと思います。

メリット

  • 開発者が仕様を正しく理解できる

      テストを書くためには、開発者が実装の流れをしっかり考え、把握している必要があります。そのため、テストが書き終わる頃には今から実装するプログラムの内容や工数、作成手順を理解できていることでしょう。

  • 実装時に余計なことを考えなくていい

      「今まで書いたコードはあってるのだろうか…」といった不安は誰しもあると思います。ですが、この開発法を取っていれば既に書き終わったコードの信頼性は担保されるので、そこを考えずにどんどん開発を進めることが出来ます。

デメリット

  • 開発開始までの準備に時間がかかる

      テストコードを書いてる時間は、実際のコードを書くことが出来ません。なので、開発開始までにかなりの時間を必要とし、工数が膨大なものとなってしまうことがあります

  • 頻繁な機能変更に弱い

      機能変更が入るたびにテストから書き直さなければならない為、追加実装の工数・労力が多くなってしまいます。

4. テスト駆動開発 is(was) NOT for me


僕がテスト駆動開発を使ったときは、残念ながらデメリットの方が色濃く出てしまいました。

使用した際のプロジェクトは、メンバーの実力がそこまであるわけではなく、仕様もガッチリ決まっているわけではありませんでした。

テスト駆動開発では、コードより先にテストを書く性質上通常の開発より実力・労力を必要とします。メンバーがフロントエンド作成・テスト作成に慣れていない場合、単純に工数が増えただけになりがちです。そのうえ、テストに漏れがあるとテストそのものの信頼性が低下してしまい、意味をなさないものとなってしまいます。

また、仕様が不安定な場合、テストを書いている途中で仕様に変更が入り作り直し…といったことが発生し、コードを全然書いてないのに日数だけが経過していく、なんてことにもなってしまいます。

では、どのようなプロジェクトでは恩恵を享受できるのでしょうか?

僕は以下の条件が整っているプロジェクトであれば、それなりの恩恵を受けられると考えています

  • メンバーがテストを書きなれている
      • テストを書く時間が大幅に減り、漏れも防止することが出来ます
  • 要件定義がしっかりと成されている
      • 単純に変更のためのコストを抑えることが出来ます
  • メンバーが途中で入れ替わる予定がある
      • テストさえ完成していれば、仕様への理解が浅くとも実装が出来ます
  • メンバーのモチベーションが高い
      • Red工程で心が折れかけるので、モチベーションが高いことは非常に大切です

5. まとめ


本ブログでは、テスト駆動開発について色々と書いてきました。

いかがでしたか?

テスト駆動開発は中々難しい開発法で、恩恵を受けるのは少し大変です。しかし、他にはない面白さがある開発法でもあるので、”使ってみたい!”という人は是非体験してみてください!

参考にしたサイト


以下のサイトを参考にさせていただきました。

おすすめ記事