見出し画像

iOSのUnit Testを習慣づける提案

REALITYのiOSアプリを担当しているかむいです。
この記事はiOS #2 Advent Calendar 2020の24日の記事です。

バグや障害を未然に防ぐための取り組みの一つにUnit Test(以降: テスト)がありますが、Testableな設計になっていてもテスト作法や知見をチームで定期的に共有しあうこともまた大切だと思います。
今回は弊社iOSチームで共有された知見について書きたいと思います。

テストを書きやすくする取組み

テストが書けない事情は数あれど、同じ様にテストを書きづらい事情も多くあると思います。後者の場合、Testableな仕組みに出来てはいるけれども、テスト作法や知見の共有がチームで課題に上がっているところも多いのではないでしょうか。

REALITYのiOSチームでは、まだ適切な場所にテストが書けていない課題はあれど、テストを書く文化は着実に浸透してきているように感じます。しかし、私個人としてはよりテスト文化を浸透させるための知見の共有・書きやすくする取り組みといった点で課題を感じていました。

そこで今回はiOSチーム内であったテストを簡単に実行する知見・取り組みを紹介したいと思います。

Xcodeのテストショートカット

Xcode上でテスト用のショートカットは幾つかあります。

・⌘U: Build and Run All Test Cases
・⇧⌘U: Build the Test Target (without running any test cases)
・⌃⌘U: Run All the Test Cases (without building the test target)
・⌃⌥⌘U: Run Selected Test Case
・⌃⌥⌘G: Run Previous Test Case(s)

上のものは代表的なものですが、下2つのものは僕は最近初めて知りました。
⌃⌥⌘Uでは 現在コードエディタ上でカーソルがある行のテストメソッドをトリガーしてくれます。
また ⌃⌥⌘G は前回実施したテストコードを実行ができるショートカットです。同じテストを都度確認したい場合などに便利ですね。

参考サイト: 

コマンドでシンプルにテスト

上記ショートカットで解決も出来ますが、テストクラスへ移動 or テストメソッドのある行数までジャンプ or ファイルへタブ移動(Xcode11まであったタブ移動ショートカットもなくなってしまいましたね...)などの挙動も割愛しテストを実行する方法はないでしょうか。

考えたのは、ターミナルなどのコマンドラインツール上で短い命令文でテストを実行する手段です。
具体的には以下のコマンドを叩くだけで、特定のクラス・メソッドのテストを短時間で実行する方法です。

$ test ${テストクラス名} ※${メソッド名}
※記述なしの場合、テストクラスの全メソッドのテスト実施

使う手段は以下の通りです

・スクリプト作成
・Makefileにターゲット追加
・シェル用フレームワークでエイリアス設定

まずテストを実行するためのスクリプトを用意しました。引数でクラス名, メソッド名を渡せる形にし、特定のテストを実行する仕組みにしてみました。

#!bin/bash
FILE_PATHS=`find ./project_path -name $TEST_CLASS.swift -type f`
TMP_PATH=${FILE_PATHS%.*}

if [ ${FILE_PATHS[@]} -eq 0 -o -z "$FILE_PATHS" ]; then
 echo "$TEST_CLASS not found!"
 exit 1
else
 if [ $TEST_METHOD ]; then
   grep "$TEST_METHOD" $filePaths
   if [ $? -gt 0 ]; then 
     echo "$TEST_METHOD not found!"
     exit 1
   fi
   TMP_PATH="${TMP_PATH}/$TEST_METHOD()"
 fi
 TMP_PATH="-only-testing:${TMP_PATH:2}"
  
 IOS_VERSION=$(xcrun simctl list runtimes iOS | grep -o 'com.apple.CoreSimulator.SimRuntime.iOS.*' | tail -n 1 | sed 's/[()]//g' | sed 's/com.apple.CoreSimulator.SimRuntime.iOS-//g' | sed 's/-/./g')
 echo "Select iOS version is $IOS_VERSION"
 
 xcodebuild \
   -workspace myProject.xcworkspace \
   -scheme Debug \
   -configuration Debug \
   -sdk iphonesimulator \
   -destination "OS=${IOS_VERSION},name=iPhone 8,platform=iOS Simulator" $TMP_PATH \
   test | bundle exec xcpretty
fi

指定のクラス名が見つかった後でさらに引数にメソッド名があった場合、そのファイルの中にメソッド名があるかを見に行きます。

次の行でxcrun simctl list runtimes iOSコマンドを使いローカルで利用しているiOSのバージョンを見に行きます。Xcodeのアップデートがあるとチーム内で利用してるバージョンが一時的に揃わないことがあったため、スクリプト内で利用OSバージョンを特定し、xcodebuildコマンド実行時に使用するOSバージョンに設定します。

また-only-testingパラメータで特定のクラスやメソッドを指定すると、テスト実行時にシミュレータApp起動を省略することが可能です。シミュレータアプリが未起動の場合に、その起動時間をテスト時間から省略することができます。

次にこのスクリプトを呼ぶ処理を、Makefileを使いよりシンプルに呼び出せるようにしてみました

TEST_CLASS?=デフォルトで呼ぶテストクラス

unit-test:
   TEST_CLASS=${TEST_CLASS} TEST_METHOD=$(TEST_METHOD) sh ./script/test/unit_test.sh

更にもう一声、zshなどのシェルに対しカスタムエイリアスを組み、もう少し記述を簡略化することも考えてみました。例えばoh-my-zshだとcustom-aliases.zshファイルで以下のようなエイリアスを追加することで引数にクラス名やメソッド名を渡すことができます

alias test='(){make unit-test TEST_CLASS=$1 TEST_METHOD=$2}'

ここまで用意すれば、ターミナルなどのツールから短い命令文でテストを実行でき、かつ前回の命令をツール側が保持しているので上矢印キーの1タップで前回と同様のテストを実行できます。

最後に

テストを簡単に実行する仕組みについて書いてみました。
テストを書きやすい仕組みや知見があると、テストを書くモチベーションにも繋がってくると思います。

当然テストを書くためには、テスト作法やTestableな設計が必要不可欠ですが、まずはこうした簡単な1アクションからチームへ伝搬してみてはいかがでしょうか。

明日はRyosukeKamimuraさんSwiftUI で初回起動時のスライドチュートリアルを実装するです。 

書いた人

そんな今日はクリスマスイブですが、私は今日が誕生日です。
誕生日プレゼントにこちらの記事にいいねを押してもらえると嬉しいです。
それではメリークリスマスー🎄