22
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

iOSのユニットテスト(LogicTests)で和暦設定時のテストを行う

Last updated at Posted at 2014-04-09

自分の作ったアプリで和暦を使っていなくても、ユーザのカレンダー設定が和暦だったりすると
日付表示がおかしくなることがあります。
カレンダー設定を和暦に変更して実機での動作確認をすればいいのですが、いつもそれを気にして確認などはしないですし忘れがちです。

それに、設定によって挙動が変わってしまうのであれば、日付に関するテストコードを書いても意味がなくなってしまいます。

事前調査

iPhoneの設定でカレンダーを和暦に設定した上で、適当なアプリにNSLog(@"%@", [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLocale"]);
で記述して起動します。
そうすると、現在選択されているロケールが取得出来ます。

OCMockを使って和暦設定時のロケールを返すMockを作成

OCMockを使い、先ほど取得したロケールを返すようにします。
いろいろ使い回せるように、下のようにSenTestCaseを継承したベースのテストケースを作成し、
和暦設定時のロケールのMockを作成するメソッドを追加します。

XXXTestCase.h
#import <SenTestingKit/SenTestingKit.h>

#import <OCMock/OCMock.h>

@interface XXXTestCase : SenTestCase

#pragma mark - NSLocale(ja_JP@calendar=japanese)

/*!
    @method createJPLocaleWithJapaneseCalendarMock
    @abstract iPhoneのカレンダー設定を和暦に書き換えたMockを作成
    @discussion
        iPhoneの[設定]-[一般]-[言語環境]-[カレンダー] = 和暦
 */
- (id)createJPLocaleWithJapaneseCalendarMock;

@end
XXXTestCase.m
@interface XXXTestCase ()

@property (nonatomic) NSMutableArray *mocksToVerify;

@end

@implementation XXXTestCase

- (void)tearDown
{
    for (id mock in self.mocksToVerify) {
        [mock verify];
    }
    self.mocksToVerify = nil;

    // [...]

    [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:XXXTestDomainName];

    [super tearDown];
}

#pragma mark - NSLocale(ja_JP@calendar=japanese)

- (id)createJPLocaleWithJapaneseCalendarMock
{
    NSLocale *locale = [NSLocale localeWithLocaleIdentifier:[NSString stringWithFormat:@"ja_JP@calendar=%@",
                                                             NSJapaneseCalendar]];
    id localeMock = [self autoVerifiedMockForClass:[NSLocale class]];
    [[[[localeMock stub] classMethod] andReturn:locale] systemLocale];
    [[[[localeMock stub] classMethod] andReturn:locale] currentLocale];
    [[[[localeMock stub] classMethod] andReturn:locale] autoupdatingCurrentLocale];

    return localeMock;
}

#pragma mark - Setup and verify mock: http://www.objc.io/issue-1/testing-view-controllers.html

- (void)verifyDuringTearDown:(id)mock
{
    if (!self.mocksToVerify) {
        self.mocksToVerify = @[].mutableCopy;
    }
    [self.mocksToVerify addObject:mock];
}

- (id)autoVerifiedMockForClass:(Class)aClass
{
    id mock = [OCMockObject mockForClass:aClass];
    [self verifyDuringTearDown:mock];
    return mock;
}

- (id)autoVerifiedPartialMockForObject:(id)object
{
    id mock = [OCMockObject partialMockForObject:object];
    [self verifyDuringTearDown:mock];
    return mock;
}

- (id)autoVerifiedNickMockForClass:(Class)aClass
{
    id mock = [OCMockObject niceMockForClass:aClass];
    [self verifyDuringTearDown:mock];
    return mock;
}

@end

和暦設定時のロケールを返すMockを利用したテストケース

yyyyMMの日付文字列を受け取って、それを年と月に分割した後、
'April, 2014'のようなMMMM, yyy書式の文字列を変換する処理があり、
カレンダーが和暦設定だと、なぜか'April, 4002'となってしまうバグがあったとします。

それを確かめるテストコードは下記のようになります。

#import "XXXTestCase.h"

#import <OCMock.h>

@interface XXXDateConvertTests : XXXTestCase

@property (nonatomic) NSString *dateString;

@end

@implementation XXXDateConvertTests

- (void)setUp
{
    [super setUp];
    self.dateString = @"20140408";
}

- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

// MARK: 日付変換処理だけ抜き出した
- (NSString *)postedAtWithDateFormatter:(NSDateFormatter *)dateFormatter
{
    NSInteger year = [self.dateString substringWithRange:NSMakeRange(0, 4)].integerValue;
    NSInteger month = [self.dateString substringWithRange:NSMakeRange(4, 2)].integerValue;

    [dateFormatter setDateFormat:@"yyyy-MM-dd"];

    // MARK: 和暦設定の場合、年を正常にパースできないのでカレンダーをグレゴリオ暦に変更
//    dateFormatter.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

    NSDate *date = [dateFormatter dateFromString:[NSString stringWithFormat:@"%i-%i-1", year, month]];

    [dateFormatter setLocale:[[NSLocale alloc]initWithLocaleIdentifier:@"en_US"]];
    [dateFormatter setDateFormat:@"MMMM, yyyy"];

    return [dateFormatter stringFromDate:date];
}

- (void)testPostedAt
{
    STAssertFalse([[[NSCalendar currentCalendar] calendarIdentifier] isEqualToString:NSJapaneseCalendar], @"");

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    NSString *resultString = [self postedAtWithDateFormatter:[[NSDateFormatter alloc] init]];

    STAssertEqualObjects(resultString, @"April, 2014", @"");

    [self createJPLocaleWithJapaneseCalendarMock];  // 和暦設定に変更

    NSLog(@"%@", dateFormatter.locale.localeIdentifier);

    dateFormatter = [[NSDateFormatter alloc] init];
    resultString = [self postedAtWithDateFormatter:dateFormatter];
    STAssertFalse([resultString isEqualToString:@"April, 2014"], @"");  // April, 4002になってしまう
}

@end

参考

22
21
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
22
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?