LoginSignup
3
2

More than 5 years have passed since last update.

【Objective-C】XCTestで非公開クラスの非公開メソッドをリフレクションを使ってテストする

Last updated at Posted at 2017-08-09

この記事では、XCTestで非公開クラスの非公開メソッドをユニットテストする方法を紹介します。

クラス自体が非公開の場合、公開クラスをテストする時のように、テスト対象クラスのヘッダファイルをimportするやり方では記述できないので、代わりにリフレクションを使うことでメソッドのテストができます。

※そもそも設計が悪いのではないかとか、publicで提供するべきではないかといった議論はまた別の機会に。

テスト対象クラスの例

PublicClass.h/.mというファイルがあり、PublicClass.mにPrivateClassという今回のテスト対象クラスが実装されているものとします。

PublicClass.h

#import <Foundation/Foundation.h>

@interface PublicClass : NSObject

// Something

@end

PublicClass.m

#import "PublicClass.h"

#pragma mark - PrivateClass

@interface PrivateClass : NSObject

@end

@implementation PrivateClass

#pragma mark - private method

- (NSInteger)privateMethodWithValue1:(NSInteger)value1 value2:(NSInteger)value2 {
    return value1 + value2;
}

#pragma mark - private class method

+ (NSInteger)privateClassMethodWithValue1:(NSInteger)value1 value2:(NSInteger)value2 {
    return value1 * value2;
}

@end

#pragma mark - PublicClass

@implementation PublicClass

// Something

@end

PrivateClassには、privateMethodWithValue1:value2:というprivateなインスタンスメソッドとprivateClassMethodWithValue1:value2:というprivateなクラスメソッドが実装されています。メソッドの内容は単純なので説明するまでもないでしょう。
この2つのメソッドが今回のテスト対象です。

XCTestでテストする

テストプログラムは以下の通りです。メソッドが返す計算結果が期待する値になっているかをテストしています。
リフレクションのやり方ですが、言葉で説明するより実際にソースコードを見てもらった方が早いでしょう。


#import <XCTest/XCTest.h>

typedef NSInteger (*PrivateInstanceMethod)(id, SEL, NSInteger, NSInteger);
typedef NSInteger (*PrivateClassMethod)(id, SEL, NSInteger, NSInteger);

@interface XCTestSampleTests : XCTestCase

@end

@implementation XCTestSampleTests

- (void)setUp {
    [super setUp];

}

- (void)tearDown {

    [super tearDown];
}

// インスタンスメソッドのテスト
- (void)testPrivateMethod {
    Class cls = NSClassFromString(@"PrivateClass");
    SEL sel = NSSelectorFromString(@"privateMethodWithValue1:value2:");
    IMP method = [cls instanceMethodForSelector:sel];
    PrivateInstanceMethod func = (void *) method;
    id obj = [cls new];

    NSInteger res = func(obj, sel, 2, 3);

    XCTAssertEqual(res, 5);
}

// クラスメソッドのテスト
- (void)testPrivateClassMethod {
    Class cls = NSClassFromString(@"PrivateClass");
    SEL sel = NSSelectorFromString(@"privateClassMethodWithValue1:value2:");
    IMP method = [cls methodForSelector:sel];
    PrivateClassMethod func = (void *) method;

    NSInteger res = func(cls, sel, 2, 3);

    XCTAssertEqual(res, 6);
}

@end

ポイント解説

NSClassFromString関数に非公開クラスのクラス名を指定してClassを作ります。
NSSelectorFromString関数には非公開のメソッド名を指定してセレクタを作ります。(引数がある時は:が必要ですので忘れずに)

次にセレクタからIMPを作りますが、インスタンスメソッドの場合はinstanceMethodForSelectorメソッドを使い、クラスメソッドの場合はmethodForSelectorメソッドを使います。

typedefでテストするメソッドのカタチを定義していますが、第1引数のidと第2引数のSELは固定で、第3引数以降は実際のテストするメソッドの引数の型を列挙していきます。(引数の無いメソッドの場合は第2引数のSELまで)

funcには、インスタンスメソッドの場合は[cls new]でインスタンスを生成して第1引数に、クラスメソッドの場合はclsをそのまま第1引数に渡します。第2引数にはセレクタを指定します。第3引数以降は実際のテストするメソッドに渡す値を指定します。


以上で非公開クラスの非公開メソッドをテストすることができました。
非公開なモノにアクセスするのはけっこう面倒なので、まずは実装の見直しや本当にテストする必要があるのか、といったことから検討すると良いかもしれません。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2