iPhone/iOSの開発をしていく上で、Objective-CでPrivate Methodにアクセスしたいなぁと思うことがあると思います。パブリックメソッドには、外部からのインターフェースのみを実装し、ロジック部分はプライベートメソッドに隠蔽するといったケースは多々あると思います。その場合、ユニットテストはプライベートメソッドに対して行う必要があります。
そもそもObjective-Cでのプライベートメソッドは、どう実装するのでしょうか?実はObjective-Cでは、厳密な意味でのプライベートメソッドは存在しないのです。一般的にはカテゴリーという機能を使って、実装ファイル(.mファイル)に宣言して代用します。次のサンプルのような感じです。
#import "ExampleClass.h" @interface ExampleClass(private) - (void) privateMethod; - (void) privateMethod:(NSString *)aString; - (void) privateMethod:(NSString *)aString second:(NSString *)bString; @end @implementation ExampleClass @synthesize exampleProperty = _exampleProperty; - (void)publicMethod { _exampleProperty = @"a"; } - (void)privateMethod { _exampleProperty = @"b"; }
3行目の(private)という部分が、カテゴリーです。慣習的にprivateにしていますが、hogehogeでもfugefugeでも何でも構わないのです。これをObjective-Cのおけるプライベートメソッドと呼んでいます。では、これをテストするにはどうしたらよいのでしょうか?
皆さん何となく知っていると思いますが、ObjectiveCでは実行時にダイナミックに結合されるので、ヘッダーで定義されていないメソッドに対しても呼び出しが出来ます。ただ、warningが出るだけです。下の図のような感じになりますね。
しかし、Warningを放置するのは、精神衛生上も保守性その他を考えても良い習慣ではないです。何とかしたいところです。主に3つの方法があるので、簡単にまとめてみます。
1.performSelectorを使う方法
2.Key-Value Codingを使って、プロパティにアクセスする方法
3.クラスを拡張して、メソッドとプロパティを定義する方法
performSelectorを使う方法
performSelectorというメソッドを使い、セレクタ経由で直接メソッドを呼び出しパラメータを渡す方法があります。言葉にしても伝わりにくいので、サンプルを見てください。
プライベートメソッドの定義
- (void) privateMethod; - (void) privateMethod:(NSString *)aString; - (void) privateMethod:(NSString *)aString second:(NSString *)bString;
performSelectorによるプリベートメソッドへのアクセス例です。
引数を渡す場合は、withObjectで渡します。引数が2つある場合はwithObjectを並べます
[exampleClass performSelector:@selector(privateMethod)]; //Test For privateMethod:(NSString *) aString [exampleClass performSelector:@selector(privateMethod:) withObject:@"c"]; //Test For privateMethod:(NSString *)aString second:(NSString *)bString [exampleClass performSelector:@selector(privateMethod:second:) withObject:@"c" withObject:@"d"];
この方法の問題点は、引数が2つまでしか渡せないことです。引数が3つ以上あるメソッドには適応できません。また、呼び方も本来使われる記述とは違う上に、可読性も低いので余りお勧めできません。
Key-Value Codingを使って、プロパティにアクセスする方法
次の方法が、Key-Value Coding。日本名では、キー値コーディングです。この方法は、プライベートメソッドではなくプライベートプロパティに直接アクセスする方法です。値の検証に利用できますが、プライベートメソッドを直接呼び出すことは出来ないので、これ単体ではユニットテストには使えません。
クラスを拡張して、メソッドとプロパティを定義する方法
最後にクラスを拡張して、プライベートメソッドとプロパティをパブリックに定義する方法です。簡単に説明すると、ExampleClass.hとExampleClass.mに対して、ExampleClassExtension.hを定義します。ExampleClassExtension.hは、 ExampleClassのインターフェースでExampleClass.mに定義されているプライベートクラスのプロパティ・メソッドを持ちます。
ExampleClass.h
#import <Foundation/Foundation.h>
@interface ExampleClass : NSObject {
NSString *_exampleProperty;
}
- (void)publicMethod;
@property (nonatomic, retain) NSString * exampleProperty;
@end
ExampleClassExtension.h
@interface ExampleClass () - (void) privateMethod; - (void) privateMethod:(NSString *)aString; - (void) privateMethod:(NSString *)aString second:(NSString *)bString; @end
プライベートメソッドで宣言しているものを、Extensionヘッダーで再度パブリックで宣言しているところがミソになります。拡張クラスはテストをターゲットに作成しておけば、意図しないアクセスを防ぐことも可能になります。プライベートメソッドの宣言を2重にする必要があるので、多少問題があるものの実用的な方法ではないかと思います。
これ以外でも方法があれば、ぜひ教えてください。
サンプルソースは、GitHubにあげております。
参照:
Accessing private methods and properties in ObjC unit tests
Objective-C Key-Value Coding | YOHEI's BLOG
performSelectorとパラメタ数 - Kazzzの日記
Communicating With Objects: Key-Value Coding