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

Cocoa touch 使用NSDateFormatter避免脆弱的单元测试,cocoa-touch,unit-testing,ocunit,xctest,kiwi,Cocoa Touch,Unit Testing,Ocunit,Xctest,Kiwi,测试NSDateFormatter方法的最佳实践是什么?例如,假设我有一个方法: - (NSString *)formatStringFromDate:(NSDate *)date { NSDateFormatter *f = [[NSDateFormatter alloc] init]; [f setTimeStyle:NSDateFormatterShortStyle]; [f setDateStyle:NSDateFormatterNoStyle]; re

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

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

    return [f stringFromDate:date];
}
我可以用两种方法用猕猴桃测试这种方法:

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。

正如您在评论中所说的,您想要测试的是单元格的detailTextLabel的文本设置为日期,并在有日期时使用预期的格式

这可以用几种不同的方法进行测试,我在这里展示了我想要的方法

首先,每次我们想要格式化一个日期时,创建一个日期格式化程序是没有效率的,这会使测试更加困难

因此,我建议在表视图控制器中创建一个日期格式化程序属性:

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

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

    }
    return _dateFormatter;
}
对于日期格式化程序,我将创建一个验证时间样式和日期样式的简单测试。我不熟悉猕猴桃,所以我使用OCUnit断言:

TableViewController *sut;// Instantiate the table view controller
STAssertTrue(sut.dateFormatter.timeStyle == NSDateFormatterShortStyle, nil);
STAssertTrue(sut.dateFormatter.dateStyle == setDateStyle:NSDateFormatterNoStyle, nil);
完成此操作后,我们的想法是创建一个测试,以验证
tableView:cellforrowatinexpath:
返回的单元格是否使用格式化程序返回的字符串设置了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;
}

希望能有所帮助。

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

这可以用几种不同的方法进行测试,我在这里展示了我想要的方法

首先,每次我们想要格式化一个日期时,创建一个日期格式化程序是没有效率的,这会使测试更加困难

因此,我建议在表视图控制器中创建一个日期格式化程序属性:

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

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

    }
    return _dateFormatter;
}
对于日期格式化程序,我将创建一个验证时间样式和日期样式的简单测试。我不熟悉猕猴桃,所以我使用OCUnit断言:

TableViewController *sut;// Instantiate the table view controller
STAssertTrue(sut.dateFormatter.timeStyle == NSDateFormatterShortStyle, nil);
STAssertTrue(sut.dateFormatter.dateStyle == setDateStyle:NSDateFormatterNoStyle, nil);
完成此操作后,我们的想法是创建一个测试,以验证
tableView:cellforrowatinexpath:
返回的单元格是否使用格式化程序返回的字符串设置了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;
}

希望有帮助。

测试的目的是什么?要检查NSDateFormatter是否工作,我实际上是在测试当存在日期属性时,
UITableViewCell
是否设置了
detailTextLabel.text
。我简化了我的问题的测试和方法。你想测试的代码在哪里?在视图控制器或表视图单元格子类中?它在
UITableViewController
子类中。测试的目的是什么?要检查NSDateFormatter是否工作,我实际上是在测试当存在日期属性时,
UITableViewCell
是否设置了
detailTextLabel.text
。我简化了我的问题的测试和方法。你想测试的代码在哪里?在视图控制器或表视图单元格子类中?它在
UITableViewController
子类中。回答得很好,正是我想要的。谢谢。只是一个小提示:我会使用
statsertequals(sut.dateFormatter.timeStyle,NSDateFormatterShortStyle,nil)
。失败时,错误消息显示当前分配给日期格式化程序的
timeStyle
,因此信息量更大。非常好的答案,正是我想要的。谢谢。只是一个小提示:我会使用
statsertequals(sut.dateFormatter.timeStyle,NSDateFormatterShortStyle,nil)
。失败时,错误消息将显示当前分配给日期格式化程序的
时间样式
,因此信息更丰富。