简体   繁体   English

如何使用关联值测试 Swift 枚举的相等性

[英]How to test equality of Swift enums with associated values

I want to test the equality of two Swift enum values.我想测试两个 Swift 枚举值的相等性。 For example:例如:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

However, the compiler won't compile the equality expression:但是,编译器不会编译相等表达式:

error: could not find an overload for '==' that accepts the supplied arguments
    XCTAssert(t1 == t2)
    ^~~~~~~~~~~~~~~~~~~

Do I have do define my own overload of the equality operator?我是否必须定义自己的相等运算符重载? I was hoping the Swift compiler would handle it automatically, much like Scala and Ocaml do.我希望 Swift 编译器能够自动处理它,就像 Scala 和 Ocaml 那样。

Swift 4.1+斯威夫特 4.1+

As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185 , Swift also supports synthesizing Equatable and Hashable for enums with associated values.正如@jedwidz指出的那样,从 Swift 4.1 开始(由于SE-0185 ,Swift 还支持为具有关联值的枚举合成EquatableHashable

So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2) works.因此,如果您使用的是 Swift 4.1 或更高版本,以下内容将自动合成必要的方法,以便XCTAssert(t1 == t2)工作。 The key is to add the Equatable protocol to your enum.关键是将Equatable协议添加到您的枚举中。

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

Before Swift 4.1 Swift 4.1 之前

As others have noted, Swift doesn't synthesize the necessary equality operators automatically.正如其他人所指出的,Swift 不会自动合成必要的相等运算符。 Let me propose a cleaner (IMHO) implementation, though:不过,让我提出一个更清洁(恕我直言)的实现:

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
    }
}

It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.这远非理想 - 有很多重复 - 但至少你不需要在里面做带有 if 语句的嵌套开关。

Implementing Equatable is an overkill IMHO.实施Equatable是一种矫枉过正的恕我直言。 Imagine you have complicated and large enum with many cases and many different parameters.想象一下,您有一个复杂而庞大的枚举,其中包含许多情况和许多不同的参数。 These parameters will all have to have Equatable implemented, too.这些参数也都必须实现Equatable Furthermore, who said you compare enum cases on all-or-nothing basis?此外,谁说你在全有或全无的基础上比较枚举案例? How about if you are testing value and have stubbed only one particular enum parameter?如果您正在测试值并且只存根了一个特定的枚举参数,该怎么办? I would strongly suggest simple approach, like:我强烈建议使用简单的方法,例如:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... or in case of parameter evaluation: ...或在参数评估的情况下:

if case .Unauthorized401(_, let response, _) = networkError {
    XCTAssertEqual(response.statusCode, 401)
} else {
    XCTFail("Unauthorized401 was expected")
}

Find more elaborate description here: https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/在这里找到更详细的描述: 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
    }
}

There seems no compiler generated equality operator for enums, nor for structs.似乎没有编译器为枚举和结构生成相等运算符。

“If you create your own class or structure to represent a complex data model, for example, then the meaning of “equal to” for that class or structure is not something that Swift can guess for you.” “例如,如果您创建自己的类或结构来表示复杂的数据模型,那么 Swift 无法为您猜测该类或结构的“等于”含义。” [1] [1]

To implement equality comparison, one would write something like:要实现相等比较,可以编写如下内容:

@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
        }
    }
}

[1] See "Equivalence Operators" at https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43 [1] 请参阅https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43 上的“等效运算符”

Here's another option.这是另一种选择。 It's mainly the same as the others except it avoids the nested switch statements by using the if case syntax.除了通过使用if case语法避免嵌套 switch 语句外,它与其他主要相同。 I think this makes it slightly more readable(/bearable) and has the advantage of avoiding the default case altogether.我认为这使它稍微更具可读性(/可承受)并且具有完全避免默认情况的优势。

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

I'm using this simple workaround in unit test code:我在单元测试代码中使用这个简单的解决方法:

extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}

It uses string interpolation to perform the comparison.它使用字符串插值来执行比较。 I would not recommend it for production code, but it's concise and does the job for unit testing.我不会推荐它用于生产代码,但它简洁并且可以完成单元测试的工作。

Another option would be to compare the string representations of the cases:另一种选择是比较案例的字符串表示形式:

XCTAssert(String(t1) == String(t2))

For example:例如:

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

Expanding on mbpro's answer, here's how I used that approach to check for equality of swift enums with associated values with some edge cases.扩展 mbpro 的答案,这是我如何使用该方法检查具有某些边缘情况的关联值的 swift 枚举的相等性。

Of course you can do a switch statement, but sometimes it's nice to just check for one value in one line.当然,您可以执行 switch 语句,但有时只检查一行中的一个值也不错。 You can do it like so:你可以这样做:

// 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 you want to compare 2 conditions in the same if clause, you need to use the comma instead of the && operator:如果要比较同一个 if 子句中的 2 个条件,则需要使用逗号而不是&&运算符:

if someOtherCondition, case .yourEnumCase = yourEnumVariable {
  // success
}

Another approach using if case with commas, which works in Swift 3:另一种使用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
  }
}

This is how I wrote in my project.这是我在我的项目中写的。 But I can't remember where I got the idea.但我不记得我是从哪里得到这个想法的。 (I googled just now but didn't see such usage.) Any comment would be appreciated. (我刚刚用谷歌搜索但没有看到这样的用法。)任何评论将不胜感激。

t1 and t2 are not numbers, they are instances of SimpleTokens with values associated. t1 和 t2 不是数字,它们是具有关联值的 SimpleTokens 的实例。

You can say你可以说

var t1 = SimpleToken.Number(123)

You can then say然后你可以说

t1 = SimpleToken.Name(“Smith”) 

without a compiler error.没有编译器错误。

To retrieve the value from t1, use a switch statement:要从 t1 检索值,请使用 switch 语句:

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

the 'advantage' when compare to accepted answer is, that there is no 'default' case in 'main' switch statement, so if you extend your enum with other cases, the compiler will force you to update the rest of code.与接受的答案相比,“优势”是,“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

From Swift 4.1 just add Equatable protocol to your enum and use XCTAssert or XCTAssertEqual :从 Swift 4.1 开始,只需将Equatable协议添加到您的枚举并使用XCTAssertXCTAssertEqual

enum SimpleToken : Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssertEqual(t1, t2) // OK

You can compare using switch您可以使用 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM