1

のインスタンスをインスタンス化するメソッドをテストしようとしていますMFMailComposeViewController。テストされているメソッドは、MFMailComposeViewControllerを含むいくつかのメソッドを呼び出しますsetSubject:

setSubject に特定の NSString (この場合は @"Test Message") が送信されることをテストしたいと思います。
モック スタブで予想される文字列に何を指定しても、失敗はありません。

単体テスト クラス:

#import <OCMock/OCMock.h>

- (void)testEmail {
    TestClass *testInstance = [[TestClass alloc] init];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock stub] setSubject:@"Test Message"];

    [testInstance testMethod];
}

TestClass で:

- (void)testMethod {
    MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
    [mailComposeVC setSubject:@"Bad Message"];
}

Test Suite 'Email_Tests' started at 2011-09-17 18:12:21 +0000
Test Case '-[Email_Tests testEmail]' started.
Test Case '-[Email_Tests testEmail]' passed (0.041 seconds).

テストは失敗するはずでした。

これを iOS シミュレーターでテストしていますが、デバイスでも同じ結果が得られます。

私は何を間違っていますか?これを達成する方法はありますか?

4

2 に答える 2

5

モックを作成しますが、それをテスト対象のクラスに渡したり、モックにそれ自体を検証するように依頼したりしないでください。「MFMailComposeViewController を使用する代わりに、私が提供する別のものを使用してください」と言うには、なんらかの形式の依存性注入が必要です。

これを行う 1 つの方法を次に示します。テスト対象のクラスでは、MFMailComposeViewController を直接割り当てるのではなく、次のようにファクトリ メソッドを介して取得します。

@interface TestClass : NSObject

- (void)testMethod;

// Factory methods
+ (id)mailComposeViewController;

@end

これが実装です。リークしていたので、ファクトリ メソッドが自動解放されたオブジェクトを返すことに注意してください。

- (void)testMethod {
    MFMailComposeViewController *mailComposeVC =
                                    [[self class] mailComposeViewController];
    [mailComposeVC setSubject:@"Bad Message"];
}

+ (id)mailComposeViewController {
    return [[[MFMailComposeViewController alloc] init] autorelease];
}

テスト側では、ファクトリ メソッドをオーバーライドするテスト サブクラスを作成して、必要なものをすべて提供します。

@interface TestingTestClass : TestClass
@property(nonatomic, assign) id mockMailComposeViewController;
@end

@implementation TestingTestClass
@synthesize mockMailComposeViewController;

+ (id)mailComposeViewController {
    return mockMailComposeViewController;
}

@end

これで、テストの準備が整いました。私はいくつかの異なることを行います:

  • 実際のクラスではなく、テスト用のサブクラスを割り当てます (漏れないでください!)
  • スタブだけでなく、期待を込めてモックを設定する
  • モックをテスト サブクラスに挿入する
  • 最後にモックを検証する

テストは次のとおりです。

- (void) testEmail {
    TestClass *testInstance = [[[TestClass alloc] init] autorelease];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock expect] setSubject:@"Test Message"];
    [testInstance setMockMailComposeViewController:mock];

    [testInstance testMethod];

    [mock verify];
}

完全を期すために、1 つの最終テストが必要です。これは、実際のクラスのファクトリ メソッドが期待どおりの結果を返すことを保証するためのものです。

- (void)testMailComposerViewControllerShouldBeCorrectType {
    STAssertTrue([[TestClass mailComposeViewController]
                 isKindOfClass:[MFMailComposeViewController class]], nil);
}
于 2011-09-18T01:09:05.060 に答える
2

Jon Reid の方法は合理的なアプローチですがmailComposeViewController、クラス メソッドを作成すると複雑になるようです。また、テスト コードでそれをサブクラス化すると、テスト時に常にモック バージョンを取得することになりますが、これは望んでいない可能性があります。インスタンスメソッドにします。次に、部分モックを使用して、テスト時にオーバーライドできます。

-(void) testEmail {
    TestClass *testInstance = [[[TestClass alloc] init] autorelease];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock expect] setSubject:@"Test Message"];
    id mockInstance = [OCMockObject partialMockForObject:testInstance];
    [[[mockInstance stub] andReturn:mock] mailComposeViewController];

    [testInstance testMethod];

    [mock verify];
}

クラスメソッドとして保持する場合は、静的グローバルにして、オーバーライドする方法を公開することを検討してください。

static MFMailComposeViewController *mailComposeViewController = nil;

-(id)mailComposeViewController {
    if (!mailComposeViewController) {
        mailComposeViewController = [[MFMailComposeViewController alloc] init];
    }
    return mailComposeViewController;
}

-(void)setMailComposeViewController:(MFMailComposeViewController *)controller {
    mailComposeViewController = controller;
}

次に、テストは Jon の例のようになります。

-(void)testEmail {
    TestClass *testInstance = [[[TestClass alloc] init] autorelease];

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
    [[mock expect] setSubject:@"Test Message"];
    [testInstance setMailComposeViewController:mock];

    [testInstance testMethod];

    [mock verify];

    // clean up
    [testInstance setMailComposeViewController:nil];
}
于 2011-09-19T22:09:20.530 に答える