【问题标题】:How to test equality of Swift enums with associated values如何测试 Swift 枚举与关联值的相等性
【发布时间】:2014-06-21 08:31:44
【问题描述】:

我想测试两个 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 一样。

【问题讨论】:

标签: swift


【解决方案1】:

斯威夫特 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 语句进行嵌套切换。

【讨论】:

  • 最糟糕的是你需要在 switch 中使用 default 语句,所以如果你添加一个新的枚举 case,编译器不会确保你添加子句来比较它新的平等案例——你只需要记住并在以后进行更改时小心!
  • 您可以通过将default 替换为case (.Name, _): return false; case(.Number, _): return false 来摆脱@MichaelWaterfall 提到的问题。
  • 更好:case (.Name(let a), .Name(let b)) : return a == b
  • 使用 where 子句,在每个false 都达到默认值之前,不是每个案例都会继续测试吗?这可能是微不足道的,但这种事情可以在某些系统中加起来。
  • 为了使 enum== 函数能够工作,必须在全局范围内(在视图控制器范围之外)实现。
【解决方案2】:

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

【讨论】:

  • 当你试图在测试基础上使用它时,你能举一个更完整的例子吗?
  • 我不确定这里的问题是什么。 if caseguard case 只是语言结构,在这种情况下,您可以在测试枚举相等性时在任何地方使用它们,而不仅仅是在单元测试中。
  • 虽然从技术上讲,这个答案并不能回答问题,但我怀疑它实际上会让很多人通过搜索到达这里,意识到他们一开始就问错了问题。谢谢!
【解决方案3】:
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
    }
}

【讨论】:

  • 这也可以用case (.Simple(let v0), .Simple(let v1)) 之类的东西来写,运算符也可以是枚举中的static。在这里查看我的答案。
【解决方案4】:

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

“例如,如果您创建自己的类或结构来表示复杂的数据模型,那么 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 上的“等价运算符”

【讨论】:

    【解决方案5】:

    这是另一种选择。除了使用if case 语法避免嵌套的switch 语句外,它与其他基本相同。我认为这使它更具可读性(/bearable),并且具有完全避免默认情况的优点。

    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
    

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

      • 我同意,对于单元测试,这是一个不错的解决方案。
      • Apple docs on init(stringInterpolationSegment:) 说:“不要直接调用这个初始化器。编译器在解释字符串插值时使用它。”。只需使用"\(lhs)" == "\(rhs)"
      • 您也可以使用String(describing:...) 或等效的"\(...)"。但是,如果关联的值不同,这将不起作用:(
      【解决方案7】:

      另一种选择是比较案例的字符串表示:

      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
      

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              与接受的答案相比,“优势”是“主”开关语句中没有“默认”情况,因此如果您使用其他情况扩展您的枚举,编译器将强制您更新其余代码.

              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
              

              【讨论】:

                【解决方案12】:

                从 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
                

                【讨论】:

                  【解决方案13】:

                  你可以用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")
                  }
                  

                  【讨论】:

                  • 带有两个参数的开关的完美位置。见上文,每个案例只需要一行代码。并且您的代码因两个不相等的数字而失败。
                  猜你喜欢
                  • 2018-12-20
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-02-01
                  • 1970-01-01
                  • 2016-06-09
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-11-01
                  相关资源
                  最近更新 更多