【问题标题】:How to test a simple data object class which implements NSCoding?如何测试实现 NSCoding 的简单数据对象类?
【发布时间】:2015-08-20 14:51:02
【问题描述】:

我有一个需要序列化和反序列化的小数据对象。假设它被称为WeatherDetails,它看起来像这样:

WeatherDetails.h

@interface WeatherDetails : NSObject <NSCoding>
{
@private

@protected
}

#pragma mark - Properties

@property (nonatomic, copy) NSString *weatherCode;
@property (nonatomic, copy) NSString *weatherDescription;

@end

WeatherDetails.m

#import "WeatherDetails.h"

@implementation WeatherDetails

NSString *const WEATHER_DETAILS_WEATHER_CODE_KEY = @"s";
NSString *const WEATHER_DETAILS_WEATHER_DESCRIPTION_KEY = @"sT";

#pragma mark - Initialization, NSCoding and Dealloc

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];

    _weatherCode = [aDecoder decodeObjectForKey:@"weatherCode"];
    _weatherDescription = [aDecoder decodeObjectForKey:@"weatherDescription"];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:_weatherCode forKey:@"weatherCode"];
    [aCoder encodeObject:_weatherDescription forKey:@"weatherDescription"];
}

目前我的测试是这样的;

#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import "WeatherDetails.h"
@interface WeatherDetailsTests : XCTestCase

@end

@implementation WeatherDetailsTests

- (void)testThatWeatherCodeIsEncoded
{
    WeatherDetails *details = [[WeatherDetails alloc] init];
    [details setWeatherCode:@"A"];

    NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:details];

    WeatherDetails *unarchive = [NSKeyedUnarchiver unarchiveObjectWithData:archive];

    XCTAssertEqualObjects(@"A", [unarchive weatherCode]);
}

- (void)testThatWeatherDescriptionIsEncoded
{
    WeatherDetails *details = [[WeatherDetails alloc] init];
    [details setWeatherDescription:@"A"];

    NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:details];

    WeatherDetails *unarchive = [NSKeyedUnarchiver unarchiveObjectWithData:archive];

    XCTAssertEqualObjects(@"A", [unarchive weatherDescription]);
}

我有一种直觉,这种测试所有属性是否正确编码的方法并不是真正的最佳方法,因为存在重复,但我真的想不出更好的方法。有没有人对我有改进的建议?

【问题讨论】:

    标签: ios unit-testing xctest


    【解决方案1】:

    您真正要测试的是对象在归档之前和之后是否相同。

    在您的WeatherDetails 中实现一个方法来比较对象(或覆盖isEqual:)。

    - (BOOL)isEqualToWeatherDetails:(WeatherDetails *)details
    {
        if (![details isKindOfClass:[WeatherDetails class]]) return NO;
        return [self.weatherCode == details.weatherCode && self.weatherDescription isEqualToString:details.weatherDescription];
    }
    

    然后你可以一次完成所有的相等比较:

    - (void)testNSCoder
    {
        WeatherDetails *details = [[WeatherDetails alloc] init];
        [details setWeatherCode:@"A"];
        details.weatherDescription = @"Cloudy";
    
        NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:details];
    
        WeatherDetails *unarchive = [NSKeyedUnarchiver unarchiveObjectWithData:archive];
    
        XCTAssertTrue([details isEqualToWeatherDetails:unarchive]);
    }
    

    如果您覆盖isEqual:,那么您可以比较这样做:

    XCTAssertEqualObjects(details, unarchive);
    

    Apple 倾向于添加其他方法(isEqualToArray:isEqualToDictionary:)。 isEqual: 被 NSSet 和 NSDictionary 等集合使用

    【讨论】:

    • 这也是我最初的想法。我想要考虑的事情是您希望能够以多快的速度找到确切的故障点;在这种特殊情况下,在您提出的解决方案中找到这个确切的故障点可能并不比单独测试每个属性以进行序列化更难,因此您提出的解决方案是有效的。
    【解决方案2】:

    最后我通过应用custom assertion 对其进行了改进;

    什么时候使用它

    只要满足以下任一条件,我们就应该考虑创建自定义断言:

    • 我们发现自己在一个又一个的测试中编写(或克隆)相同的断言逻辑
    • 我们发现自己在测试的结果验证部分编写条件测试逻辑。也就是说,我们对断言方法的调用嵌入在 if 语句或循环中。
    • 我们测试的结果验证部分受到模糊测试的影响,因为我们在测试中使用程序性而非声明性结果验证。
    • 每当断言失败时,我们都会进行频繁调试,因为它们没有提供足够的信息。

    自定义断言目前如下所示:

    /*!
     * @define XCTAssertEqualSerialized(value, object, selector)
     * Serializes (\a object), then desirializes it and compares result's property (\a propertyName) to (\a value) with XCTAssertEqualObjects
     * @param value Value to compare.
     * @param object Object to serialize.
     * @param selector Objects property to compare.
     */
    
    #define XCTAssertEqualSerialized(value, object, selector) \
    ({ \
    NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:object]; \
    NSObject *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:archived]; \
    XCTAssertEqualObjects(value, [unarchived valueForKeyPath:NSStringFromSelector(selector)]); \
    })
    
    /*!
     * @define XCTAssertNotNilSerialized(object, selector)
     * Serializes (\a object), then desirializes it and checks result's property (\a propertyName) is not nil with XCTAssertNotNil
     * @param object Object to serialize.
     * @param selector Objects property to compare.
     */
    
    #define XCTAssertNotNilSerialized(object, selector) \
    ({ \
    NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:object]; \
    NSObject *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:archived]; \
    XCTAssertNotNil([unarchived valueForKeyPath:NSStringFromSelector(selector)]); \
    })
    

    测试结果如下:

    - (void)testThatWeatherCodeIsEncoded
    {
        WeatherDetails *details = [[WeatherDetails alloc] init];
        [details setWeatherCode:@"A"];
    
        XCTAssertEqualSerialized(@"A", details, @selector(weatherCode));
    }
    
    - (void)testThatWeatherDescriptionIsEncoded
    {
        WeatherDetails *details = [[WeatherDetails alloc] init];
        [details setWeatherDescription:@"A"];
    
        XCTAssertEqualSerialized(@"A", details, @selector(weatherDescription));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-03
      • 1970-01-01
      • 1970-01-01
      • 2015-03-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多