XCUITestを触ってみて

Xcode7から利用できるようになったXCUITestを触ってみたのでメモ。

第一印象

  • swiftでテストを書けるのは予想以上にいい

swiftのテストをswiftで書ける。実装とテストの境がより低くなる。やってみて初めて気づいたことだが、これは気分的に非常によい。ずっとswiftモードだから高い集中力を長く維持できる。ゾーンに入りっぱなし。

  • 最初のとっかかりはレコーディングが助けてくれる

すぐ慣れてサクサク書けるようになるが、それでもどのようにテストを書けばいいかわからない操作も時々出てくる。そのようなときはレコーディングすればすぐその操作のコードを生成できる。ただ、ジェスチャーによってはレコーディングされないので、テストするには工夫が必要になる場合もある。レコーディングでXcodeがしばしば落ちるのはどうにかならないものか。

  • すべてのUIオブジェクトを扱えるわけではない

XCUIElementTypeではないUIオブジェクトはどうも扱えない模様。例えばUIControlとか。Storyboard/IB/コードでAccessibilityIdentifierを指定していても取得できない。UIControlでグルーピングしてまとめて操作することもあるのにテストでは同じようにできないというのは少し厄介。

小ネタ

  • secure text field に入力できない

パスワード入れると ****** のようにマスクされるアレ。typeText()で入力できない。どうもバグらしく(or未対応)、現時点ではこんな感じにクリップボードからペーストという方法で解決するしかない。なんとも強引。


  • text fieldに入力できない、ことがある

複数のテキストフィールドがあると発生するとか、Xcode7beta4の頃から発生するようになったとかいう情報もある。どちらにしてもsecure text fieldのときと同じ方法で回避。

Neither element nor any descendant has keyboard... | Apple Developer Forums

  • ピンチインは動作するが、ピンチアウトが動作しない


How to pinch open

は動作するが、


How to pinch close

は動作しない。

  • po app.debugDescription が大活躍

画面に表示される要素の名称や正確な文字列などを調べるときに
po app.debugDescription が大活躍する。

位置づけ

全体的には好意的な印象を持った。Xcodeに含まれているのですぐ使い始めることができるし、使いやすい。
ただ、単純な不具合?がまだまだ多いので、そのあたりの品質が上がってくればいいのだが。

E2Eテスト自動化は完全(カバレッジ)を求めるとどうしても破綻しやすいので、ユニットテストと合わせて、コスパのいい範囲内で積極的に使っていきたい。
手動テストの削減、デグレの早期発見、仕様の明確化、などメリットは多いはず。今後のAppleに期待。

Swiftで書かれたiOSアプリをTravis CIでビルドしてFabric Beta / TestFlightで配信する

Swiftで書かれたiOSアプリをTravis CIでビルドしてFabric Betaで配信するまでのポイントまとめ。

.travis.ymlのlanguageはobjective-cにする

Swiftで書かれたiOSアプリの場合は、.travis.ymlにこんな感じに書きたいところだが、

language: swift

以下のエラーが発生してしまうため、

/home/travis/build.sh: line 42: /usr/local/bin/actool: Permission denied

次のように書く必要がある。

language: objective-c

証明書や鍵やProvisioningProfileは暗号化する

これはGitリポジトリを公開している場合などに必要な作業であって、プライベートリポジトリなどでは不要。

各ファイルの暗号化や、暗号化パスワードの.travis,ymlへの埋め込み、TravisCI上での復号化については以下のページが参考になる。
Travis CI for iOS · objc.io

暗号化する前の元ファイルをGitリポジトリに追加しないように注意。

ProvisioningProfileはFabric Betaの場合はAd-Hoc、TestFlightの場合はAppStoreを使う

Fabric Betaで配信する場合はProvisioningProfileはAd-Hoc、
TestFlightで配信する場合はProvisioningProfileはAppStoreにする必要がある。

対象Schemeはsharedにする

そのまんま。Xcode上で設定する。

Travis CI上のXcodeのデフォルトのバージョンと異なる場合は.travis.ymlに明示的に指定する

osx_image: xcode7.1

Travis CI上でshを呼び出す場合は実行権限を付与する

chmod a+x xxxxxxxx.sh

Bundle InstallやCocoaPods Installが毎回実行されないようにキャッシュする

cache:
    directories:
        - vendor/bundle
        - Pods

Travisの公式サイト上で紹介されている以下のような書き方では、自分の場合はGithub->Travisに連携しなくなってしまった。

cache:
    - bundler
    - cocoapods

CocoaPodsを使用している場合はワークスペースを.travis.ymlに明示的に指定する

xcode_workspace: xxxxxxx.xcworkspace

CocoaPods 0.38.2はエクスポートエラーになるため、0.39.0を使用する。

rdar://22740827: Xcode7 (7A220): xcodebuild -exportArchive with new -exportOptionsPlist option chokes on 'method' key in plist

複数のXcodeをインストールして、CocoaPodsの設定に失敗する事象

% bundle init
% vi Gemfile
gem 'cocoapods'
% bundle install
% bundle exec pod setup
% vi Podfile
target 'xxxxxxxx' do
pod 'OpenSSL'
end
% bundle exec pod install

としたときに

・・・
Building openssl-1.0.2d for iPhoneSimulator 8.4 i386
Please stand by...

となって失敗することがあります。

私が遭遇したケースでは、以下の中で紹介されている方法で解消しました。

github.com

その原因はOpenSSLのビルドプロセスが/Applications/Xcode.appがあることを期待して処理しようとするものの、
何らかの理由で/Applications/Xcode.appが存在しない、というものです。

私の場合は、複数Xcodeをインストールし、名前を変更して判別させていたため、デフォルトのパスにXcode.appがない状態でした。
/Applications/Xcode 6.4.app
/Applications/Xcode 7.0.app
/Applications/Xcode 7.1.app

そこで、Xcode 7.1.appをXcode.appに変更した上で、
gem uninstall cocoapods
bundle exec pod repo remove master
できれいにしてから再度最初からインストール・設定し直すと成功するようになりました。

Ruby on Railsの入門者用メモ

Ruby on Railsの入門者用メモです。

設計思想

DRY/CoC

コマンド

アプリケーションを作成する。
$ rails new applicationname

WebサーバーのWEBrickを起動する。
$ rails s

Scaffoldでオブジェクトを追加・更新・削除する雛形を生成する。
$ rails g scaffold modelname field:type field:type ...

DBを作成する。
$ rake db:migrate

Gemインストールをスキップして、アプリケーションを作成する。
$ rails new applicationname --skip-bundle

GemFileを設定してGemをインストールする。
$ bundle install

雛形ではなく独自に作成したい場合、Scaffoldではなく1.Model、2.Controller&Viewという順に作成する。
$ rails g model modelname field:type field:type ...
$ rails g controller controllername

現在使用中のDBにアクセスする。テーブル情報を表示する。レコードを表示する。抜ける。
$ rails db
> .schema
> select * from modelnames;
> .exit

コンソールを立ち上げる。抜ける。
$ rails console
> quit

URIを生成する。リソースと実装箇所を表示してくれる。
$ rake routes

・他のモデルに関連付けたモデルを作成する。
$ rails g model modelname field:type anothermodelname:references
$ vi app/model/anothermodelname.rb
has_many :modelnames を追加する

メモ(あとで整理する予定)

・GemFileでGemを管理する
rails sのsはserverの略
rails gのgはgenerateの略
・key:typeでtypeがstringの場合はkeyのみでよい(typeのデフォルトはstringのため)
・modelの名前は先頭大文字で単数形
・controllerの名前は先頭大文字で複数
・createはnew+save
・config/routes.rbでルーティングを設定する
・app/views/layouts/application.html.erbはアプリケーション共通テンプレート
・viewのファイルに書いた内容は<%= yield %>に反映される
・<%=render 'xxxx' %>と書くと、_xxxx.html.erbの内容が埋め込まれる
・フィールドのデフォルト値を定義する場合などは自動生成されるyyyyMMddhhmmss_create_modelnames.rbを書き換えてから、rake db:migrateを実行する。
====ここまでdotinstall

====ここからパーフェクトRails
・modelにはscopeを定義できる。scopeはよく使う検索結果みたいなもの。再利用性と可読性が上がる。default_scopeもあるが、チーム開発では見落としに要注意。
・1対多は、referencesというカラム型を多のほうに追加するマイグレーションを作成し、1のmodelでhas_many、多のmodelでbelongs_toを定義する。
・1対1は、has_manyの代わりにhas_oneを使う。
・多対多は、中間modelを作成し(referencesを2つもつ)、多の2つのmodelでhas_manyを定義する。
 例:BookとAuthorの場合(中間modelがBookAuthor)
 $ rails g model BookAuthor book:references author:references
 class Book < ActiveRecord::Base
  # ...省略...
  has_many :book_authors
  has_many :authors, through: :book_authors
 end
 class Author < ActiveRecord::Base
  # ...省略...
  has_many :book_authors
  has_many :books, through: :book_authors
 end
・バリデーションの「!」があるかないかは、バリデーション失敗時に例外が起こるかどうか。
・コールバックは実行されないメソッドもあるので注意が必要。
enum(Rails4.1以降)は、定数のように扱えるが実体は数値。DBを効率よく使える&可読性向上。
・最も基本的なルーティング
 resources :publishers
・ルーティングの拡張例(/publishers/:publisher_id/books/:idとか親子に、/pulishers/:id/detailとか個別に、/publishers/searchとか全体に)
 resources: publishers do
  resources :books
  member do
  get 'detail'
  end
  collection do
  get 'search'
  end
 end
・StrongParametersで脆弱性を防止
・テンプレートファイルはRAILS_ROOT/app/views/コントローラ名/アクション名.html.erbで検索される
・コンテンツのタイプによって表示を切り替える
 respond_to do |format|
  format.html
  format.csv
 end
・partialテンプレート
 <%= render partial: 'form' %> のようにpartialオプションをつけると、先頭が「_」で始まるファイル名のテンプレートから検索する。
 そのため、partialテンプレートのファイル名は先頭に「_」をつける。
・レイアウト用のテンプレート(メタタグ、ヘッダー、フッターなど)はapp/views/layouts/に配置する。
 デフォルトはapplication.html.erbファイルであり、その中の<%= yield %>にそれぞれのページのコンテンツが展開される。
 特定のレイアウトを使用したい場合はrenderメソッドのlayoutオプションで明示的に指定する。
・variantsで条件によってテンプレートを切り替え。モバイル対応とか。
 コントローラのbefore_actionでvariantsに条件ごとの値(例えば :mobile)を割り当て、通常はshow.html.erbだとすれば、show.html+mobile.erbが選択される。
・テンプレートエンジン
 ・ERB・・・Ruby/Rails標準、HTMLとほぼ同じ
 ・Haml・・・シンプルなインデントでHTML構造を表現
 ・Slim・・・Hamlよりシンプルで高速
・ヘルパー
 モデルから受け取ったデータをユーザーにとってわかりやすいフォーマットに変更する。
 Rails組み込みヘルパー:url_for/form_tag/form_for/stylesheet_link_tag/javascript_include_tag/distance_of_time_in_words_to_now/number_with_delimiter
 独自ヘルパーは、app/helpersに配置し、アプリ全体で使うものならapplication_helper.rb、コントローラごとに使うものならbooks_helper.rbなどとするのが一般的。
json構築にはjbuilderというgemを利用する。最近のRailsプロジェクトにはデフォルトで入っている。
 app/controllers/books_controller.rbで以下のように書いたら
 class BooksController
  def show
  @book = Book.find(params[:id])
  redpond_to do |format|
  format.json
  end
  end
 end
 app/views/books/show.json.jbuilderに以下のように書く
 json.extract! @book, :id, :name, :price, :created_at
 すると、以下のようなjsonが生成される
 {"id":1,"name":"Book 1","price":1000,"created_at":"2014-02-04T05:47:00.114Z"}
 ネストさせたり、if文やunless文も可能。
・アプリケーションの主要なロジックはなるべくモデルに書く。そうすることで、コードが整理される、テストがしやすい、などメリットがある。しかし、アプリケーションの複雑化に伴って、モデルが肥大化した状態「Fat Model」となるため、それは別の方法で整理する必要がある。
・Sidekiq:非同期処理ライブラリ
・Pry:irbをより高機能にしたREPL環境
・pry-rails:Pryをrails cで利用するためのgem、ブレークポイント
・pry-byebug:ステップ実行

Ruby on Rails Tutorialを2015年2月最新バージョンの環境で進めたら...

Ruby on Rails チュートリアル:実例を使って Rails を学ぼう

Ruby on Rails Tutorialを2015年2月最新バージョンの環境で進めたら、
こんな点でひっかかりました、というメモです。

チュートリアルに載っている環境は古いため、
どうせなら今後使用する環境に近いバージョンで、と考え、
あえて2015年2月最新バージョンで環境をセットアップしました。

その結果、主に以下の点でチュートリアルとは異なる環境となりました。

チュートリアルの環境>
Ruby 2.0.0
Rails 4.0.5
Bundler ?.?.?
rspec-rails 2.13.1
selenium-webdriver 2.35.1
Capybara 2.1.0
bootstrap 2.3
bcrypt-ruby 3.1.2
factory_girl_rails 4.2.1

<今回の環境>
Ruby 2.2.0
Rails 4.2.0
Bundler 1.8.2
rspec-rails 3.2.0
selenium-webdriver 2.44.0
Capybara 2.4.4
bootstrap-sass 3.3.3
sprockets 2.12.3
bcrypt-ruby 3.1.5
factory_girl_rails 4.4.1
rspec-its 1.2.0

◆ひっかかったポイントその1

「3.2.1 テスト駆動開発」で以下のコマンドを実行すると、

$ bundle exec rspec spec/requests/static_pages_spec.rb

以下のようなエラーになりました。

Failure/Error: visit '/static_pages/home'
     NoMethodError:
       undefined method `visit' for #<RSpec::ExampleGroups::StaticPages::HomePage:0x007fb6c8a41ce0>
1 example, 1 failures

以下のサイトによると、Capybaraをインストールしている場合はテストスクリプトはspec/features以下に置く必要があるとのこと。
RspecをRails 4.1で動かすまでの話 - Taught by Myself

spec/requests を spec/features にして再実行すると、

$ bundle exec rspec spec/features/static_pages_spec.rb

またしても前回と同じエラーです。

以下のサイトによると、、rspec-rails3系からrails_helper.rbが作られるようになり、
それに伴って、*_spec.rbの先頭は、
require 'spec_helper' ではなく、
require 'rails_helper' とする必要があるとのこと。
Rails4.1 with zeusでrspec3使おうとしたらいろいろハマった - Qiita

そこで、*_spec.rbを変更して再実行すると、

Failure/Error: expect(page).to have_content('Sample App')
       expected to find text "Sample App" in "StaticPages#home Find me in app/views/static_pages/home.html.erb"
1 example, 1 failures

正常に失敗です。TDDなので、これでOKです。
テストを通るようにhome.html.erbを書き換えてから再実行すると、

1 example, 0 failures

これで正常に成功です。(まぎらわしい。。)

ひっかかったポイントその1は以降、幾度となく出てきます。

◆ひっかかったポイントその2

「5.1.2BootstrapとカスタムCSS」、「5.1.3パーシャル (partial)」でチュートリアルのレイアウトと一致しません。

これは、単純にBootstrapの2系と3系の違いですね。Railsとは関係ないですが、一応書きます。次の2点を変更します。

(1)app/views/layouts/_header.html.erb を以下のように変更する。

<ul class="nav pull-right">

<ul class="nav navbar-nav navbar-right">

(2)app/views/static_pages/home.html.erb を以下のように変更する。

<div class="center herounit">

<div class="center jumbotron">

◆ひっかかったポイントその3

「5.2.2素晴らしい構文を備えたスタイルシート」で「Undefined variable: "$grayLight".」などのエラーになります。

以下のサイトによると、これもBootstrapの2系と3系の違いのようです。

After update to 3.0.0 - Undefined variable: "$grayLight". · Issue #463 · twbs/bootstrap-sass · GitHub

color: $grayLight; は
color: $gray-light; に変更して、
color: $grayDarker; は
color: $gray-darker; に変更するとエラーは消えます。

「7.1.4gravatar画像とサイドバー」でも
$grayLighter; が出てきますので、
$gray-lighter; と変更すると成功します。

◆ひっかかったポイントその4

「5.3.4RSpecを洗練させる」でspec/support/utilities.rbを作成してテストするところで「undefined method `full_title'」のエラーになります。

以下のサイトによると、rspecの2系と3系の違いのようです。

Rails - RSpec3でspec/support内のファイルを読み込む - Qiita

チュートリアルでは「spec/supportディレクトリはRSpecによって自動的に読み込まれる」と書かれていますが、それはどうも2系の話のようで、3系では自動的に読み込まれないようです。
spec/rails_helper.rb の
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
コメントアウトを外すと、spec/supportディレクトリは自動的に読み込まれるようになり、エラーは消えます。

◆ひっかかったポイントその5

「6.3.3ユーザー認証」において、誤ったパスワードで認証しようとしたらfalseになることを確認するテストを書きますが、チュートリアルの通りに書いてテストを実行すると、「Failure/Error: specify { expect(user_for_invalid_password).to be_false } expected false to respond to `false?`」エラーになります。

これはRSpec2系と3系の違いによるもので、以下のように変更してテストを実行すると通ります。

be_false

be_falsy

◆ひっかかったポイントその6

「7.2.2form_forを使用する」において、以下のようにdescriptionの部分文字列を指定してテストを実行しますが、思うように対象のテストが実行されません。

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"

どうやらチュートリアルtypoのようです。「7.2.1ユーザー登録のためのテスト」でテストのdescriptionが"signup page"ではなく、"signup"となっていました。そのため、以前の別の"signup"のテストまで実行されてしまっていました。

◆ひっかかったポイントその7

「7.2.2form_forを使用する」において、テストをパスするはずとされているテストが2つパスせず、2つとも「The action 'create' could not be found for UsersController」というエラーになります。

app/controllers/users_controller.rbを確認すると、確かにcreateはありません。読み落としていないかチュートリアルを検索してみたところ、以前の章ではなく、これから読み進める「7.3.1正しいフォーム」においてcreateは実装するようでした。そのため、ここはエラーのままで、次へ進むことにします。

◆ひっかかったポイントその8

「7.3.3ユーザー登録のエラーメッセージ」で、Bootstrapの2系と3系の違いから、チュートリアル通りにすると以下のエラーになります。

The selector ".control-group" was not found.
The selector ".error" was not found.

それぞれ、app/assets/stylesheets/custom.css.scssを以下のように修正します。

@extend .control-group

@extend .form-group
@extend .error

@extend .has-error

これでエラーはなくなりますが、.has-errorのスタイルはなくなったらしく表示に反映されません。詳しくは以下の記事を参照してください。
Railsのfield_with_errorsクラスにBootstrap3を適用 - Qiita

◆ひっかかったポイントその9

「8.2.1[このアカウント設定を保存する]」で、`method_missing': undefined method `its'というエラーが発生します。
itsメソッドはrspec3ではなくなり、rpsec-itsに分けられたようです。

gem rspec-its

として、bundle installすると、rspec-its 1.2.0が入り、テストも成功しました。

チュートリアルを完了するまで結構かかりそうなので進んだところまでを都度更新します。6/15現在、8.2.1です。

StoryBoardを使わないことにした理由

iOSアプリ開発において、StoryBoardを使う派ですか?使わない派ですか?

いろいろ試してみた結果、とあるプロジェクトでStoryBoardを使わないことにしました。
その理由を書きます。


1. コードとStoryBoardの関係がわかりにくい

StoryBoardを使うといっても普通はコードを書かないわけにはいかないので、StoryBoardを使う=StoryBoardを使うしコードも書く、ということになります。
つまり、StoryBoardとコードの両方を理解して初めて全体を把握できるということです。
StoryBoardでこれを定義すると、コードでいうところのどの処理までできるのか、ということがなかなか直感的にはわからなくて、StoryBoardとコードの間を脳内で埋める作業?が私には面倒でした。
これは、最終的にコードで理解したいかどうか次第で、人によって受け方が違ってくると思います。
私は、変更内容はDiffで明確にならないと気持ち悪い派なのです。

2. ちょっと複雑な構成になるとStoryBoardの難易度が高い

StoryBoardはとても簡単に画面や遷移を定義できますが、ちょっと複雑な構成になってくると、その難易度が高くなる印象がありました。
簡単なプロトタイプなどの作成ではStoryBoardのほうが速いのですが、複雑になるとStoryBoardでどう定義すればよいのか調べるのに結構時間がかかりました。
単にStoryBoardを高度に使いこなせていないだけかもしれませんが。。

3. まれにStoryBoard上の見た目とビルドしたアプリで差異が出ることがある

UITableCell絡みで、ビルドするとズレて表示されてしまうことがありました。
StoryBoard上で同じ場所に再定義(移動して同じ場所に戻す)するとズレが解消するのですが、開発を進めているとまたいつの間にかズレでしまうという。。
私の使い方が悪いのかわかりませんが、こういうのがまた気持ち悪さにつながってしまうんですよね。。

4. コードに慣れてくるとStoryBoardのありがたさが半減する

コード(Swift)に慣れてくると、StoryBoardとさほど変わらない速さで(というと大げさですが)開発を進めることができるようになったので、少なくとも速さの面ではありがたみが薄れました。



とはいえ、一方でStoryBoardの良さも実感できました。例えば、コードが少なくてスッキリ、画面遷移が見たままでわかる、分業しやすい、新規メンバーの理解の助けになる、など。適材適所に使い分ける必要がありそうですね。

ASP.NET MVCでPOSTされたデータを取得する

ASP.NET MVCでPOSTされたデータを取得したときのこと。

たとえば、

[HttpPost]
public ActionResult CreateData(string value)
{
    (省略)
}

のようなアクションメソッドがあったとしてPOSTされたデータを文字列として出力したい場合(ログ出力など)には、アクションメソッドのパラメータvalueを取得して間単に出力できます。

そして、それなりの規模になるとアクションメソッドの数もかなり多くなってくるため、その文字列出力処理も当然共通化しておきたいと考えるようになります。

ところが、アクションメソッドのパラメータをもとに出力するという共通処理は現実的ではありません。stringやintといった型だけでなく、独自に作成した型も対象としたい場合もあり、その型も様々なプロパティーを持ってたり、それがCollectionだったり、と多岐に渡ります。そうなってくると、GetType().GetProperties()で単純に取得することもできず、難しくなってきます。

根本的に見直さなければなりません。そこで、アクションメソッドのパラメータを使用しない方法を考えます。

アクションメソッド(POST)のパラメータはHTTPリクエストボディをパースしてControllerに渡されているのですが、パース前のHTTPリクエストから直接文字列として取得してしまえばいい、ということに気づきました。シリアライズしないので、こちらのほうがよっぽど単純ですね。

ということで、HttpRequestBaseのオブジェクトであるRequestを使います。

[HttpPost]
public ActionResult CreateData(userClass object)
{
    using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
    {
        string output = reader.ReadToEnd();
    }

    (省略)
}

よし取れた、と思ったのですが、取れたのは空文字でした。
ググっても上と同じようなソースコードが見つかります。
が、その中に以下のようなソースコードもありました。

[HttpPost]
public ActionResult CreateData(userClass object)
{
    Request.InputStream.Position = 0;
    using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
    {
        string output = reader.ReadToEnd();
    }

    (省略)
}

以下の記事によるとMVC2からMVC4の間で動作が変わったとのこと。


asp.net mvc - MVC 4 advancing Request.InputStream before reaching ModelBinder - Stack Overflow

これで目的の文字列を取得して出力することができました。