KIFでiOSアプリケーションのUIテストを自動化する
iOSネイティブアプリケーションのUIテストの自動化方法をまとめます。
最近のUIテスト自動化ツールとして代表的なものは↓です。
- Calabash
- KIF
- MonkeyRunner
MonkeyRunnerは触ったことがないのでコメント出来ませんが
CalabashはRubyでコードを書く必要があって慣れないので
今回はKIFを使うパターンを紹介します。
※XCTestでのテストに慣れているならKIFの方がとっつき易いと思います。
検証環境は以下の通りです。
1. KIFインストール用のPodfileを作成する
CocoaPodsを使ってインストールしていきます。
公式サイトに習い、定義ファイル(Podfile)は以下のように書きます。
target 'テスト対象ターゲット', :exclusive => true do pod 'KIF', '~> 3.0', :configurations => ['Debug'] end
「テスト対象ターゲット」の部分はお手元の環境に合わせましょう。
作成したPodfileはテスト対象プロジェクトの直下に保存します。
2. KIFをインストールする
ターミナルを起動して、テスト対象プロジェクトの直下に移動します。
その状態でCocoaPodのインストールコマンドを実行します。
pod install
3. テストクラスを作成する
作成されたXcodeワークスペースファイルをXcodeで開きます。
※Xcodeプロジェクトは開かないように注意です!
テストクラスを作成していきます。
#import <UIKit/UIKit.h> #import <XCTest/XCTest.h> #import <KIF/KIF.h> @interface KifAppTests : KIFTestCase @end @implementation KifAppTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { // ここにテストコードを実装していくよ! }
まずはKIFの必要クラスをインポートしていきます。
(KIF/KIF.hの部分ですね!)
続いて、テストクラスの継承元クラスを設定します。
KIF用のテストクラスになるので、継承元クラスは”KIFTestCase”を使います。
4. テストコードを実装する
KIFTestCaseクラスが提供するインスタンス(tester)を使って
画面操作を行うコードを実装していきます。
画面項目の同定にはAccesiblility Labelの情報を使います。
そのため、事前に操作対象の部品にラベルを設定しておいてください。
ボタンであれば、ラベルなしでもボタンの表示名ベースで特定可能です。
(1) 画面項目をタップする
画面項目をタップする場合のテストコードの書き方です。
- (void)testExample { [tester tapViewWithAccessibilityLabel:@"Button"]; }
引数で指定したAccessibility Labelを持つ項目をタップします。
(2) 画面項目に値を入力する
画面項目に値を入力する場合のテストコードの書き方です。
- (void)testExample { [tester enterText:@"hogehoge" intoViewWithAccessibilityLabel:@"input"]; }
第1引数に入力したいテキストを入力します。
第2引数にテキストを入力する項目のAccesibility Labelを指定します。
5. テストを実行する
XCTest実行時と同じようにテストターゲットを実行します。
実装した処理に従ってシミュレータの操作が自動化されていれば成功です。
実際に使ってみた感想です。
良いところ
- XCTestと同じ感覚でUIテストの自動化コードを書ける
- 導入が簡単である
- 実機でもシミュレータでも実行できる
悪いところ
- テストケース実行後に画面が初期状態に戻らない
予めリセット処理をアプリケーションごとに実装しておけば
良いだけなのですがちょっと面倒臭いような気がします。
Swiftで実現する非推奨(deprecated)APIの置換処理
来月のWWDC2015にて、次期iOS(iOS 9?)が発表されそうです。
新しいAPIが使えるようになり、楽しみが増える一方で使えなくなるAPIもあり
開発しているアプリの新バージョン対応に右往左往するシーズンです。
一般的に非推奨APIは以下の3種類に分類出来ます。
Objective-Cでの対応方法は確立しているので
今回は上記の3種類に対して、Swiftでどう対応していくのか整理します。
クラスが非推奨になった
Objective-C時代は、以下のような対応でした。
if ([DeprecatedClass class]) { // DeprecatedClassが存在する場合 } else { // DeprecatedClssが存在しない場合 // 新バージョンでの代替APIを使う
Swiftでは以下のような処理を実装します。
if NSClassFromString("DeprecatedClass") { // DeprecatedClassが存在する場合 } else { // DeprecatedClssが存在しない場合 // 新バージョンでの代替APIを使う }
メソッドが非推奨になった
Objective-C時代は、以下のような対応でした。
if ([obj respondsToSelecor:@selector("deprecatedMethod")]) { // deprecatedMethodが存在する場合 } else { // deprecatedMethodが存在しない場合 // 新バージョンでの代替APIを使う
Swiftでも同じですね!
if obj.respondsToSelector("deprecatedMethod") { // deprecatedMethodが存在する場合 } else { // deprecatedMethodが存在しない場合 // 新バージョンでの代替APIを使う }
プロトコルが非推奨になった
Objective-C時代は、以下のような対応でした。
if ([obj conformsToProtocol:@protocol("DeprecatedProtocol")]) { // DeprecatedProtocolが存在する場合 } else { // DeprecatedProtocolが存在しない場合 // 新バージョンでの代替APIを使う
Swiftでも同じですね!
if obj.conformsToProtocol("DeprecatedProtocol") { // DeprecatedProtocolが存在する場合 } else { // DeprecatedProtocolが存在しない場合 // 新バージョンでの代替APIを使う }
楽なところから始めるObjective-CからSwiftへの移行
少し前から進めていたSwiftのお勉強の成果として
Objective-CからSwiftに移行するときのポイントをまとめてみます。
自分自身、そこまで習熟者ではないので
コンセプトとしては”楽なところ”から移行というテーマで
書いてみたいと思います。
Optionalでnilチェックを駆逐する
Swiftを始めて一番便利だと感じたのがOptional型です。
というか、非Optionalな変数がnilを拒絶する点が素晴らしい。
// 非Optional型の変数を定義します var moji = "もじもじ君" moji = nil // コンパイルエラー発生
こんな感じでXcodeがエラー扱いにしてくれます。
この特性がどこで発揮されるか!?といえば
その1つがメソッドの引数に関するnilチェック処理だと思います。
Objective-Cだと↓みたいなコードよく書きませんか?
- (NSString *)myMethod:(NSString *)arg { // nilチェック if (arg != nil) { return nil; } }
ただただ面倒くさい!
とはいえ、チェックがないと怖すぎる……。
Swiftの変数はデフォルトでnilを許容しないので
非Optionalで引数定義するだけでチェック処理が駆逐出来ます。
func myMethod(#arg:String) -> String { // さらばnilチェック }
Genericsで型チェックを厳密に!
Genricsも意図しない型が代入されるリスクをなくせる点でメリットが大きいです。
クラス定義や関数定義でGenericsを活用する......という旨味もあるのですが
”楽なところ”からがコンセプトなので省略です。
じゃあ,どこで使うかといえば,配列とDictonaryです。
Objective-Cでは,配列やDictonaryに投入出来る変数型はid型なので
『この配列にはNSStringしか入れたくない!』ということを実現しようとすると
下のようなコードを書いて抑止するしかないです。
// 引数がNSStringクラスだったら if ([args isKindOfClass:[NSString class]]) { [list addObject: args]; }
面倒です。とても面倒です。
Swiftでは定義するときに投入する型を指定します。
let list:Array<String> = Array<String>()
こんな感じで配列の中身はStringだよ!と指定してあげます。
上を書くのが面倒であれば下のような書き方でもOKです。
let list = [String]()
ちなみにディクショナリだと下のような感じです。
let map:Dictionary<String:MyClass> = Dictonary<String:MyClass>()
ひとまず、OptionalとGenericsの2点から始めると良いと思います。
今回のエントリには書いてませんが,.hファイルと.mファイルが
.swiftファイルにまとまるので,それだけでも楽になる気がします。
Tomcat 8.0系でのデータソース定義
Tomcat上で動くWebアプリでDB接続する必要があり
データソースの定義ってどうやるんだ?!と思って調べてみました。
ちなみにEclipseでDynamic Web Projectで開発したケース......です。
検証環境は以下の通り。
1. context.xmlを作成する
META-INFフォルダの直下にcontext.xmlという名前でファイルを作成します。
このファイルをTomcatが読み込んでコネクションプーリングを行います。
従ってデータベースの場所やら接続情報、JNDIのアクセス名などの情報を書き込みます。
<Context> <Resource name="jdbc/myapp" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/webapp" username="" password="" /> </Context>
今回は設定していませんが
「maxActive(最大接続数)」「maxIdle(最低接続維持数)」「maxWait(タイムアウト時間)」
などの設定も付与可能です。
詳しくはTomcat公式をご覧くださいー。
2. web.xmlに参照情報を付与する
Tomcatが確保したコネクションをWebアプリから取得するため
web.xmlに参照情報を付与します。
<resource-ref> <res-ref-name>jdbc/myapp</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
注意点としては、各要素の設定情報は
「context.xml」でのネーミングと一致させることです。
3. 設定が上手くいってるか確かめてみる
実際にコードを書いて試してみます。
単純にDataSourceインスタンスが取れるかどうかで判定する例。
InitialContext context = new InitialContext(); DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/myapp"); if (ds != null) { System.out.println("データ取得できたー!"); }
さて、上手く取れてるでしょうか?
Spring BootでJSONデータを受け付けるWebAPIを作る
前回の記事の続きモノです。
↑の記事で、POSTでリクエストパラメータを受け付けるWebAPIを
Spring Bootでサクッと作る方法をまとめてみましたが、今度は
リクエストをJSON形式で受け取る方法を整理してみます。
(前回の記事から続きで実装する体で進めます......!)
環境は以下の通りです。
1. Controllerクラスのメソッドをいじる(1)
前回、↓のようなControllerクラスを作りました。
@Controller @EnableAutoConfiguration public class SampleController { @RequestMapping("/service") @ResponseBody public List<String> top() { List<String> list = new ArrayList<String>(); list.add("太宰"); list.add("夏目"); return list; }
このControllerクラスのtopメソッドを改造していきます。
いじるポイントは2つ。
- @RequestMappingにconsumesを追加する
- 引数でJSONデータをパース後にバインドするクラスを設定する
既存の@RequestMappingアノテーションに設定を追加します。
前回『メソッドに紐付いたURL』の設定を@RequestMappingで行うと書きましたが
実際のところはURLの紐付け設定だけでなく、受け取るデータ種別の設定も可能です。
今回は受け取るデータ種別としてJSONデータを受け取れるように設定します。
具体的には以下のような感じになります。
@Controller @EnableAutoConfiguration public class SampleController { @RequestMapping(value="/service",consumes=MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<String> top() { List<String> list = new ArrayList<String>(); list.add("太宰"); list.add("夏目"); return list; }
前回書いていた『@RequestMapping("/service")』という部分は
省略せずに書くと『@RequestMapping(value="/service")』となります。
value以外にも設定は在って、今回はconsumesで受け取るデータ種別を設定しています。
(※前回、今回と省略してますが、戻るデータ種別を定義するproducesという項目も在ります)
2. JSONのデータを保持するクラスを作る
続いてリクエストで受け取ったJSONがバインドされるクラスを作ります。
Spring Boot(=Spring MVC)では、JSON形式のデータを受け付けた後、
対応するクラスにデータをバインドして渡してくれます。
今回は適当に↓のようなクラスを作りました。
public class SampleBean implements Serializable { private static final long serialVersionUID = 1L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
3. Controllerクラスのメソッドをいじる(2)
またControllerクラスに戻ってきます。
ステップ2で作成したクラスとJSONをマッピングするように再度
Controllerクラスを弄っていきます。
@Controller @EnableAutoConfiguration public class SampleController { @RequestMapping(value="/service",consumes=MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<String> top(@RequestBody SampleBean bean) { List<String> list = new ArrayList<String>(); list.add("太宰"); list.add("夏目"); return list; }
topメソッドの引数に@RequestBodyアノテーション付きの引数を追加しました。
引数の型は先ほど作ったSampleBeanクラスです。
この設定を付与することでbeanインスタンスにJSONデータがマッピングされます。
bean.getName()の結果をコンソール出力する処理を追記して試してみましょう。
4. 実際にリクエストしてみる
動いているか検証してみます。
以下のようなJavaScriptで検証できると思います。
// 各フィールドから値を取得してJSONデータを作成 var data = { "name": "hoge", }; // 通信実行 $.ajax({ type:"post", url:"http://localhost:8080/services", data:JSON.stringify(data), contentType: 'application/json', dataType: "json", success: function(json_data) { // 成功時の処理 } });
Spring Bootで作る簡単WebAPI
サーバ通信を行うスマホアプリを開発する際にデータをやり取りするWebアプリ
(※httpでリクエストするとJSONで結果を返す類のWebAPI)が必要だったので
何とか簡単に作れないかと模索ところ、Spring Bootがなかなか便利だった。
最大の便利ポイントはAPサーバ(Tomcat)を組み込んだ.jarファイルが作れるところ。
関係者に展開するときに”.jarファイルをjava -jarで実行すりゃいいよ!”で済むのが良い。
(関係者の環境で”動かない!”とか言われて揉めるのを避ける効果がある!)
手順をさらっとまとめてみます。
ちなみに検証環境は以下の通り。
1. Mavenプロジェクトを作る
Eclipseのプロジェクト作成ウィザードからMavenプロジェクトを選択する。
最近、Mavenを使い始めた初心者だけど、ライブラリ同士のバージョン関係をいっさい
考えなくても必要なライブラリ一式がゴッソリ入手出来る点が素晴らしいです。
(ただ、相手側もMaven知ってる前提じゃないとソース展開が面倒)
ちなみにMavenプロジェクトのarchetypeは指定なしで良いです。
「Create a Simple Project」をしれっと選択して次に進む。
artifacatIdやgroupId、バージョンは任意の設定でOK。
2. pom.xmlファイルを編集する
必要なライブラリの依存関係を定義するpom.xmlファイルを編集する。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.0.2.RELEASE</version> </parent>
まず、parent要素に上の内容を追記。
Spring Bootを使う上での必要ライブラリ諸々が定義された
親のpom.xmlファイルを入手しますよ......という意味だと思う。
後述のdependencyも親のpom.xmlの利用が前提になっている。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
続いて本命のSpring Boot本体を取得する依存性設定を追加する。
この状態で保存して、プロジェクト右クリック Maven --> Update Projectを実行。
これでインターネットから必要なライブラリがローカルにダウンロードされる。
「Maven Dependencies」にjarファイルが入ってれば成功。
3. Controllerクラスを作る
クライアントからのリクエストを受け付けるControllerクラスを作る。
@Controller @EnableAutoConfiguration public class SampleController { // これからメソッド書いていくよ!
ポイントはクラスに付けてるアノテーション「@EnableAutoConfiguration」。
このアノテーションが付くことで「Spring Bootよ!面倒な設定は任せた!」という
殿様的な伝令が発せられ、疲弊した開発者は面倒な設定から解放される。
4. リクエストを受け付けるメソッドを作る
Controllerクラスにリクエストを受け付けるメソッドを作っていく。
メソッドの戻り値はListでもHashMapでも独自クラスでも何でも良い。
指定したクラスの中身がJSONになってクライアントに返却されるので
開発対象のサービスの仕様次第で柔軟に変えるとハッピー。
@Controller @EnableAutoConfiguration public class SampleController { @RequestMapping("/service") @ResponseBody public List<String> top() { List<String> list = new ArrayList<String>(); list.add("太宰"); list.add("夏目"); return list; }
ポイントはメソッドに付けてるアノテーション。
@RequestMappingでは、メソッドに紐付いたURLを設定する。
上のtopメソッドの場合「http://localhost:8080/service」というURLが対応する。
@ResponseBodyは端的にいうと「JSONを返却するよ!」という宣言である。
これらのお膳立てが出来た状態で何らかのクラスを返却してやると
そのクラスの中身がJSONに変換されてクライアントに送られる動きになる。
クライアントからのリクエストパラメータを元に
何か処理を制御したい場合、@RequestParamアノテーションを駆使して、
valueでパラメータ名、その後のメソッド引数に対応する型と変数名を
指定してやることでメソッド内で簡単にパラメータを扱うことが出来る。
@Controller @EnableAutoConfiguration public class SampleController { @RequestMapping("/service") @ResponseBody public List<String> top(@RequestParam(value="name", required=false) String name) { List<String> list = new ArrayList<String>(); if (name == "hoge") { list.add("芥川"); } else { list.add("江戸川乱歩"); } list.add("太宰"); list.add("夏目"); return list; }
4. mainメソッドを作る
Spring Bootを使ったアプリは、コンソールのJavaプログラムのように
mainメソッドから実行される仕様であるため、mainメソッドを作っていく。
public static void main(String args[]) { SpringApplication.run(SampleController.class, args); }
mainメソッドでは、SpringApplication.runメソッドを実行する。
引数になるのは最初に作ったControllerクラスである。
5. 実行する
「mvn spring-boot:run」で実行する。
コンソールにSpring Bootのロゴやらログが表示される。
最後に「Started SampleController in 2.326 seconds」とか出たら成功。
オマケ. 単体で実行可能なjarファイルを作る
pom.xmlに追加設定を加えれば単体実行可能な.jarファイルも作成出来る。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>jp.co.cross_xross.controller.SampleController</mainClass> </configuration> </plugin> </plugins> </build>
mainClassにmainメソッドを実装したクラス名を指定するところがミソ。
この状態で「mvn package」を実行すると
targetディレクトリに単体実行可能な.jarファイルが生成される。
実行する場合は「java -jar xxxx.jar」で良いので非常に簡単。
Glassfish 4.1 + MySQL でデータソース定義
ソースコードからデータベースに対して直接コネクションを張ることも出来るが
どうもアプリ→データベースで接続&切断を行うのはコストが高いらしい……。
なので、お作法としてはAPサーバにデータベースとのコネクションを持たせておいて
アプリはAPサーバからコネクションを拝借する方がベターなのだという!
(アプリ全体で接続数は◯◯個とかの制限をかけてコネクションの使い回しが可能)
今回は以下のレシピでAPサーバにデータソース定義をしてみようと思う。
1. MySQL用のJDBCコネクタを入手する。
GlassfishがMySQLにアクセスためにはJDBCコネクタが必要である。
Oracleの以下のサイトから入手する。
zipファイルに同梱されているmysql-connector-java-5.0.8-bin.jarを
glassfish4/glassfish/domains/domain1/lib/extにコピーしておく。
2. Glassfishの管理コンソールを表示する
Glassfishが起動した状態でhttp://localhost:4848を開くと管理コンソールが表示される。
この管理コンソールは今回解説するデータソースの定義以外にも
サーバのログの確認やクラスタ構成の設定等もGUIから出来るようだ。