20200208のSwiftに関する記事は7件です。

#24 チュートリアル的な初回起動時のみ表示されるViewController1例

はじめに

個人のメモ程度の出来なのであまり参考にしないで下さい.

環境

Xcode:11.2.1
Swift:5.1.2
2019/11

part1

Main.storyboardsegueを選択する.

スクリーンショット 2020-02-08 午後9.17.16.png

part2

segueを選択した状態で,Attributes inspectorIdentifierに任意の文字列を入れる.

スクリーンショット 2020-02-08 午後9.17.25.png

part3

AppDelegate.swiftapplication(_:didFinishLaunchingWithOptions:)に4行追加する.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        //この下の4行を追加
        let ud = UserDefaults.standard
        let firstLunchKey = "firstLunch"
        let firstLunch = [firstLunchKey: true]
        ud.register(defaults: firstLunch)

        return true
    }

part4

ホーム画面のViewControllerStartViewController.swiftviewDidAppear(_)に7行追加する.

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //この下の7行を追加
        let ud = UserDefaults.standard
        let firstLunchKey = "firstLunch"
        if ud.bool(forKey: firstLunchKey) {
            ud.set(false, forKey: firstLunchKey)
            ud.synchronize()
            self.performSegue(withIdentifier: "toAboutApp", sender: nil)
        }
}

part5

チュートリアル画面のViewControllerAboutAppViewController.swiftの「閉じる」ボタンのアクションにに1行追加する.

@IBAction func toback(_ sender: UIButton) {

        //この下の1行を追加
        self.dismiss(animated: true, completion: nil)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftの無限ループをforEachで実現する

はじめに

なんで余計なことを考えるのか。while( true ) {}を使いましょう。
(Swift触り始めて数日の初心者なので、間違いなどがある可能性があります)

きっかけ

ネットのどこかでみかけた以下のやりとり。

cソースからswift5に移行してるのですが、ループ処理の代替で詰まりました。
for (;;) {}
上記for文をswift5で書くとするとどう書くでしょうか?
while (true) {}
が無難ですかね...

それ以外ないような

やはりwhile文使うしか無さそうですね。ありがとうございました

foreachは?

無限ループforeachサンプルお願いします

ごめん勘違いしてたわ

作ってみたもの

cancelメソッドを実行するまで無限にnextできるイテレーター。

無限forEach
class Loop:Sequence,IteratorProtocol{
    typealias Element = Loop
    var _loopVal :Loop?

    init() { _loopVal = self }    
    func next() -> Loop? {return _loopVal }
    func cancel() { self._loopVal = nil }
}
forEachでの使い方
var cnt = 0
Loop().forEach { loop in 
    cnt += 1
    print(cnt)

    if cnt > 5 { loop.cancel() }
}
for..inでの使い方(breakで抜けるパターン)
var cnt = 0
for _ in Loop(){
    cnt += 1
    print(cnt)

    if cnt > 5 { break }
}

(使用例の条件が簡易すぎて用途が不明瞭すぎる…)

以下雑感(作ってる間に考えてたこととか)

  • 最初は単なる無限のジェネレーター(イテレーター、シーケンス)を作ればそれで完了だと思っていた。
    • for..inはいいけど、forEach{}(クロージャー)で使うと無限ループ終了できないね。うん。
    • Cancelの仕組みつくろか。 cancel:boolを条件値に持てばええな。
    • ……。ループするたびに判定処理とか実行コストアカンでしょ……。
    • selfを変数にいれて管理すれば判定いらんくなるな。(これでやっと↑のコードに)
ループ毎に判定してたやつ
class Loop:Sequence,IteratorProtocol{
    typealias Element = Loop
    var _cancel = false

    func next() -> Loop? {return self._cancel ? nil : self }
    func cancel() { self._cancel = true }
}


  • Cancelの仕組みはスレッドセーフの観点からみたらどうなる??
    • breakで抜ける分には、breakの条件になる値をスレッドセーフにして読み書きしてれば大丈夫だろうけど、Cancelでは次ループに入る可能性があるのでは?
    • ……残念なことに問題がでそうなコードを書いてみたけれど、おかしな値を出すことができなかった。
      (並列処理の書き方がよくない??)
スレッドセーフ検証
class Counter{
    var count: Int = 0;
    init(){}
}

func doAsyncs() {
    let group = DispatchGroup()
    let queue = DispatchQueue(label: "queue", attributes: .concurrent)

    let loop = Loop();
    let cnt = Counter();

    // 並列処理させるタスク
    // 共用のカウンタをインクリメントする
    // (微妙な動きさせたいので、一旦共用カウンタは排他制御なし)
    // 共用のカウンタが規定値超えたところでループを抜ける。
    let task = { (taskNo: Int) in
        var i = 0
        loop.forEach { l in
            if cnt.count > 100000 {
                l.cancel()
                // ループ抜けた時のタスクNoとタスク内カウンタを表示
                print("taskNo:\(taskNo)   i:\(i)")
                return
            }
            cnt.count += 1
            i += 1
        }
    }

    // タスク10個作って実行
    for i in 1 ... 10{
        let work =  DispatchWorkItem {
                task(i)
                group.leave()
            }
        group.enter()
        queue.async(execute: work)
    }

    // 全タスク終了で共用カウンタの値出力
    group.notify(queue: .main) { print("result=\(cnt.count)") }
}

doAsyncs()
出力結果
taskNo:7   i:10589
taskNo:2   i:10333
taskNo:10   i:10380
taskNo:4   i:10383
taskNo:1   i:10516
taskNo:3   i:10508
taskNo:6   i:10419
taskNo:8   i:10400
taskNo:9   i:10479
taskNo:5   i:10557
result=100001
  • iの合計値>ループ終了条件値なのは共用カウンタのインクリメントをアトミック操作してないため……。
  • Loopのインスタンスはタスクごとに別にしても、共用にしても結果変わらず。
  • そもそも終了条件をみたしているときはループを抜るので、問題にならないのか。
  • そうなると_loopVal = nilの操作がアトミックかを考えればいいだけかな?
  • Swiftがそのへんどうなのかは、また今度調べよう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftPMで�Quickを入れたら"No such module 'Quick'" というエラーが出た話

iOSテスト全書を読んでいると、第5章に「BDDによるアプリ開発」というQuick/Nimbleを利用したチュートリアルがあったので、早速やろうとしたらライブラリ導入の時点でつまづきました。

初歩的すぎて呆れる方もいるかもしれませんが、初心者の自分はこれで3時間以上溶かしてしまったので、同じ様な初心者の方にお役に立てれば幸いです。Qiita初投稿のため、内容に誤り等あればコメントでご指摘いただけると助かります。

ちなみに、簡単にSwiftPMの話もしています。

環境

MacOS Mojave 10.14.6
Xcode11.2.1
Swift5

まずライブラリを入れる

「iOSテスト全書」内ではCocoaPodsとCarthageでの導入手順が記載されていますが、私はSwiftPMで入れてみました。Xcode11からはGUIでパッケージ管理できるので便利ですね。
まずはXcodeのメニューから[File]→[Swift Packages]→[Add Package Dependency...]を選びます。
スクリーンショット 2020-02-08 17.22.00.png

すると、githubのリポジトリを検索するウインドウが出てくるので、お目当の「Quick」を検索します。ちなみに、github以外にあるものでもURLを指定すればパッケージを入れることができるそうです(私は未検証)。
スクリーンショット 2020-02-08 17.16.29.png

バージョンやブランチを聞かれます。今回は特にこだわらないのでそのままNextを押します。
スクリーンショット 2020-02-08 17.17.39.png

次は、ライブラリをどのターゲットに対して追加するかを聞かれます。デフォルトでは実際のソース(今回はqiitasample)が選ばれていますが、Quickはテストコードで使いたいライブラリなので、忘れずにテストコードの方(qiitasampleTests)を選びましょう。
スクリーンショット 2020-02-08 17.27.19.png

すると、Xcodeの左側に追加したパッケージと、その依存関係にあるパッケージが追加されます。Quickを入れただけでSwiftPMがNimbleも落としてきてくれたみたいですね。
スクリーンショット 2020-02-08 17.29.33.png

いきなりエラー

早速Quick/Nimbleを使ってテストコードを書いていきましょう!まずはQuickをimportして...
スクリーンショット 2020-02-08 17.36.45.png
それらしいものがサジェストされません。もう不穏ですね。
とりあえずQuickと手打ちしてビルド(Controll+B)してみると...
スクリーンショット 2020-02-08 17.36.59.png
No such module Quick
と言われてしまいました。いや、あるじゃん。
この後プロジェクトをクリーンしてみたり、エラー名でググってみたりしましたが、それらしい解決法に出会えずハマってしまいました...

解決法

どうにかできないかと思いXcodeのメニューを見ていると、[Product]の中に[Build for...]という項目を見つけました。よく見ると...
スクリーンショット 2020-02-08 17.41.38.png
Build For Testingがある!ここでようやく、Controll+Bではテストコードに対するビルドが行われていないことに気付きました。確かにXcode上部のバーにQuickがビルドされているような表示は出ていなかった気がする。
Build For Testingを実施すると、QuickやNimbleにもビルドがかかり、無事エラーも解消されました。

まとめ

  • テストコードに対してパッケージを追加した時は、Build For Testingを実行する。
  • ググってもわからないからといって諦めず、メニューをよく見てIDEの機能をよく確認する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドットインストールの動画を観てXcodeの環境構築をしたときに詰まったところ

Xcode11とドットインストール動画内での環境の違いと対策

これからSwiftの学習およびiOSアプリの開発を行いたいと考えている初学者の方が、恐らく私含めいらっしゃると思います。

動画で学習できるドットインストールを使って、まずは環境構築をしようとしたのですが、当時の動画内での設定(最終更新は2017年)とかなり違っていて時間を喰われたので、その具体的な内容と対策について情報共有します。

Qiitaへは初の投稿となり、至らないことも多いかと思いますが、該当される方は是非ご活用ください。

SwiftUI

まずはプロジェクトを作成する場面です。下記画像の「Create a new Xcode project」を選択します。
おそらくここまでは動画内の説明のまま進めて大丈夫です。

     スクリーンショット 2020-02-08 15.12.59.png

問題は次あたりに出てくるこの画面です。

     スクリーンショット 2020-02-08 15.15.23.png

User Interfaceの選択肢は、ドットインストールの動画内にはありませんでした。SwiftUIはざっと調べたところ、2019年6月ごろに発表された新しいUIのようです。

動画の通りに学習を進めていくと、「Main.storyboard」というファイル(iPhoneなどの画面にボタンなどの部品を配置できるファイル)を使うことがわかったのですが、SwiftUIを使用するとこれが見つかりません。おそらく代替ファイルのようなものはあると思いますが、慣れていないうちはStoryboardをUIとして選択することを推奨します。

アシスタントエディター

動画内では画面右上の方に○が2つ重なったようなアイコンをクリックしてアシスタントエディターを表示させていましたが、Xcode11ではそのアイコンが無くなっています。ググってみたところ、対策については割と多く情報がありましたが、設定でアイコンを表示させるというようなことはできなさそうです。
これについては「Xcode11 アシスタントエディター」で検索してみてください。

アシスタントエディターの画面分割

上記内容でググったところ、どうやらMain.storyboardファイルが表示されている状態で、編集したい新たなファイルをoption+クリックすればよいとの情報が多数確認できたので実際にやってみたんですが、画面が左右ではなく上下に分割されてしまいました。

このままでも良かったんですが、どうにもお互い狭い画面でしか確認できないのがストレスに感じそうだったので、解決方法を調べてみたところ、以下画像の一番左のアイコンで設定できることがわかりました。

           スクリーンショット 2020-02-08 15.20.47.png

画面全体でいうと一番右上あたりにまとまっていると思います。四角のなかに+マークがついているようなアイコンですが、こいつにoptionキーを押しながらカーソルを持っていくと、中の縦線の向きが変わります。(上記画像ではすでに左右分割の設定になっている)
これを変更した状態で再度、アシスタントエディターを表示したところ、無事に左右分割になってくれました。

他のサイト様を色々調べて回ったんですが、追加で表示したいファイルをoption+クリックで初めから左右分割できる、という記述が多く、自分の設定だけおかしいのか...と焦ってしまいました。

環境構築は私含め初学者の方が萎えやすいポイントかと思われますので、この記事が少しでもお力になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CombineLatest Streamのテスト手法

はじめに

テスト書いてますか?
テストコードが0のアプリにようやくテストコードを入れるようになり、RxTest/RxBlockingを使いはじめました。
CombineLatestで複数のObservabelをまとめて観測する値がtuple型のときに、テストで詰まったので解決策を残します。

環境

  • macOS Catalina 10.15.3
  • Xcode 11.3.1 (11C504)
  • RxSwift 5.0.1

Rxのテスト

Model

テストしたいModelを用意します。
今回はuserStream: Observable<(String, Int)>の値をテストします。

import RxSwift
import RxCocoa

class UserModel {

    let name: BehaviorRelay<String> = BehaviorRelay(value: "")
    let age: BehaviorRelay<Int> = BehaviorRelay(value: 0)

    // outputStream
    let userStream: Observable<(String, Int)>

    init(name: String, age: Int) {
        self.name.accept(name)
        self.age.accept(age)

        userStream = Observable.combineLatest(self.name, self.age)
    }
}

テストコード

それではUserModelのuserStreamのテストを書いてみます。

import XCTest
import RxSwift
import RxCocoa
import RxBlocking
import RxTest
@testable import TestApp

class TestAppTests: XCTestCase {

    var userModel: UserModel!
    var scheduler: TestScheduler!
    var disposeBag: DisposeBag!

    override func setUp() {
        userModel = UserModel(name: "takutoki", age: 5)
        scheduler = TestScheduler(initialClock: 0)
        disposeBag = DisposeBag()
        super.setUp()
    }

    func testObservableWithTuple() {
        // assertする型を定義
        let testObserver = scheduler.createObserver((String, Int).self)

        // テスト対象のbind先をtestObserverにする
        userModel.userStream
            .bind(to: testObserver)
            .disposed(by: disposeBag)

        // name に流すテストObservable
        let nameTestObservable = scheduler.createColdObservable([
            Recorded.next(10, "ShirasakaKoume"),
            Recorded.next(20, "KoshimizuSachiko"),
            Recorded.next(30, "HoshiSyoko")
            ])

        // age に流すテストObservable
        let ageTestObservable = scheduler.createColdObservable([
            Recorded.next(15, 13),
            Recorded.next(25, 14),
            Recorded.next(35, 15)
            ])

        nameTestObservable
            .bind(to: userModel.name)
            .disposed(by: disposeBag)

        ageTestObservable
            .bind(to: userModel.age)
            .disposed(by: disposeBag)

        // expect
        // 0: 初期値
        // 10,20,30: nameが変更されたときに流れる
        // 15,25,35: ageが変更されたときに流れる

        let expected: [Recorded<Event<(String, Int)>>] = [
            Recorded.next(0, ("takutoki", 5)),
            Recorded.next(10, ("ShirasakaKoume", 5)),
            Recorded.next(15, ("ShirasakaKoume", 13)),
            Recorded.next(20, ("KoshimizuSachiko", 13)),
            Recorded.next(25, ("KoshimizuSachiko", 14)),
            Recorded.next(30, ("HoshiSyoko", 14)),
            Recorded.next(35, ("HoshiSyoko", 15))
        ]

        scheduler.start()

        XCTAssertEqual(testObserver.events, expected) // Global function 'XCTAssertEqual(_:_:file:line:)' requires that '(String, Int)' conform to 'Equatable'
    }
}

XCTAssertEqualの行でコンパイル前にエラーが起きます。

余談ですが、Xcode10.3ではビルド時に以下のエラーが発生します。
これだと原因がいまいちはっきりしませんね。
実際の直面したときは以下のエラーだったので、解決法がなかなか見つかりませんでした。

Expression type '()' is ambiguous without more context

どうするか

エラーメッセージにある通りEquatableに準拠した型同士でassertするようにすれば良いです。
そこでstruct同士でassertを行うようにします。

struct AssertStruct: Equatable {

    var name: String
    var age: Int

    public static func == (lhs: AssertStruct, rhs: AssertStruct) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
}

Equatableを準拠したAssertStructを作成して ==メソッドを実装します。
テストも書き直します。

func testObservableWithTuple() {
        // assertする型をAssertStructに変更する
        let testObserver = scheduler.createObserver(AssertStruct.self)

        // userStreamをAssertStructに変換するoperatorを追加する
        userModel.userStream
            .map{ AssertStruct(name: $0.0, age: $0.1) }
            .bind(to: testObserver)
            .disposed(by: disposeBag)

        let nameTestObservable = scheduler.createColdObservable([
            Recorded.next(10, "ShirasakaKoume"),
            Recorded.next(20, "KoshimizuSachiko"),
            Recorded.next(30, "HoshiSyoko")
            ])

        let ageTestObservable = scheduler.createColdObservable([
            Recorded.next(15, 13),
            Recorded.next(25, 14),
            Recorded.next(35, 15)
            ])

        nameTestObservable
            .bind(to: userModel.name)
            .disposed(by: disposeBag)

        ageTestObservable
            .bind(to: userModel.age)
            .disposed(by: disposeBag)

        // 期待する値はAssertStructの型に変更する
        let expected: [Recorded<Event<AssertStruct>>] = [
            Recorded.next(0, AssertStruct(name: "takutoki", age: 5)),
            Recorded.next(10, AssertStruct(name: "ShirasakaKoume", age: 5)),
            Recorded.next(15, AssertStruct(name: "ShirasakaKoume", age: 13)),
            Recorded.next(20, AssertStruct(name: "KoshimizuSachiko", age: 13)),
            Recorded.next(25, AssertStruct(name: "KoshimizuSachiko", age: 14)),
            Recorded.next(30, AssertStruct(name: "HoshiSyoko", age: 14)),
            Recorded.next(35, AssertStruct(name: "HoshiSyoko", age: 15))
        ]

        scheduler.start()

        XCTAssertEqual(testObserver.events, expected)
    }

スクリーンショット 2020-02-08 15.24.36.png
All Greeeeen!!!

最後に

上記の例ではこんなんテストする必要あるんか?と思いますが、
実際の業務ではoperatorを複数噛ませたりdistinctUntilChangedなど入れたりと複雑な要件になりがちです。
テストでoutputを素早く確認できるのは安心ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MessageKit メッセージの削除

MessageKit でLineのように長押しでメッセージを削除

UICollectionViewの機能を利用する。
削除以外の他の処理も実装可能です。

継承クラスにメニューオブジェクトを定義

MessageChatViewControllerでチャット画面の実装、ChatMessageCellでテキスト、ChatPhotoCellで画像を表示しているものとする。

class MessageChatViewController: MessagesViewController {

    // 新しくロングタップメニューを追加する
    let messageMenuItem = UIMenuItem(title: "削除", action: #selector(ChatMessageCell.deleteMessageData(_:)))

    let photoMenuItem = UIMenuItem(title: "削除", action: #selector(ChatPhotoCell.deletePhotoData(_:)))
}

*ここはなぜかセルのメソッドでないと動かないっぽい

長押しの許可

ここから先はUICollectionViewDelegate, UICollectionViewFlowDelegateのメソッドでMessageChatViewControllerクラスに実装する。

    // 長押しを許可するか?
    override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {

        // 自分のメッセージのみ
        let message = self.messageForItem(at: indexPath, in: messagesCollectionView)
        if message.sender.senderId != currentSender().senderId {
            return false
        }
        // セル種別
        if let _ = collectionView.cellForItem(at: indexPath) as? ChatMessageCell {
          return true
        }
        if let _ = collectionView.cellForItem(at: indexPath) as? ChatPhotoCell {
          return true
        }
        return false
    }

メニュー選択時の処理の実行を許可

ちょっと面倒だけど編集時に編集を許可するとかそんな設計思考ににてる。
actionでメニュー選択時に実行する処理が渡されるのでdescriptionプロパティ でセレクタを判断してUIMenuItem生成時に設定したメソッドであればtrueを返す。

    // 長押しメニューを選択時に処理の実行を可能にするか?
    override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {

        // セル種別
        if let _ = collectionView.cellForItem(at: indexPath) as? ChatMessageCell {
          // 削除のみ
          if action.description == "deleteMessageData:" {
              return true
          }
        }

        if let _ = collectionView.cellForItem(at: indexPath) as? ChatPhotoCell {
          // 削除のみ
          if action.description == "deletePhotoData:" {
              return true
          }
        }

        return false
    }

こここでよくあるように「return super.collectionView(collectionView, canPerformAction: action, forItemAt: indexPath, withSender: sender)」を入れておくとコピーとかそんなデフォルトの動きもメニューに追加される。

長押しメニュー選択時の処理

ここまできてようやく実際の処理を記述する。
便利なのはindexPathとactionが渡されること。
長押しメニューで選択したときに自力で変数を渡す処理を書かなくていい。

    // 長押しメニュー実行時の処理 delegeteでセルから戻ってくる
    override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {

        let message = self.messageForItem(at: indexPath, in: messagesCollectionView)
        guard let message_id = Int(message.messageId) else { return }

        // セル種別
        if let _ = collectionView.cellForItem(at: indexPath) as? ChatMessageCell {
          // 削除のみ
          if action.description == "deleteMessageData:" {
                // テキストセル削除処理
          }
        }

        if let _ = collectionView.cellForItem(at: indexPath) as? ChatPhotoCell {
          // 削除のみ
          if action.description == "deletePhotoData:" {
              //画像セル削除処理
          }
        }
    }

セルにメソッドを追加

長押しメニューからの処理はセル→UICollectionViewのコールバックで行われます。UIMenuItem生成時に設定するメソッドの作成と、そのメソッドからUICollectionViewの処理を呼び出す部分を定義します。

    // メッセージ削除処理 delegeteでコールバックして実行する
    @objc func deleteMessageData(_ sender: Any?) {
        if let collectionView = self.superview as? UICollectionView {
            // indexPath取得
            if let indexPath = collectionView.indexPath(for: self) {
                // action実行
                collectionView.delegate?.collectionView?(collectionView, performAction: NSSelectorFromString("deleteMessageData:"), forItemAt: indexPath, withSender: sender)
            }
        }
    }

セルからindexPathを取得してセレクタ指定で処理を実行。
セレクタをメソッド名と同じにしてるけど、ここはおそらく別でもいけるはず。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XCTestのAssertion一覧、テストファイルの使い方について

XCTestのAssertion一覧と使い方について、頭に入りきっていないので自身のためのカンペを兼ねてまとめました。
筆者が初学者のため、噛み砕いた記事になります。誤字脱字、補足、ここおかしいよ!等歓迎します。

テストファイルの記入

import XCTest //XCTestのフレームワークをインポート

@testable import モジュール名 //モジュールをテスト用にインポート。モジュールが異なる場合、アクセス識別子がpublic,openでないとアクセスできないが、@testableとすることでinternal(明示的にアクセス識別子が書かれてない場合のデフォルト値)へアクセスできる。

class クラス名: XCTestCase //XCTestCaseを継承

XCTestのAssertion一覧

Assertion名の引数の最後にあるstringは試験失敗時のメッセージとして、文字列を入れることができる。試験の結果が確認しやすいよう状況に応じて使用する。

Boolean Assertions

Assertion名 説明
XCTAssert(expression,string) expressionがtrueであることを確認する
XCTAssertTrue(expression,string) expressionがtrueであることを確認する(XCTAssertと同じ)
XCTAssertFalse(expression,string) expressionがfalseであることを確認する

Nil and Non-nil Assertions

Assertion名 説明
XCTAssertNil(expression,string) expressionがnilであることを確認する
XCTAssertNotNil(expression,string) expressionがnilではないことを確認する

Comparable Value Assertions

Assertion名 説明
XCTAssertGreaterThan(expresstion1,expression2,string) expression1 > expression2となっていることを確認する
XCTAssertGreaterThanOrEqual(expresstion1,expression2,string) expression1 ≧ expression2となっていることを確認する
XCTAssertLessThan(expresstion1,expression2,string) expression1 < expression2となっていることを確認する
XCTAssertLessThanOrEqual(expresstion1,expression2,string) expression1 ≦ expression2となっていることを確認する

Equality and Inequality Assertions

Assertion名 説明
XCTAssertEqual(c,string expression1とexpression2を比較し、一致することを確認する
XCTAssertNotEqual(expresstion1,expression2,string) expression1とexpression2を比較し、一致しないことを確認する
XCTAssertEqualObjects(c,string expression1とexpression2を比較し同一オブジェクトであることを確認する(Objective-C専用)
XCTAssertNotEqualObjects(expresstion1,expression2,string) expression1とexpression2を比較し同一オブジェクトではないことを確認する(Objective-C専用)
XCTAssertEqualWithAccuracy(expresstion1,expression2,accuracy,string) expression1とexpression2を比較しaccuracyの範囲以内に差が収まっていることを確認する
XCTAssertNotEqualWithAccuracy(expresstion1,expression2,accuracy,string) expression1とexpression2を比較しaccuracyの範囲以内に差が収まっていないことを確認する

NSException Assertions

Assertion名 説明
XCTAssertThrows(expression,string) expressionで例外が発生することを確認する
XCTAssertNoThrows(expression,string) expressionで例外が発生しないことを確認する
XCTAssertThrowsSpecific(expression,exception_class,string) expressionで、特定のクラス(exception_class)で例外が発生することを確認する
XCTAssertNoThrowsSpecific(expression,exception_class,string) expressionで、特定のクラス(exception_class)で例外が発生しないことを確認する
XCTAssertThrowsSpecificNamed(expression,exception_class,exception_name,string) expressionで、特定のクラス(exception_class)の特定の例外(exception_name)が発生することを確認する
XCTAssertNoThrowsSpecificNamed(expression,exception_class,exception_name,string) expressionで、特定のクラス(exception_class)の特定の例外(exception_name)が発生しないことを確認する

Failing Unconditionally

Assertion名 説明
XCTFail(string) テストを失敗させる

参考
Apple Developer
XCTestのAssert一覧
iOSアプリ開発自動テストの教科書

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む