如何测试Swift枚举与关联值的相等性
我想测试两个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
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)”
。您也可以使用字符串(描述:…)
或等效的“\(…)”
。但这并不适用于