勉強会「Swiftユニットテスト入門」を開催しました

こんにちは。ビザスクでiOSエンジニアをしている田中です!
この季節は寒い毎日が続きますね。みなさま体調は大丈夫でしょうか?
手洗いうがいがかかせないですね。

さて、そんな寒い中、1/25にサポーターズさんでSwiftユニットテスト入門というホットな勉強会を開催させていただきました。

本日は勉強会で発表させていただいた内容を簡単に紹介させていただきつつ、より良いテストを書くために弊社でも導入しているオープンソースのテストフレームワーク「Quick」を紹介させていただきます。

発表内容

先日の勉強会では、iOSアプリを作ったことはあるがユニットテストを書いたことがない方や、ユニットテストを書いたことがあるがうまく書けていないと悩んでいる方を対象に、下記の内容について発表させていただきました。

  1. XcodeでのXCTestを使ったユニットテストの始め方
  2. テストしやすいアプリの設計
  3. 実践的なユニットテスト

発表資料はこちらになります。

実際に動かせるサンプルコードはこちらです。
https://github.com/yopicpic/UnitTestSample

スライドの方では独自のAPIを呼び出す実装になっておりますが、サンプルの方はGitHubのAPIを呼び出すようになっているなど、スライドとサンプルコードで若干の違いがありますが要点は変わっておりません。

iOSアプリのユニットテストにご興味のある方は是非、ご一読ください!

テスティングフレームワーク「Quick」

勉強会の際にも少し言及させていただきましたが、iOSアプリのユニットテストを行う際はXCTestを直接使う以外にも、別のテスティングフレームワークを使うこともできます。
(といっても内部的にはXCTestを呼び出しています)

QucikはSwiftで使うことができるBDD(ビヘイビア駆動開発)のテストフレームワークです。

XCTestよりも書きやすい記述方法でテストを書くことができますので、テストを書くことに慣れてきたらご利用を検討してみても良いと思います。

インストール

QuickはCocoaPodsやCarthageでインストールできます。ここではCocoaPodsを使ったインストール方法をご紹介します。

# Podfile
target 'UnitTestSample' do
  use_frameworks!

  target 'UnitTestSampleTests' do
    inherit! :search_paths
    pod 'Quick'
    pod 'Nimble'
  end
end

# install command
pod install

Quickはテストでしか使わないのでテストターゲットにだけ追加するのがポイントです。
また、QuickはNimbleというマッチャーと一緒に使うことをオススメされており、せっかくなので一緒にインストールすることをオススメします。

Quickの使い方

Quickではdescribe,context,itを使ってテストを構造化することができます。

それぞれの記述すべき内容はこのようになります。

describe→テスト対象
context→テストの条件
it→期待される振る舞い

例えば、「メソッドAはBの時にCになる」というテストであればこのように構造化されます。

describe("A") {
  context("when B") {
    it("C") {
      // ここにテストコード
    }
  }
}

これに加えて、「メソッドAはDの時にEになる」というケースもあればこのように書けます。

describe("A") {
  context("when B") {
    it("C") {
      // ここにテストコード
    }
  }

  context("when D") {
    it("E") {
      // ここにテストコード
    }
  }
}

また、contextの中にcontextを入れることもできるので、このよう階層化もできます。

describe("A") {
  context("when B") {
    context("when C")
      it("D") {
        // ここにテストコード
      }
    }
  }
}

XCTestを直接使う場合は、

func testAWhenBItC()
func testAWhenBWhenCItD()

といったようにテスト名で工夫しないと表現できない階層構造を表現できるのがQuickの利点です。

また、XCTestのsetUp,tearDownにあたるbeforeEatch,afterEatchがquickにあります。
これはそれぞれのdescribe,context内に記述することができるので、共通のセットアップと個別のセットアップをわけて書くこともできます。

describe("A") {
  beforeEach { /*セットアップ*/ }
  afterEach { /*後片付け*/ }

  context("when B") {
    beforeEach { /*セットアップ*/ }
    afterEach { /*後片付け*/ }

    it("C") {
      // ここにテストコード
    }
  }

  context("when B") {
    beforeEach { /*セットアップ*/ }
    afterEach { /*後片付け*/ }

    it("C") {
      // ここにテストコード
    }
  }
}

実際のコード

実際のテストコードでXCTestとQuickの比較をしてみたいと思います。
前述のサンプルコードにXCTestで書かれたテストがありますが。
(サンプルだとsetUpメソッドを使ってませんがこちらでは比較のため使っています)

import XCTest

@testable import UnitTestSample

class UserPagePresenterTests: XCTestCase {
  var mockUserPageView: MockUserPageView!
  var mockUserService: MockUserService!
  var presenter: UserPagePresenter!

  override func setUp() {
    mockUserPageView = MockUserPageView()
    mockUserService = MockUserService()

    presenter = UserPagePresenter(view: mockUserPageView, userService: mockUserService)
  }

  func test正常にプロフィール取得() {
    let user = User()
    user.name = "yopi"
    user.imageURL = "http://dummy.co.jp/a.png"
    user.location = "Tokyo, Japan"
    mockUserService.profileResult = .success(user)
    let presenter = UserPagePresenter(view: mockUserPageView, userService: mockUserService)

    presenter.show()

    XCTAssertTrue(mockUserPageView.isIndicatorShown)
    XCTAssertEqual(mockUserPageView.name, "yopi")
    XCTAssertEqual(mockUserPageView.iconURL, "http://dummy.co.jp/a.png")
    XCTAssertEqual(mockUserPageView.location, "Tokyo, Japan")
    XCTAssertTrue(mockUserPageView.isIndicatorHidden)
  }
}

これをQucikで書いてみるとこのようになります。

import Nimble
import Quick
import XCTest

@testable import UnitTestSample

class UserPagePresenterQuickTests: QuickSpec {
  override func spec() {
    var mockUserPageView: MockUserPageView!
    var mockUserService: MockUserService!
    var presenter: UserPagePresenter!

    beforeEach {
      mockUserPageView = MockUserPageView()
      mockUserService = MockUserService()

      presenter = UserPagePresenter(view: mockUserPageView, userService: mockUserService)
    }

    describe("show()") {
      context("when api request succeeds") {
        var user: User!

        beforeEach {
          user = User()
          user.name = "yopi"
          user.imageURL = "http://dummy.co.jp/a.png"
          user.location = "Tokyo, Japan"
          mockUserService.profileResult = .success(user)
        }

        it("returns user") {
          presenter.show()

          expect(mockUserPageView.isIndicatorShown).to(beTrue())
          expect(mockUserPageView.name).to(equal("yopi"))
          expect(mockUserPageView.iconURL).to(equal("http://dummy.co.jp/a.png"))
          expect(mockUserPageView.location).to(equal("Tokyo, Japan"))
          expect(mockUserPageView.isIndicatorHidden).to(beTrue())
        }
      }
    }
  }
}

expect(“テスト対象”).to(“期待値”)という書き方はNimbleの書き方です。

Quickの方が記述量は増えておりますが、同クラスの別のメソッドのテストを追加したり、APIリクエストが失敗したケースを追加したりすることを想定すると、Quickの方が記述しやすく、わかりやすいのではないかと思います。

Quickのデメリット

XCTestを直接使う場合、テストクラスのクラス名と個々のテストメソッド名の左側にチェックマークが表示されると思います。

クラス名のチェックマークをクリックすると該当クラスの全てのテストが実行され、個別のテストメソッド名のチェックマークをクリックすると該当のテストメソッドだけが実行されます。

Quickの場合、クラス名のチェックマークは表示されますが、個別のテストのチェックマークは表示されません。そのため、個別のテストだけを実行したくても該当クラスのテスト全てを実行する必要があるのがデメリットではないかと思います。

まとめ

先日開催した勉強会「Swiftユニットテスト入門」についてご紹介させていただきました。テストを書いたことがない方や、なかなか上手くかけていない方をターゲットにしておりますので、ご興味がある方はぜひ資料をご覧ください。

BDDのテストフレームワーク「Quick」について紹介させていただきました。テストを書くことに慣れ、テストの構造が複雑化してきて困っている方には便利なフレームワークだと思います。

良いコード、良いプロダクトを生み出していくために、良いテストをかけるようになっていきたいですね。
お読みいただきありがとうございました!

一緒にやってくれる仲間を募集しています!

エンジニアを募集しています

ビザスクでは、エンジニアとして働きたい方を募集しています。
ご興味のある方は下記よりお気軽にご連絡ください。