我想测试两个Swift enum值的相等性。例如:

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那样。


当前回答

我在单元测试代码中使用这个简单的解决方法:

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

它使用字符串插值来执行比较。我不建议在生产代码中使用它,但是它很简洁,并且可以用于单元测试。

其他回答

除了上面的答案之外,您还可以添加计算属性作为助手。这也是优化可读性的许多方法之一。

    enum UserAccountViewStack: Hashable {
        case notLoggedIn
        case initialDevicePairing
        case deviceMainView
        case leftMenu(LeftMenuStack? = nil)
        case rightMenu(RightMenuStack? = nil)

        static var `default`: Self {
            .deviceMainView
        }

        var isLeftMenu: Bool {
            if case .leftMenu = self {
                return true
            }
            return false
        }

        var isRightMenu: Bool {
            if case .rightMenu = self {
                return true
            }
            return false
        }
    }

在我看来,实现Equatable有点过头了。想象你有一个复杂的大枚举,有很多case和不同的参数。这些参数也都必须实现Equatable。此外,谁说你在全有或全无的基础上比较枚举案例?如果您正在测试值,并且只存根了一个特定的enum参数怎么办?我强烈建议简单的方法,比如:

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/

扩展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子句中的两个条件,你需要使用逗号而不是&&操作符:

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

对于枚举和结构,似乎没有编译器生成的相等操作符。

例如,如果你创建了自己的类或结构来表示一个复杂的数据模型,那么这个类或结构的“等于”的含义就不是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
        }
    }
}

[1]参见https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43的“等价运算符”

斯威夫特4.1 +

正如@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 4.1之前

正如其他人所注意到的,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语句里面做嵌套开关。