こんにちは、株式会社カミナシのエンジニア @imu です。
はじめに
テスト駆動開発(以下、TDD)は知っているけど、業務でTDD使ってみようと思ってもリリースに追われてしまい、時間が取れなかったりしてチャレンジ出来ていませんでした。今回は時間に余裕があったのでTDDを実践してみました。
環境構築
ExpoCLIのインストール
npm install -g expo-cli
インストールが終わったらひな形を作成しましょう。 今回はManaged workflowのblank(TypeScript)を利用します。
expo init tdd_regexp
テストのパッケージをインストール
公式ドキュメントに従ってテストのパッケージをインストールします。
yarn add jest-expo @types/jest --dev
package.jsonに以下の設定を行います。
"scripts": { ... "test": "jest" }, "jest": { "preset": "jest-expo" }
TDD
目的
- 「キレイ」で「動作する」コードを書くこと
乱雑、混乱、散らかっているコードがないように整然と保つことかなと思っています。どうしてもリリース日間に合わせるために、「コレでいいか」と動作する方向だけに力を入れないようにしないといけないですね。
- エンジニアの心理的安全性
テストが成功している内はその動作は保証されていると言えます。(正しい実装であるかの保証は出来ません。あくまでテストが通ることが保証されているだけです)
これによってエンジニアは自身を持って大胆かつ、積極的にリファクタリングがしやすくなると思います。
テストコード大事ですね!
3つのサイクル
基本となる開発サイクルは以下になります。
- Red: 失敗するテストを書く
- Green: できる限り早く、テストに通るような最小限のコードを書く
- Refactor: コードの重複を除去する(リファクタリング)
【Red】失敗するテストを書く
最初に必ず落ちるテストを書き、テストが失敗することから始めます。
【Green】できる限り早く、テストに通るような最小限のコードを書く
どんなコードでも良い(マジックナンバー、固定値等)のでテストを成功させるコードを書いていきます。
【Refactor】コードの重複を除去する(リファクタリング)
意味のあるコードになっていれば終わりですが、Greenの段階で「とりあえず動いた」コードを放置してはいけません。
忙しいと思ってしまう「あとでキレイにします!」はやらないのと同義だと思うので、都度リファクタリングしていきましょう。
リファクタリングした後は、Red -> Green -> Refactorを繰り返していく手法となります。
TDDに沿ってやってみよう
今回はこのパターンだけ正となるようにします
yyyy/MM/dd
1回目
【Red】失敗するテストを書く
IsIncludesDate.ts
const isIncludesDate = (text: string): boolean => { const regexps: RegExp[] = [] return regexps.some(reg => reg.test(text)) } export default isIncludesDate
IsIncludesDate.test.ts
import isIncludesDate from './IsIncludesDate' describe('文字列が日付か正規表現で調べる', () => { test('yyyy/MM/dd', () => { expect(isIncludesDate('2000/01/01')).toBe(true) }) })
テストを実行してみましょう!ここで落ちなきゃダメです笑
【Green】動けば良いコードを実装
const isIncludesDate = (text: string): boolean => { const regexps: RegExp[] = [/^\d{4}\/\d{2}\/\d{2}$/] return regexps.some(reg => reg.test(text)) }
【Refactor】コードの重複を除去する(リファクタリング)
前のサイクルで正常になったので終わり…と言いたいところなんですが「yyyy/MM/dd」で日付じゃないパターンも想定しないといけないと思います。
それは次サイクルでやってみよう!
2回目
日付かどうか判定したい関数なので「0000/00/00」は日付と判定してほしくないことを確認します。
【Red】
describe('文字列が日付か正規表現で調べる', () => { test('yyyy/MM/dd - 異常値', () => { expect(isIncludesDate('0000/00/00')).toBe(false) }) })
テストを実行してみましょう! 日付と判定してほしくないのに関数の結果は日付を判定されていますね。 次サイクルでテストを成功させるようにしていきましょう。
【Green】
正規表現が「数値4桁/数値2桁/数値2桁」になっているので、日付の範囲になるように変更する
const isIncludesDate = (text: string): boolean => { const regexps: RegExp[] = [/^\d{4}\/(0?[1-9]|1[0-2])\/(0?[1-9]|[12][0-9]|3[01])$/] return regexps.some(reg => reg.test(text)) }
【Refactor】
リファクタリングも必要ないので今回はこれで終わりです! 正規表現のパターンを追加した場合は再度1回目からやっていきましょう!
最終的なテストコード
import isIncludesDate from './IsIncludesDate' describe('文字列が日付か正規表現で調べる', () => { test('yyyy/MM/dd - 正常値', () => { expect(isIncludesDate('2000/01/01')).toBe(true) expect(isIncludesDate('2000/1/1')).toBe(true) expect(isIncludesDate('2000/01/1')).toBe(true) expect(isIncludesDate('2000/1/01')).toBe(true) }) test('yyyy/MM/dd - 異常値', () => { expect(isIncludesDate('0000/00/00')).toBe(false) expect(isIncludesDate('2000/13/01')).toBe(false) expect(isIncludesDate('2000/01/32')).toBe(false) expect(isIncludesDate('9999/99/99')).toBe(false) expect(isIncludesDate('')).toBe(false) expect(isIncludesDate(null)).toBe(false) expect(isIncludesDate(undefined)).toBe(false) }) })
うるう年のチェックも出来れば正確になると思うので是非やってみてください!
おわりに
TDDのイメージはついたでしょうか?ロジカルな実装や修正が頻繁に入るところは、TDDを使うと良さそうです。すべてこの手法でするのは難しいですが、予めインプット、アウトプットが決まっている場合にはTDDを使って実装してきたいと思います!
正直なところ、この手順で合っているか不安ではありますが、まず第一歩が踏み出せた気がします笑
最後に弊社ではエンジニアを募集しております。 興味がある、話を聞いてみたい、応募したいという方はお気軽にご応募ください!