[英]How to test equality of Swift enums with associated values
我想测试两个 Swift 枚举值的相等性。 例如:
enum SimpleToken {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)
但是,编译器不会编译相等表达式:
error: could not find an overload for '==' that accepts the supplied arguments
XCTAssert(t1 == t2)
^~~~~~~~~~~~~~~~~~~
我是否必须定义自己的相等运算符重载? 我希望 Swift 编译器能够自动处理它,就像 Scala 和 Ocaml 那样。
正如@jedwidz指出的那样,从 Swift 4.1 开始(由于SE-0185 ,Swift 还支持为具有关联值的枚举合成Equatable
和Hashable
。
因此,如果您使用的是 Swift 4.1 或更高版本,以下内容将自动合成必要的方法,以便XCTAssert(t1 == t2)
工作。 关键是将Equatable
协议添加到您的枚举中。
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
正如其他人所指出的,Swift 不会自动合成必要的相等运算符。 不过,让我提出一个更清洁(恕我直言)的实现:
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
switch (lhs, rhs) {
case let (.Name(a), .Name(b)),
let (.Number(a), .Number(b)):
return a == b
default:
return false
}
}
这远非理想 - 有很多重复 - 但至少你不需要在里面做带有 if 语句的嵌套开关。
实施Equatable
是一种矫枉过正的恕我直言。 想象一下,您有一个复杂而庞大的枚举,其中包含许多情况和许多不同的参数。 这些参数也都必须实现Equatable
。 此外,谁说你在全有或全无的基础上比较枚举案例? 如果您正在测试值并且只存根了一个特定的枚举参数,该怎么办? 我强烈建议使用简单的方法,例如:
if case .NotRecognized = error {
// Success
} else {
XCTFail("wrong error")
}
...或在参数评估的情况下:
if case .Unauthorized401(_, let response, _) = networkError {
XCTAssertEqual(response.statusCode, 401)
} else {
XCTFail("Unauthorized401 was expected")
}
在这里找到更详细的描述: https : //mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/
enum MyEnum {
case none
case simple(text: String)
case advanced(x: Int, y: Int)
}
func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
switch (lhs, rhs) {
case (.none, .none):
return true
case let (.simple(v0), .simple(v1)):
return v0 == v1
case let (.advanced(x0, y0), .advanced(x1, y1)):
return x0 == x1 && y0 == y1
default:
return false
}
}
似乎没有编译器为枚举和结构生成相等运算符。
“例如,如果您创建自己的类或结构来表示复杂的数据模型,那么 Swift 无法为您猜测该类或结构的“等于”含义。” [1]
要实现相等比较,可以编写如下内容:
@infix func ==(a:SimpleToken, b:SimpleToken) -> Bool {
switch(a) {
case let .Name(sa):
switch(b) {
case let .Name(sb): return sa == sb
default: return false
}
case let .Number(na):
switch(b) {
case let .Number(nb): return na == nb
default: return false
}
}
}
这是另一种选择。 除了通过使用if case
语法避免嵌套 switch 语句外,它与其他主要相同。 我认为这使它稍微更具可读性(/可承受)并且具有完全避免默认情况的优势。
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
extension SimpleToken {
func isEqual(st: SimpleToken)->Bool {
switch self {
case .Name(let v1):
if case .Name(let v2) = st where v1 == v2 { return true }
case .Number(let i1):
if case .Number(let i2) = st where i1 == i2 { return true }
}
return false
}
}
func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
return lhs.isEqual(rhs)
}
let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")
t1 == t1 // true
t1 == t2 // false
t3 == t3 // true
t3 == t4 // false
t1 == t3 // false
我在单元测试代码中使用这个简单的解决方法:
extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}
它使用字符串插值来执行比较。 我不会推荐它用于生产代码,但它简洁并且可以完成单元测试的工作。
另一种选择是比较案例的字符串表示形式:
XCTAssert(String(t1) == String(t2))
例如:
let t1 = SimpleToken.Number(123) // the string representation is "Number(123)"
let t2 = SimpleToken.Number(123)
let t3 = SimpleToken.Name("bob") // the string representation is "Name(\"bob\")"
String(t1) == String(t2) //true
String(t1) == String(t3) //false
扩展 mbpro 的答案,这是我如何使用该方法检查具有某些边缘情况的关联值的 swift 枚举的相等性。
当然,您可以执行 switch 语句,但有时只检查一行中的一个值也不错。 你可以这样做:
// NOTE: there's only 1 equal (`=`) sign! Not the 2 (`==`) that you're used to for the equality operator
// 2nd NOTE: Your variable must come 2nd in the clause
if case .yourEnumCase(associatedValueIfNeeded) = yourEnumVariable {
// success
}
如果要比较同一个 if 子句中的 2 个条件,则需要使用逗号而不是&&
运算符:
if someOtherCondition, case .yourEnumCase = yourEnumVariable {
// success
}
另一种使用if case
和逗号的方法,适用于 Swift 3:
enum {
case kindOne(String)
case kindTwo(NSManagedObjectID)
case kindThree(Int)
static func ==(lhs: MyEnumType, rhs: MyEnumType) -> Bool {
if case .kindOne(let l) = lhs,
case .kindOne(let r) = rhs {
return l == r
}
if case .kindTwo(let l) = lhs,
case .kindTwo(let r) = rhs {
return l == r
}
if case .kindThree(let l) = lhs,
case .kindThree(let r) = rhs {
return l == r
}
return false
}
}
这是我在我的项目中写的。 但我不记得我是从哪里得到这个想法的。 (我刚刚用谷歌搜索但没有看到这样的用法。)任何评论将不胜感激。
t1 和 t2 不是数字,它们是具有关联值的 SimpleTokens 的实例。
你可以说
var t1 = SimpleToken.Number(123)
然后你可以说
t1 = SimpleToken.Name(“Smith”)
没有编译器错误。
要从 t1 检索值,请使用 switch 语句:
switch t1 {
case let .Number(numValue):
println("Number: \(numValue)")
case let .Name(strValue):
println("Name: \(strValue)")
}
与接受的答案相比,“优势”是,“main” switch 语句中没有“default” case,因此如果您使用其他 case 扩展枚举,编译器将强制您更新其余代码。
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
extension SimpleToken {
func isEqual(st: SimpleToken)->Bool {
switch self {
case .Name(let v1):
switch st {
case .Name(let v2): return v1 == v2
default: return false
}
case .Number(let i1):
switch st {
case .Number(let i2): return i1 == i2
default: return false
}
}
}
}
func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
return lhs.isEqual(rhs)
}
let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")
t1 == t1 // true
t1 == t2 // false
t3 == t3 // true
t3 == t4 // false
t1 == t3 // false
从 Swift 4.1 开始,只需将Equatable
协议添加到您的枚举并使用XCTAssert
或XCTAssertEqual
:
enum SimpleToken : Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssertEqual(t1, t2) // OK
您可以使用 switch 进行比较
enum SimpleToken {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
switch(t1) {
case let .Number(a):
switch(t2) {
case let . Number(b):
if a == b
{
println("Equal")
}
default:
println("Not equal")
}
default:
println("No Match")
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.