読者です 読者をやめる 読者になる 読者になる

プログラマでありたい

おっさんになっても、プログラマでありつづけたい

Objective-Cで、プライベートメソッド・プロパティにアクセスし、ユニットテストを実行する方法

iOS

 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