如何在objective-c中比较两个具有很多属性的对象
Posted
技术标签:
【中文标题】如何在objective-c中比较两个具有很多属性的对象【英文标题】:How compare two objects with a lot of attributes in objective-c 【发布时间】:2014-04-04 09:09:56 【问题描述】:我想测试我的 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");
这个实现的主要问题是我必须重写Basket
类中的isEqual
方法,并且对于每个AProduct
对象,重写isEqual
方法来检查所有属性。我认为这是非常危险的,因为如果我或其他开发人员添加了一个新属性并忘记在 isEqual
方法上检查此属性的值,那么篮子的单元测试可能会成功,但反序列化失败。
有没有办法避免这种风险?虽然我解析了类的所有属性,并为每个属性运行isEqual
method,但我希望有更好的解决方案。
谢谢
【问题讨论】:
您知道您可以覆盖-isEqual:
并访问子类的实现吗?在类层次结构中,每个子类只会检查它定义的属性。
是的,我知道我可以覆盖 isEqual 方法。我在原来的帖子中谈到了这个,看看。
但是你知道你可以调用超级实现吗?
是的,但我不明白你的回答是什么意思。
我扩展了我的答案。实际上是您没有正确执行 TDD。
【参考方案1】:
标准的 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:
工作正常,但我还没有在 Shirt 中覆盖和扩展它。
现在我编写以下测试方法:
- (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:
方法,而是紧紧地坚持测试驱动开发。
回顾一下:
TDD 圆舞曲
编写一个失败的测试 编写最简单的代码以通过测试 重构生产和测试代码 然后重复【讨论】:
是的,谢谢,我同意你的看法。但是,在我添加到类中的每个属性中添加 isEqual 方法(或执行相同操作的自定义方法)中的比较测试是一项艰巨的任务。但是当我看到你所有的答案时,我想没有别的办法了。 为什么禁止给-isEqual:
添加对比测试?
我们很容易忘记添加比较,然后,单元测试将不会完全检查两个对象之间的相等性。
这就是你进行单元测试的原因:得到提醒。这也是为什么您应该首先编写将失败的测试,然后通过添加正确的代码将它们变为绿色。
(我认为@JeanLbr 的意思是“不祥之兆”。)【参考方案2】:
如果您想为单元测试目的使用自动比较方法,并且此方法应该能够抵抗属性列表的更改,那么您别无选择,您必须使用元数据。
我不会在生产代码中使用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;
可能我忘记了某些事情,但您应该大致了解如何做到这一点。
【讨论】:
感谢您的回答!我认为这种方法非常危险,因为如果开发人员在没有在 areEqual 方法中检查的情况下将属性添加到类中,那么篮子的单元测试将不再有用。但是如果没有其他方法,我会这样做。以上是关于如何在objective-c中比较两个具有很多属性的对象的主要内容,如果未能解决你的问题,请参考以下文章