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

如何测试Swift枚举与关联值的相等性,swift,Swift,我想测试两个Swift枚举值的相等性。例如: enum SimpleToken { case Name(String) case Number(Int) } let t1 = SimpleToken.Number(123) let t2 = SimpleToken.Number(123) XCTAssert(t1 == t2) let t1 = SimpleToken.Number(123) // the string representation is "Number(123

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

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == 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
但是,编译器不会编译等式表达式:

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

我必须定义自己的相等运算符重载吗?我希望Swift编译器能够像Scala和Ocaml一样自动处理它。

似乎没有编译器为枚举或结构生成的相等运算符

“例如,如果您创建自己的类或结构来表示一个复杂的数据模型,那么斯威夫特无法猜测该类或结构的“等于”含义。”[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] 参见

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

您可以使用开关进行比较

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")
}
Swift 4.1+ 正如有帮助地指出的,从Swift 4.1(由于)开始,Swift还支持为具有关联值的枚举合成
equalable
Hashable

因此,如果您使用的是Swift 4.1或更新版本,下面将自动合成必要的方法,以便
xctasert(t1==t2)
工作。关键是将
equalable
协议添加到您的枚举中

enum SimpleToken:可均衡{
案例名称(字符串)
案件编号(Int)
}
设t1=SimpleToken.Number(123)
设t2=SimpleToken.Number(123)
Swift 4.1之前 正如其他人所指出的,Swift不会自动合成必要的相等运算符。不过,让我提出一个更干净的(IMHO)实现:

enum SimpleToken:可均衡{
案例名称(字符串)
案件编号(Int)
}
公共函数==(左:SimpleToken,右:SimpleToken)->Bool{
开关(左、右){
案例编号(.Name(a),.Name(b)),
让(.Number(a),.Number(b)):
返回a==b
违约:
返回错误
}
}

这远非理想——有很多重复——但至少您不需要在内部使用if语句进行嵌套切换。

我在单元测试代码中使用了以下简单的解决方法:

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

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

与公认的答案相比,“main”switch语句中没有“default”大小写,因此如果用其他大小写扩展枚举,编译器将强制您不使用o更新代码的其余部分

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

这里还有另一个选项。它与其他选项基本相同,只是它通过使用
if case
语法避免了嵌套的switch语句。我认为这使它更具可读性(/beable),并且具有完全避免默认情况的优点

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

另一个选项是比较案例的字符串表示形式:

XCTAssert(String(t1) == String(t2))
例如:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == 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

另一种方法是在Swift 3中使用带逗号的
if case

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

这就是我在项目中的写作方式。但我不记得我是从哪里得到这个想法的。(我刚才在谷歌上搜索过,但没有看到这样的用法。)任何评论都将不胜感激。

实现
equalable
是一种过分的做法。想象一下,您有一个复杂而庞大的枚举,包含许多实例和许多不同的参数。这些参数也必须实现
equalable
。此外,谁说您在全有或全无的基础上比较枚举实例?如何如果您正在测试值,并且只截取了一个特定的枚举参数,我强烈建议使用简单的方法,如:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}
…或在参数评估的情况下:

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

在这里可以找到更详细的描述:

在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 4.1开始,只需将
equalable
协议添加到您的枚举中,并使用
xctasert
xctasertequal

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

开的rdar://17408414 ()。从Swift 4.1开始,由于,Swift还支持为具有关联值的枚举合成
equalable
Hashable
。最糟糕的是,您需要在开关中使用默认语句,因此如果您添加新的枚举大小写,编译器不会确保您添加子句来比较新大小写的equalty–以后进行更改时,您只需记住并小心!您可以通过将
default
替换为
case(.Name,):return false;case(.Number,):return false
。更好的方法是:
case(.Name(let a),.Name(let b)):返回a==b
等。使用where子句,在每个
false
都达到默认值之前,不会继续测试每个案例吗?这可能很简单,但在某些系统中,这类事情可能会累加起来。要使其工作,必须在全局范围内实现
枚举
=
函数(不在视图控制器的范围内)。对于具有两个参数的开关来说,这是一个完美的位置。请参见上文,如何在每种情况下只需要一行代码。对于两个不相等的数字,您的代码都会失败。我同意,对于单元测试,这是一个不错的解决方案。init(stringInterpolationSegment:)上的Apple文档说:“不要直接调用此初始值设定项。编译器在解释字符串插值时会使用它。”。只需使用
“\(lhs)”==“\(rhs)”
。您也可以使用
字符串(描述:…)
或等效的
“\(…)”
。但这并不适用于