我想测试Basket
类型的对象框的反序列化,其中包含AProduct
类型的对象数组。
我的所有产品类都继承自AProduct
,这是一个抽象类。
渐渐地,我将拥有越来越多的产品类,每个产品类都有不同的属性。
目前,我正在测试我的篮子的反序列化:
Basket *oldBasket = BasketInstance;
[BasketInstance resetBasket]; // BasketInstance is a macro which points on the current Basket in singleton.
[BasketInstance deserializeItself:[self getBasketSerializedForTest]]; // getBasketSerializedForTest return a dictionnary of the same basket serialized.
XCTAssertTrue([BasketInstance isEqual:oldBasket], @"Basket deserialization is no correct");
此实现的主要问题是我必须覆盖isEqual
类中的Basket
方法以及每个AProduct
对象,重写isEqual
方法以检查所有属性。我认为这是非常危险的,因为如果我或其他开发人员添加了一个新属性并忘记在isEqual
方法上检查此属性的值,那么篮子的单元测试可能会因破损而失败。
是否有任何解决方案可以避免这种风险?我虽然解析了类的所有属性,但是对于每个属性,运行isEqual
方法,但我希望有更好的解决方案。
由于
答案 0 :(得分:3)
标准的OOP方法是每个子类测试其自己的属性。因此每个属性都会被检查一次。我认为这是一个简单明了的任务。没有任何危险,也没有比运行时更复杂。
@interface AProduct : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *stockIdentifier;
@end
@implementation AProduct
-(BOOL)isEqual:(id)object
{
BOOL equal= [object isKindOfClass:[self class]]
&& [[(AProduct *)object name] isEqualToString:self.name]
&& [[(AProduct *)object stockIdentifier] isEqualToString:self.stockIdentifier];
return equal;
}
@end
@interface Shirt : AProduct
@property (nonatomic, copy) NSString *size;
@property (nonatomic, copy) NSString *color;
@end
@implementation Shirt
-(BOOL)isEqual:(id)object
{
BOOL equal = [super isEqual:object];
if (equal) {
equal = [[(Shirt *)object size] isEqualToString:self.size]
&& [[(Shirt *)object color] isEqualToString:self.color];
}
return equal;
}
当你担心单元测试时:你应该编写一个相等的测试,我认为它也会失败。如果红色和蓝色衬衫的评价相同,则此测试失败并且您知道必须查看-isEqual:
假设我只是将AProduct子类化为Shirt。 AProduct让它-isEqual:
正常工作,但我还没有在衬衫上覆盖并扩展它。
现在我写下以下测试方法:
- (void)testShirtInEquality
{
Shirt *redShirt = [[Shirt alloc] init];
redShirt.name =@"Shirt";
redShirt.stockIdentifier =@"shirt";
redShirt.size = @"XXL";
redShirt.color = @"red";
Shirt *blueShirt = [[Shirt alloc] init];
blueShirt.name =@"Shirt";
blueShirt.stockIdentifier =@"shirt";
blueShirt.size = @"XXL";
blueShirt.color = @"blue";
XCTAssertNotEqualObjects(redShirt, blueShirt, @"not equal!");
}
它将失败,因为-isEqual:
的AProduct实现将触发并发现所有内容都相同。现在我知道我必须编写代码来使这个测试通过,然后我添加
-(BOOL)isEqual:(id)object
{
BOOL equal = [super isEqual:object];
if (equal) {
equal = [[(Shirt *)object size] isEqualToString:self.size]
&& [[(Shirt *)object color] isEqualToString:self.color];
}
return equal;
}
测试通过。
所以实际上答案不是编写动态-isEqual:
方法,而是密切关注测试驱动开发。
回顾一下:
答案 1 :(得分:1)
如果您希望自动比较方法用于单元测试,并且此方法应该能够抵抗属性列表中的更改,那么您无需使用元数据进行此操作。
我不会在生产代码中使用isEqual:
方法来执行此操作,因为这样的自动方法可能会使某些人感到困惑并导致意外问题。因此比较器应仅用于单元测试目的。
BOOL areEqual(AProduct *a, AProduct *b) {
if (![a isMemberOfClass: [b class])
return NO;
unsigned int count = 0, i;
objc_property_t *props = class_copyPropertyList([b class], &count)
for (i=0; i<count; ++i) {
objc_property_t property = props[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
id aValue = [a valueForKey: name];
id bValue = [b valueForKey: name];
if ([aValue isKindOfClass: [AProduct class]) {
if (!areEqual((AProduct *)aValue, (AProduct *)bValue))
return NO;
} else {
if (![aValue isEqual: bValue])
return NO;
}
}
return YES;
}
可能我已经忘记了一些事情,但你应该总体了解如何做到这一点。