XCTAssert错误:("3")不等于("3")。

43
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:@"1"];
[arr addObject:@"2"];
[arr addObject:@"3"];

// This statement is fine.
XCTAssertTrue(arr.count == 3, @"Wrong array size.");

// This assertion fails with an error: ((arr.count) equal to (3)) failed: ("3") is not equal to ("3")
XCTAssertEqual(arr.count, 3, @"Wrong array size.");

我对XCTAssertEqual理解有误吗?为什么最后一个断言失败了?


2
还有一些其他很棒的匹配库,比如OCHamcrest和Expecta。还有Kiwi和Cedar——完整的测试框架,带有漂亮的内置匹配库。(以防你还没有尝试过这些)。 - Jasper Blues
5个回答

50

我在使用Xcode 5的测试时也遇到了不少麻烦。它仍然存在一些奇怪的错误行为,但我已经找到了你的特定的XCTAssertEqual不起作用的明确原因。

如果我们看一下测试代码,我们可以看到它实际上执行以下操作(直接从XCTestsAssertionsImpl.h中获取 - 可以在那里更轻松地查看):

#define _XCTPrimitiveAssertEqual(a1, a2, format...) \
({ \
    @try { \
        __typeof__(a1) a1value = (a1); \
        __typeof__(a2) a2value = (a2); \
        NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))]; \
        NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))]; \
        float aNaN = NAN; \
        NSValue *aNaNencoded = [NSValue value:&aNaN withObjCType:@encode(__typeof__(aNaN))]; \
        if ([a1encoded isEqualToValue:aNaNencoded] || [a2encoded isEqualToValue:aNaNencoded] || ![a1encoded isEqualToValue:a2encoded]) { \
                _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_Equal, 0, @#a1, @#a2, _XCTDescriptionForValue(a1encoded), _XCTDescriptionForValue(a2encoded)),format); \
        } \
    } \
    @catch (id exception) { \
        _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_Equal, 1, @#a1, @#a2, [exception reason]),format); \
    }\
})

问题在于:

测试实际上是将值编码为NSValue,然后进行比较。你可能会问:“好吧,但这有什么问题?”我之前也没有想到,直到我自己制作了一个测试案例。问题在于,NSValue的-isEqualToValue方法必须同时比较NSValue的编码类型和实际值。只有两者都相等时该方法才会返回YES

在你的情况下,arr.count是一个NSUInteger,它是unsigned int的一个typedef。编译时常量3在运行时可能退化为signed int。因此,当将它们放入NSValue对象中时,它们的编码类型不相等,因此根据-[NSValue isEqualToValue],这两个值不能相等

你可以通过一个自定义示例来证明这一点。下面的代码显式地执行了与XCTAssertEqual完全相同的操作:

// Note explicit types
unsigned int a1 = 3;
signed int a2 = 3;

__typeof__(a1) a1value = (a1);
__typeof__(a2) a2value = (a2);

NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))];
NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))];

if (![a1encoded isEqualToValue:a2encoded]) {
    NSLog(@"3 != 3 :(");
}

每次日志中都会出现"3 != 3 :(",这其实是预期的行为。 NSValue 在进行比较时会检查其类型编码。不幸的是,在测试两个(“相等”的)整数时,这不是我们所期望的。

顺便说一下,XCTAssertTrue 的逻辑更加直接,通常表现符合预期(同样,请参阅实际源代码以了解它如何确定断言是否失败)。


28
值得注意的是,正确的修复方法只需要简单地包含类型信息。XCTAssertEqual(arr.count, (NSUInteger)3, @"Wrong array size."); - Rob Napier
5
谢谢,一个更简单的方法是:XCTAssertEqual(arr.count, 3U, @"Wrong array size.");,意思是断言数组 arr 的元素个数为 3,如果不是则会输出错误信息 "Wrong array size." - owenfi
4
最好使用(NSUInteger)3而不是3U,因为对于64位和32位编译,NSUInteger的typedef不同。在64位中,NSUInteger是无符号长整型 unsigned long,而在32位中是无符号整型 unsigned int - zim
或者使用我从@WayneHartman在链接中得到的答案学到的XCTAssertEqualWithAccuracy(arr.count, 3, 0.000000001); - Elise van Looij

5

我也遇到了这个问题。正如 @ephemera 和 @napier 所指出的那样,这是一个类型问题。

可以通过使用 c-literal 修饰符提供正确类型的值来解决它。

XCTAssertEqual(arr.count, 3ul, @"Wrong array size.");

通过查看左侧使用的函数的返回类型,您可以找到正确的类型 - 在 arr.count 上进行 ALT-click

- (NSUInteger)count;

现在ALT键点击NSUInteger以查找其类型:

typedef unsigned long NSUInteger;

现在找到无符号长整型的c语言字面数值格式-谷歌是一个好朋友,但这个页面也可以使用:http://www.tutorialspoint.com/cprogramming/c_constants.htm。这里有一个快速提示,您可能需要使用U(无符号)L(长整型)或F(浮点型),并确保您写1.0而不是1以获得双精度。小写也可以,如上面的示例所示。

我认为如果你希望你的测试在32位和64位上都能正常运行,那么这种方法是不可行的。使用3ul将导致32位系统失败。 - ThomasW

3

如果有其他人像我一样遇到了由于双重比较而导致的问题(上面的解决方案对于浮点数和双精度数不起作用),请尝试:

XCTAssertEqualWithAccuracy(number.doubleValue, 12.34, 0.01);

生成失败,当 (\a expression1) 和 (\a expression2) 之间的差别大于 (\a accuracy)。

0

我也遇到了这个问题,非常感谢这里提供的解决方法。 简单提示一下,这个问题在 Xcode 5.1 版本中已经被修复了。

https://developer.apple.com/library/mac/releasenotes/DeveloperTools/RN-Xcode/xc5_release_notes/xc5_release_notes.html

XCTAssertEqual宏(以前使用OCUnit的STAssertEquals)可以正确比较不同类型的标量值,例如int和NSInteger,而无需转换。但是,它不能再接受非标量类型(例如结构体)进行比较。(14435933)
我还没有从Xcode 5.0.2升级,但我的同事已经升级了,之前由于这个问题而失败的相同XC测试现在可以通过不需要转换的方式来通过测试。

0

一个替代方案是只使用强制类型转换:

XCTAssertEqual(arr.count, (NSUInteger)3, @"Wrong array size.");

考虑到当前工具的状态,这可能是最好的解决方案,特别是如果您的代码中经常使用XCTAssertEqual,而不想切换到XCTAssertTrue

(我注意到@RobNapier在评论中提出了这个建议。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接