如何将两个对象与objective-c中的许多属性进行比较

时间:2014-04-04 09:09:56

标签: objective-c unit-testing serialization deserialization xctest

我想测试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方法,但我希望有更好的解决方案。

由于

2 个答案:

答案 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:方法,而是密切关注测试驱动开发。


回顾一下:

TDD Waltz

  • 编写一个失败的测试
  • 编写最简单的代码以通过测试
  • 重构生产和测试代码
  • 并重复

答案 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;
}

可能我已经忘记了一些事情,但你应该总体了解如何做到这一点。