如何测试实现 NSCoding 的简单数据对象 class?
How to test a simple data object class which implements NSCoding?
我有一个小数据对象需要序列化和反序列化。假设它被称为 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]);
}
我有一种直觉,这种测试所有属性是否正确编码的方法并不是真正的最佳方法,因为存在重复,但我真的想不出更好的方法。有没有人对我有改进的建议?
您真正要测试的是对象在归档前后是否相同。
在您的 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 等集合使用
最后我通过应用 custom assertion;
改进了它
When To Use It
We should consider creating a Custom Assertion whenever any of the following are true:
- We find ourselves writing (or cloning) the same assertion logic in test after test
- We find ourselves writing Conditional Test Logic in the result verification part of our tests. That is, our calls to Assertion Methods are embedded in if statements or loops.
- The result verification parts of our tests are suffering from Obscure Test because we are using procedural rather than declarative result verification in the tests.
- We find ourselves doing Frequent Debugging whenever assertions fail because they do not provide enough information.
自定义断言当前如下所示:
/*!
* @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));
}
我有一个小数据对象需要序列化和反序列化。假设它被称为 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]);
}
我有一种直觉,这种测试所有属性是否正确编码的方法并不是真正的最佳方法,因为存在重复,但我真的想不出更好的方法。有没有人对我有改进的建议?
您真正要测试的是对象在归档前后是否相同。
在您的 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 等集合使用
最后我通过应用 custom assertion;
改进了它When To Use It
We should consider creating a Custom Assertion whenever any of the following are true:
- We find ourselves writing (or cloning) the same assertion logic in test after test
- We find ourselves writing Conditional Test Logic in the result verification part of our tests. That is, our calls to Assertion Methods are embedded in if statements or loops.
- The result verification parts of our tests are suffering from Obscure Test because we are using procedural rather than declarative result verification in the tests.
- We find ourselves doing Frequent Debugging whenever assertions fail because they do not provide enough information.
自定义断言当前如下所示:
/*!
* @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));
}