繁体   English   中英

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

[英]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 那样。

斯威夫特 4.1+

正如@jedwidz指出的那样,从 Swift 4.1 开始(由于SE-0185 ,Swift 还支持为具有关联值的枚举合成EquatableHashable

因此,如果您使用的是 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 语句的嵌套开关。

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

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

这是另一种选择。 除了通过使用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协议添加到您的枚举并使用XCTAssertXCTAssertEqual

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.

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