使用NSDateFormatter避免脆弱的单元测试

时间:2013-11-27 09:59:45

标签: cocoa-touch unit-testing ocunit xctest kiwi

测试NSDateFormatter方法的最佳做法是什么?例如,假设我有一个方法:

- (NSString *)formatStringFromDate:(NSDate *)date {
    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    [f setTimeStyle:NSDateFormatterShortStyle];
    [f setDateStyle:NSDateFormatterNoStyle];

    return [f stringFromDate:date];
}

我可以通过两种方式考虑使用Kiwi测试此方法:

1)在单元测试中创建相同的格式化程序:

it(@"should format a date", ^{
    NSDate *date = [NSDate date];
    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    [f setTimeStyle:NSDateFormatterShortStyle];
    [f setDateStyle:NSDateFormatterNoStyle];

    [[[testObject formatStringFromDate:date] should] equal:[f stringFromDate:date]];
});

2)明确写出预期的输出:

it(@"should format a date", ^{
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:1385546122];
    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    [f setTimeStyle:NSDateFormatterShortStyle];
    [f setDateStyle:NSDateFormatterNoStyle];

    [[[testObject formatStringFromDate:date] should] equal:@"9:55 am"];
});

现在,对我而言,#1似乎有点多余。我知道测试将通过,因为我基本上在我的单元测试中复制了这个方法。

方法#2是非首发,因为它非常脆弱。它完全依赖于测试设备当前的区域设置是您所期望的。

所以我的问题是:是否有更合适的方法来测试这种方法,或者我应该继续使用测试方法#1。

1 个答案:

答案 0 :(得分:1)

正如您在评论中所说,您要测试的是,当日期存在时,单元格的detailTextLabel文本将设置为具有预期格式的日期。

这可以通过几种不同的方式进行测试,我在这里公开了我想要的方法。

首先,每次我们想要格式化日期时创建日期格式化程序都不高效,这使得测试更加困难。

所以我建议在表视图控制器中创建一个date formatter属性:

// .h
@property (strong, nonatomic) NSDateFormatter *dateFormatter;

// .m
- (NSDateFormatter *) dateFormatter
{
    if (_dateFormatter == nil)
    {
        _dateFormatter = [[NSDateFormatter alloc] init];
        [_dateFormatter setTimeStyle:NSDateFormatterShortStyle];
        [_dateFormatter setDateStyle:NSDateFormatterNoStyle];

    }
    return _dateFormatter;
}

对于日期格式化程序,我将创建一个验证timeStyle和dateStyle的简单测试。我不熟悉Kiwi所以我使用OCUnit断言:

TableViewController *sut;// Instantiate the table view controller
STAssertTrue(sut.dateFormatter.timeStyle == NSDateFormatterShortStyle, nil);
STAssertTrue(sut.dateFormatter.dateStyle == setDateStyle:NSDateFormatterNoStyle, nil);

完成此操作后,我们的想法是创建一个测试,验证tableView:cellForRowAtIndexPath:返回的单元格的detailTextLabel的文本集是否与格式化程序返回的字符串一致。正如你所说测试日期格式化程序返回的字符串是脆弱的所以我们可以模拟它,stub stringFromDate:返回一个常量并验证detailTextLabel的文本是否设置为该常量。

所以我们写了测试。我试着用新西兰人的嘲笑来写它,对不起,如果我做错了什么 - 这个想法很重要:

TableViewController *sut;// Instantiate the table view controller

id mockDateFormatter = [NSDateFormatter mock];

NSString * const kFormattedDate = @"formattedDate";
NSDate * const date = [NSDate date];

[mockDateFormatter stub:@selector(stringFromDate:) andReturn:kFormattedDate withArguments:date,nil];

sut.dateFormatter = mockDateFormatter;
sut.dates = @[date];// As an example we have an array of dates to show. In the real case we would have an array of the objects you want to show in the table view.

[sut view];// In case we have the registered cells...
UITableViewCell *cell = [sut tableView:sut.tableView 
                 cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

STAssertEqualObjects(cell.detailTextLabel.text, kFormattedDate, nil);

满足该测试的方法是这样的:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    // I assume you have a standard cell registered in your table view
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier"];

    NSDate *date = [self.dates objectAtIndex:indexPath.row];
    cell.detailTextLabel.text = [self.dateFormatter stringFromDate:date];

    return cell;
}

希望它有所帮助。