概要
この記事を読むことで、単体テストにおける基本的な考え方を具体例を交えて学習し、その後、Swiftプロジェクトにおける一般的な自動テストのツールであるXCTest
を用いた自動テストを実施できるようになります。
環境
Xcode15.0.1以上をインストール済みであること
(それ以前のバージョンでもXCTestの実施は可能ですが、Code Coverageの部分が異なります。)
単体テストとは?
単体テストはソフトウェア開発における重要な工程の一つで、開発したソフトウェアの内、1つのコンポーネントが正しく機能することを確認するために実施します。
単体テストの実行回数を出来るだけ減らし、効率的に単体テストを実施するために、まずは単体テストの種類について見ていきます。
単体テストの種類
今回、以下のような四則演算を行う関数を用意しました。その上で、どのようなテストケースを実装するべきかをテストケースの種類を含めて考えていきましょう。
関数名 | Input | Output |
---|---|---|
add | num1:Int,num2:Int | num1+num2の結果 |
subtract | num1:Int,num2:Int | num1-num2の結果 |
multiple | num1:Int,num2:Int | num1×num2の結果 |
divide | num1:Int,num2:Int | num1/num2の結果 |
単体テストの種類には、一般的に以下の種類が挙げられます。
種類 | 目的 | 具体例 | 注意点 |
---|---|---|---|
機能テスト (Functional Tests) | 関数が正しい計算結果を返すことを確認する。 | 今回の関数の例では、add ,subtract ,multiple ,divide 関数に自然数を与えたときに正しく動作するかをテストする。 |
具体的な機能の仕様に基づいてテストケースを設計し、正しい結果を期待してテストする。 |
境界(限界)値テスト (Boundary Tests) | ある関数が正しい計算結果を返すことを確認する。 | 今回の関数の例では、divide 関数で整数の最大/最小値に対する挙動をテストする。 |
境界値に対する振る舞いを特にテストし、境界値に対する期待結果を定義する。 |
異常系テスト (Error Cases Tests) | 関数がエラーケースを正しく処理することを確認する。 | 今回の関数の例では、divide 関数のnum1に自然数、num2 に0を与えた時に割り算にエラーがスローされることを確認する。 |
エラーが正しくスローされ、エラーメッセージが期待通りかどうかを確認する。 |
上記の例では0除算を行った場合をエラーパターンとしていますが、境界(限界)値テストの項目として考えるケースもあります。
divide
関数を例にテストケースを考えてみます。
それぞれのケースで何の値を引数として渡し、どういった結果が出力されればテストとして正しいかを考えてみましょう。
1.機能テストの場合
- 割り切れるケース ->
10/2が5と等しいか
- 割り切れないケース ->
10/3が3と等しいか
2.限界値テストの場合
-
Int.max(9,223,372,036,854,775,807:実行環境が64bitの場合)
に対して割り算を行った場合 ->Int.max / 2
が4,611,686,018,427,387,903
と等しいか -
Int.max-1
に対して割り算を行った場合 ->(Int.max-1) / 2
が4,611,686,018,427,387,904
と等しいか -
Int.max+1
に対して割り算を行った場合 -> オーバーフローが発生するため実行不可 -
Int.min(-9223372036854775808)
に対して割り算を行った場合 ->Int.min / 2
が4,611,686,018,427,387,903
と等しいか -
Int.min
-1に対して割り算を行った場合 -> オーバーフローが発生するため実行不可 -
Int.min+1
に対して割り算を行った場合 ->Int.min / 2
が-4611686018427387903
と等しいか
3.異常系テスト
- 0除算を行った場合 ->
5/0を行った場合、エラーが発生する
と考えてみます。
上記の内容を踏まえて、テストコードを書いてみましょう。
XCTestとは?
XCTestは、Appleが提供するソフトウェアテストフレームワークで、自動化されたテストスイート(テストケースの集まりのこと)を構築してアプリケーションの安定性と品質を向上させるのに役立ちます。
事前準備
ソースコードを用意しました。
以下のリンクからダウンロードし、Xcodeで開いてください。
XCTestSample
また、自動テストに移る前に画像の赤枠のCode Coverage
の設定を有効にしておいてください。
XCTestファイルの書き方
今回使用するファイルは
Calculator.swift
-
CalculatorTests.swift
です。
Calculator.swift
には単体テストの種類の項で挙げた関数が以下のように実装されています。
class Calculator {
// 足し算
func add(_ x: Int, _ y: Int) -> Int {
return x + y
}
// 引き算
func subtract(_ x: Int, _ y: Int) -> Int {
return x - y
}
// 掛け算
func multiple(_ x: Int, _ y: Int) -> Int {
return x * y
}
// 割り算
func divide(_ x: Int, _ y: Int) throws -> Int {
if y == 0 {
throw CalculatorError.divisionByZero
}
return x / y
}
}
enum CalculatorError: Error {
case divisionByZero
}
そして、具体例を挙げたテストケースはCalculatorTests.swift
内に以下のようなテストコードの方式で記述してあります。
import XCTest
@testable import XCTestSample
class CalculatorTests:XCTestCase{
var calculator:Calculator!
// 各テストメソッドごとの前処理
override func setUp(){
super.setUp()
self.calculator = Calculator()
}
// 各テストメソッドごとの後処理
override func tearDown() {
super.tearDown()
}
// ...コード省略...
// 機能テスト - 割り切れるケース
func testDivisionWithDivisibleNumbers() {
do {
XCTAssertEqual(try calculator.divide(10, 2), 5, "Division of 10 by 2 failed")
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// 機能テスト - 割り切れないケース
func testDivisionWithNonDivisibleNumbers() {
do {
XCTAssertEqual(try calculator.divide(10, 3), 3, "Division of 10 by 3 failed")
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// 限界値テスト
func testDivisionWithBoundaryValues() {
do {
XCTAssertEqual(try calculator.divide(Int.max, 2), 4_611_686_018_427_387_903, "Division of Int.max by 2 failed")
XCTAssertEqual(try calculator.divide(Int.max - 1, 2), 4_611_686_018_427_387_903, "Division of (Int.max - 1) by 2 failed")
XCTAssertEqual(try calculator.divide(Int.min, 2), -4_611_686_018_427_387_904, "Division of Int.min by 2 failed")
XCTAssertEqual(try calculator.divide(Int.min + 1, 2), -4_611_686_018_427_387_903, "Division of (Int.min + 1) by 2 failed")
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// 異常系テスト - 0除算
func testDivisionByZero() {
XCTAssertThrowsError(try calculator.divide(5, 0)) { error in
XCTAssertTrue(error is CalculatorError, "Division by zero did not throw CalculatorError")
}
}
}
テストファイルを作る際の注意点
- XCTestをimportをする
- @testable importを行う
- クラスはXCTestCaseクラスを継承する
- テストメソッドの名前はtestで始める(testDivideのような形式)
- テストを実行したいファイルとテストケースを記述したファイルがそれぞれTaegetに含まれていることを確認する
Assertの種類
XCTestではコードサンプルにあるように、XCTAssert〇〇のような関数を使用して値を評価します。
主な関数は以下の通りです。
より詳しく知りたい場合には公式ドキュメントを参照してください。
XCTest 関数名 | サンプルコードでの入力例 (Input) | サンプルコードでの出力例 (Output) | サンプルコードでの効果例 (Effect) | 使い所 (Use Case) |
---|---|---|---|---|
XCTAssertEqual | calculator.add(3, 5) | 8 | XCTAssertEqual(calculator.add(3, 5), 8, "Addition failed") | 引数として渡された値二つを比較し、数値が等しいかを確認 |
XCTAssertNotEqual | calculator.subtract(10, 3) | 7 | XCTAssertNotEqual(calculator.subtract(10, 3), 7, "Subtraction failed") | 引数として渡された値を比較し、数値が等しくないかを確認 |
XCTAssertGreaterThan | calculator.multiple(4, 7) | 28 | XCTAssertGreaterThan(calculator.multiple(4, 7), 20, "Multiplication failed") | 引数として渡された値二つを比較し、前者の数値が大きいかを確認 |
XCTAssertLessThan | calculator.divide(12, 4) | 3 | XCTAssertLessThan(calculator.divide(12, 4), 5, "Division failed") | 引数として渡された値二つを比較し、前者の数値が小さいかを確認 |
XCTAssertTrue | 無し | 無し | 無し | 引数として渡された条件が真であるかを確認 |
XCTAssertFalse | 無し | 無し | 無し | 引数として渡された条件が偽であるかを確認 |
XCTAssertNil | 無し | 無し | 無し | 引数として渡された値が nil であるかを確認 |
XCTAssertNotNil | 無し | 無し | 無し | 引数として渡された値が nil でないかを確認 |
XCTAssertThrowsError | try calculator.divide(5, 0) | Error | XCTAssertThrowsError(try calculator.divide(5, 0), "Division by zero should throw an error") | 引数として渡された値でエラーがスローされるかを確認 |
XCTAssertNoThrow | try calculator.divide(10, 2) | No Error | XCTAssertNoThrow(try calculator.divide(10, 2), "Division should not throw an error") | 引数として渡された値でエラーがスローされないかを確認 |
XCTestの実行方法
テストコードを記載後、Xcode左側に存在するテストタブを選択し、実行したいテスト関数の名前にカーソルを合わせると以下のように再生ボタンになります。
再生すると、テストコード内で記載されたテストが実行されます。
実行後、テスト結果のタブに移動すると、実行結果やテストカバレッジなどのテストに関する情報を参照できるようになっています。