jarinosuke blog

about software engineering, mostly about iOS

UITestの設計での注意点

UITest

Xcode 7 から UITest という新しい仕組みが導入され、

今までの UI Automation による自動テストより遥かに簡単に UI に関するテストが

簡単に行うことができるようになった。

そこでこの記事では、UITest におけるテストの設計や導入の際に注意する点を挙げる。

なお実際の UITest の書き方やセットアップ方法などは取り上げない。

別プロセスで動く UITest と問題点

UITest は UnitTest とは違い App Target とは別のプロセスで実行される。

そのため、App Target に含まれているファイルにはアクセスできない。 (もちろん UITest 側でコンパイルすれば使えるが…)

なので UITest 側でアプリケーション側に影響を与えるコードは原則 UI に関してのみになる。

そこで問題になるのが、例えばボタンを押して通信成功後に画面遷移を確認したいテストの場合などだ。

本来ならアプリケーションに対して特定の通信に対してスタブを打ちたいところであるが

上記の通りそれは UITest 側からは行えない。

別プロセスで動いているので、もしスタブ処理を UITest 側にターゲット追加したとしても

NSURLSession が動作しないと思う。(未確認)

解決方法

上記の通りアプリケーションのプロセスと UITest のプロセスが分かれている。

しかし2つの間のインターフェースとなるものが一つあリ、それが Launch Arguments である。

それを用いて以下の流れで UITest 側から指定の処理をアプリケーションに振るまわせることが可能になる。

  1. UITest 側で Launch Arguments を指定
  2. アプリケーションを起動
  3. アプリケーションが受け取った Launch Arguments をもとに処理を実行

コード

実際のコードを以下に示す。

UITests.swift

func testSomething() {
  let app = XCUIApplication()
  app.launchArguments = ["stub"]
  app.launch()
}

AppDelegate.m

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  let arguments = NSProcessInfo.processInfo().arguments
  for argument in arguments {
    if argument == "stub" {
      //do stubbing
    }
}

こうすることで、UITest 側の任意のテストケースにおいて

アプリケーション側で指定の処理を実行することが可能になる。

しかしこうすることで他に問題が発生するので、それの説明と回避策を以下に挙げる。

配布アプリケーションからテストコードを引き剥がす

アプリケーション側で任意のテスト処理を実行できるようになったは良いが、

このままでは App Store へそのままテストコード入りのバイナリが上がってしまうことになる。

OSSなどを使っている場合、万が一 Private API を用いているテストライブラリがあったとすると悲しい。

例えば OHHTTPStubs は、Private API は使ってはいないものの、

申請時はなるべく外してほしい(意訳)と言及している。

スタブを例にとると、スタブ用のデータが静的ファイルなどの場合には申請時にはそのデータをバイナリに含めないようにしないといけない。

そのような場合に取る2つの方法を以下に挙げてこの記事は終了とする。

CocoaPods での Configuration に依存したリンク

以下の用に pod に対して configuration を付けることで

指定の configuration 時にのみインストールすることが可能になる。

pod 'OHHTTPStubs', :configurations => ['Release', 'AdHoc']

Target Membership を動的に変更する

CocoaPods による OSS だけであれば上記で十分だが、

たとえばアプリケーション内にスタブ用の静的ファイルも入れている場合は

UITest で使用する Configuration 時だけ静的ファイルをバイナリに入れるという処理をする必要がある。

その場合には User-Defined Build Setting にフラグを用意し、

Build Phase に Run Script を追加して以下のように流し込む方法があると思う。

if [ ${ENABLE_STUBBING} = 1 ]; then
cp path_to_resource/* ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/

参考

機械学習の勉強が一段落した

前回のブログから2ヶ月

位たってしまったけど、ようやく18週間コースの Machine Learning

を終えることができた。

恐らくこの2,3ヶ月で自分が聞いた声で最多なのは Andrew Ng 先生だろう。最後の感謝のビデオはちょっと感動した。

Certificates も現在申請中なので、受け取り次第ここに自己満足のために貼りたい。

20160401 追記

2週間くらい待ってようやく Certificate が届いた。

終了したと言ってもこれで実務で使えるようになるかと言ったらそんなことはあんまりなくて

あくまで知ってると言えるレベル。次はこの深層学習コースを進めていくつもり。

復習も兼ねて、コースで学んだことを記事にしていきたい。

機械学習の勉強をはじめた

最近巷で機械学習が流行ってる。

ミーハーな自分も例に漏れずに去年の末くらいから時間を見つけては勉強している

教材としては色々本など買ってはみたけれど、

大学のアカデミックな教材みたいな理論よりなものか、

手法として用いるだけで中身の理解までできないものの

二つが多くてしっくりくるものが少なかった。

Coursera

そこで新年になって Coursera の Machine Learning

がオススメと聞いて授業を受けている。

自身のスペック

自分のここらへんの教養としては、

  • 理系の学部レベルの確率・統計
  • 社会人数年レベルのプログラミング経験
  • 英語の読み・聴きはある程度できる

のように、実務で一度も機械学習などを使った経験はないが

うっすら確率・統計を覚えており、特に数学やプログラミングへの

アレルギーもないといった具合。

英語に関しては動画には字幕がついているので読めればOK

現状

提供されるコースも大変わかりやすいし、サンプルも豊富で

実際に自分で手を動かして習った理論を実装するテストもあるので

平日に毎日1時間授業を受けて、週末にテストを2時間くらいでパスするのがここ1ヶ月でようやく習慣化してきた。

f:id:jarinosuke0808:20171207113207p:plain

f:id:jarinosuke0808:20171207113233p:plain

と言いつつも上記をみてもらえば分かる通り、

まだ大学で習った線形回帰やロジスティック回帰など基礎の基礎しか

受講しておらず、ようやく来週から Neural Network に入るところではあるので

話半分に聞いておいてもらえると。

また進捗あったらブログに書きたい。

Swift のコードレビューで気をつけていること

Swift での iOS アプリ開発

徐々にですが、でも確実に色々な場面で Swift のコードを見る機会が増えてきたことを実感します。

iOS の設計思想など大枠の部分では Objective-C での知見は生きてきます。

しかし Swift の言語仕様についても知っておかないと

ついつい低きに流れて Objective-C ぽい Swift になってしまいがちです。

Swift のコードレビュー

そこで Swift らしく Swift の良さを活かしたコードにするためにコードレビューの話になるわけです。

iOS 開発全般におけるコードレビューについては以下のブログにまとまっているので省きます。

iOSアプリケーション開発のコードレビューで気をつけていること - ninjinkun's diary

また本記事を書くにあたって Swift コードレビューを調べていて良いものがまとまっていたので

fork して日本語訳にしたものもあります。

jarinosuke/swift-style-guide README_JP.md

日本語訳が怪しいところは[?]を付けているので、レビューしてもらえると助かります。

書こうと思って溜めていたことは大体上にも書いてあったので安心しました。

ということで以下から気をつけていることをいくつか挙げました。

以下に挙げる具体的な言語仕様や文言の翻訳などは詳解Swiftを参考にしています。

詳解 Swift

詳解 Swift

var なの?let じゃダメなの?」

強い型付けによる安全性を持っている Swift だからこそ、let でいけるところは全て let にしましょう。

var で定義されている変数については、その理由がコードを読んで理解できるかどうかで判断します。

「このオプショナル nil の可能性あるよね?クラッシュするよ?」

Swift の特徴ともいえるオプショナル型。

その変数に入っている値を実際に取得するために開示したいケースが多々あります。

そこでオプショナル型に対して使われるのが開示指定と呼ばれるもので、変数の後ろの! を付けるあれです。

手軽な反面、もし変数の値が nil な場合、ランタイム時にクラッシュしてしまいます。

なのでオプショナル型の開示指定は極力避けて、オプショナル束縛構文を用いるべきです。

if let と言った方がはやいですね。

また同様に暗黙的開示オプショナル型の定義も nil が入る可能性がないか慎重に行うべきです。

使うべきシーンとしては ViewController の GUI パーツなど、初期化時に必ず値が入っていることが

保証されている箇所などに限定するべきです。

self いらなくない?」

Objective-C では self を経由して自身のプロパティやメソッドにアクセスすることが普通でした。

Swift では self を使わずともそれらにアクセスすることができます。

なので self を用いる時はクロージャ内や名前空間を意識した場合などに限定して簡潔さを保つべきです。

まとめ

上記の翻訳ドキュメントには他にもまだまだ書いてありますので興味あればみてください。

またこれ以外にも「こんなところみてるよ」などあったらコメントなどで教えて下さい!

参考

実践 Auto Layout

今こそ frame 思考脱却の時

Xcode 4 / iOS 6 から存在していた Auto Layout でしたが、

当時は Interface Builder の Auto Layout 対応も中々ひどく、使うのが辛かった記憶があります。

そんななか僕は順調に layoutSubviews に傾倒していったわけですが、

iPhone 6/iPhone 6 plus がついに登場し、Size Class という新しい概念も投入され

現状では間違いなく2年前とは比べ物にならないレベルで Universal アプリは作りやすくなりました。(ただし iOS 8 専用アプリのみ)

ある程度のデザインパターンを懐に用意していた方が時間が省けます。

ここでは Auto Layout を用いたレイアウトに関するユースケース毎に簡潔に書いていますので、

「それ知ってるわ」みたいなのがあったら適宜読み飛ばしていって下さい。

また Auto Layout の基礎の基礎の部分は省いています。

等幅間隔で設置する

Auto Layout に少しずつ慣れてきて、今まで layoutSubviews でやってきたことをやろうとして

一番最初に「どうやるのこれ」ってなるのがこれかなと思います。

以下のリンクで僕も勉強させてもらいました。

Auto Layout は2つの View 間の制約しか設定出来ないため、等間隔を実現するとなると

間隔毎に見えない Spacer View を置いて、それらの幅を同じにする制約を張ることになります。

今までのレイアウト思考に慣れていると「えー、めちゃくちゃ余分な View 増えるな…」となりますが、それしか無いんです。しょうがない

中心から一定間隔ずらす

起動時のスプラッシュ画面も iOS 8 から nib で作成出来る様になり、

中心から50pt上にロゴを配置したいなどといった要件がある場合もあると思います。

こういった場合は Align Center Y を張った後に、その Constraint の constant を変更する事で可能です。

失敗例として自分がやっていたのは、ロゴを透明な Container View の中に置き

その Container View を Align Center Y して、その中でロゴを pin したりしていました。

UILabel を text の長さによって複数行にする

UILabel に入るテキストの長さによって、UILabel の大きさを変えたい事は多々あります。

手順を追いながら一つずつ簡単に説明します。

  • 1. numberOfLines プロパティを0に設定

numberOfLines に 0 を設定すると行数が無制限になります

  • 2. 位置を指定する Pin Constraints を設定

UILabel の size を決定する前に origin を決定します

  • 3. 高さを指定する Height Constraint を Low Prority(=250) で設定

可変の高さにしますが、height の制約は必要になります。 Priority を Low に設定する事で後述する設定が活きます。

  • 4. ContentCompressionResistancePriority(=251) より Height Constraint が低くなっている事を目視確認

Xcode 6 で確認した限りは UILabel の ContentCompressionResistancePriority はデフォルトで251となっていたので、 Low Priority の 250 よりは高くなっていると思いますが、目視で念のため確認します。

  • 5. ContentHuggingPriority(=750)の方が Height Constraint より高くなっている事を目し確認

4と同様、デフォルトで ContentHuggingPriority は750になっていると思いますが目視で確認します。

  • 6. preferredMaxLayoutWidth を設定

これを設定する事で UILabel が text から frame を計算する時の width を決定する足がかりとなります。

Xcode 6 から Automatic Preferred Max Layout Width なるものが Interface Builder に登場していますが、

残念ながら以下のリンクの通り iOS 8 以上サポートなので条件に満たさない場合はしっかり設定します。

Automatic Preferred Max Layout Width is not available on iOS version prior to 8.0

上記では簡単に説明しましたが、コードもしくは Interface Builder を用いた具体的な手順は以下のリンクが大変分かりやすいです。オススメです

UILabel sizeToFit doesn’t work with Auto Layout iOS 6

また上記で出てきた content compression resistence priority や content hugging priority、それに付随する intrinsicContentSize の説明は綺麗に省きましたが、以下のリンクが丁寧に解説されていて分かりやすいので参照下さい。

[iOS 7] Xcode 5 で始める Auto Layout 入門 #6 -補足編

UIScrollView の subview と Auto Layout と contentSize

Auto Layout 使用時には contentSize をコードなどで直接セットしてはいけません。

viewDidLayoutSubviews のタイミングとかでできてしまうと思うんですが不適切です。

Auto Layout 使用時には内部の subviews の制約により contentSize が決定されるべきです。

以下のリンクの文章を参考にすると contentSize を決定する際に、

UIScrollView And Autolayout | Technical Note TN2154

contentView の Constraints は自身のサイズ(contentSize)を UIScrollView の size とは独立して計算出来る様になっていなければいけない

と解釈しました。

何が言いたかったというと UIScrollView 内の Auto Layout は結構ややこしいため、よっぽど静的で単純でない限りは未だにコードで実装しています。

以下のスライドでも UIScrollView + Auto Layout について取り上げられていますが、完全に同意です。

黒魔術AutoLayoutとiPhone 6/6 plus

Auto Layout まめ知識

Interface Builder で行う Constraint 設置作業はスポーツに近いものがあります。

どんどん出てくる warning をモノともせず、無心で Constraint をはっていくのみです。

そんなときに以下のショートカットを知っておくと少しだけクリックの負担が減ります。

  • Update Frames shift + cmd + =

  • Update Constraints opt + cmd + =

欲を言うと、 Clear Constraints も欲しかった…

終わりに

まだまだ他にも Interface Builder 上では warning 出なくなったのに

Debugger Console でひたすら Unsatisfied Constraints が出てきたりする対策方法などの

デバッギングについて紹介したかったんですが、長くなってしまったので一旦これで。

他にも何かユースケースなどあったら追加しますので教えて下さい!

参考資料