Swift numaralandırmalarının ilişkili değerlerle eşitliği nasıl test edilir


193

İki Swift enum değerinin eşitliğini test etmek istiyorum. Örneğin:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

Ancak, derleyici eşitlik ifadesini derlemez:

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

Eşitlik operatörünün kendi aşırı yüklenmesini tanımladım mı? Swift derleyicisinin Scala ve Ocaml gibi otomatik olarak ele almasını umuyordum.


1
Açılmış rdar: // 17408414 ( openradar.me/radar?id=6404186140835840 ).
Jay Lieske

1
Swift 4.1'den SE-0185 nedeniyle Swift, sentezlemeyi Equatableve Hashableilişkili değerlere sahip numaralandırmalar için de destekliyor .
jedwidz

Yanıtlar:


245

Hızlı 4.1+

Olarak @jedwidz yardımseverlikle Swift 4,1 (nedeniyle, işaret eden SE-0185 , Swift de sentezleme destekler Equatableve Hashableilişkili değerlerin enums için.

Swift 4.1 veya daha yeni bir sürüm kullanıyorsanız, aşağıdakiler otomatik olarak çalışacak şekilde gerekli yöntemleri sentezleyecektir XCTAssert(t1 == t2). Anahtar, Equatableprotokolü numaralandırmanıza eklemektir .

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

Swift 4.1 öncesi

Diğerlerinin de belirttiği gibi Swift, gerekli eşitlik operatörlerini otomatik olarak sentezlemiyor. Yine de daha temiz (IMHO) bir uygulama önereyim:

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

İdeal olmaktan uzak - çok fazla tekrar var - ama en azından içeride if ifadeleriyle iç içe anahtarlar yapmanız gerekmez.


39
Bu konuda berbat olan şey, anahtarda varsayılan ifadeyi kullanmanız gerektiğidir, bu nedenle yeni bir numaralandırma durumu eklerseniz, derleyici bu yeni durumu eşitlik için karşılaştırmak için maddeyi eklediğinizden emin olmaz. daha sonra çizgide değişiklikler yaptığınızda hatırlamanız ve dikkatli olmanız yeterlidir!
Michael Waterfall

20
@MichaelWaterfall defaultile değiştirerek belirtilen sorundan kurtulabilirsiniz case (.Name, _): return false; case(.Number, _): return false.
Kazmasaurus

25
Daha iyi: case (.Name(let a), .Name(let b)) : return a == bvb.
Martin R

1
Where yan tümcesinde, her bir dava, her biri için varsayılana kadar test edilmeye devam etmez falsemi? Önemsiz olabilir, ancak bu tür şeyler bazı sistemlerde toplanabilir.
Christopher Swasey

1
Bu hem çalışması için enumve ==işlevi (görünümünüzü denetleyicisi kapsamı dışında) küresel kapsamına uygulanması gerekmektedir.
Andrej

77

Uygulama Equatableaşırı bir IMHO'dur. Birçok durumda ve birçok farklı parametreyle karmaşık ve büyük numaralandırma yaptığınızı düşünün. Bu parametrelerin de Equatableuygulanması gerekir . Ayrıca, enum vakalarını ya hep ya hiç olarak karşılaştırdığınızı kim söyledi? Değeri test ediyorsanız ve yalnızca belirli bir enum parametresini sapladıysanız ne olur? Basit bir yaklaşım öneririm, örneğin:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... veya parametre değerlendirmesi durumunda:

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

Daha ayrıntılı açıklama burada bulabilirsiniz: https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/


Test amaçlı değil, bunu kullanmaya çalışırken daha eksiksiz bir örnek verebilir misiniz?
teradyl

Burada sorunun ne olduğundan emin değilim. if caseve guard casebasitçe dil yapılarıdır, bunları yalnızca Birim Testlerinde değil, bu durumda numaralandırma eşitliğini test ederken her yerde kullanabilirsiniz.
mbpro

3
Teknik olarak bu cevap soruyu cevaplamasa da, aslında birçok kişinin arama yoluyla buraya gelmesini sağladığını, başlamak için yanlış soru sorduklarını anlıyorum. Teşekkürler!
Nikolay Suvandzhiev

15

Ne derlemeler ne de yapılar için derleyici oluşturulan eşitlik operatörü var gibi görünüyor.

“Örneğin, karmaşık bir veri modelini temsil etmek için kendi sınıfınızı veya yapınızı oluşturursanız, o sınıf veya yapı için“ eşit ”ifadesi Swift'in sizin için tahmin edebileceği bir şey değildir.” [1]

Eşitlik karşılaştırmasını uygulamak için kişi şöyle bir şey yazacaktır:

@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] Bkz. "Eşdeğerlik Operatörleri", https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43


14

İşte başka bir seçenek. if caseSözdizimini kullanarak iç içe anahtar ifadelerinden kaçınması dışında esas olarak diğerleriyle aynıdır . Bu biraz daha okunabilir (/ katlanılabilir) yapar ve varsayılan davadan tamamen kaçınmanın avantajına sahip olduğunu düşünüyorum.

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

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

Bu aynı zamanda case (.Simple(let v0), .Simple(let v1)) operatör gibi staticnumaralandırma ile de yazılabilir . Cevabımı burada görün.
LShi

11

Birim test kodunda bu basit geçici çözümü kullanıyorum:

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

Karşılaştırmayı gerçekleştirmek için dize enterpolasyonu kullanır. Üretim kodu için tavsiye etmem, ama özlü ve birim testi için iş yapıyor.


2
Katılıyorum, birim testi için bu iyi bir çözüm.
Daniel Wood

İnit'teki Apple belgeleri (stringInterpolationSegment :) diyor ki: "Bu başlatıcıyı doğrudan çağırma. Dize enterpolasyonlarını yorumlarken derleyici tarafından kullanılır.". Sadece kullan "\(lhs)" == "\(rhs)".
skagedal

Veya String(describing:...)eşdeğerini de kullanabilirsiniz "\(...)". Ancak, ilişkili değerler farklıysa bu işe yaramaz :(
Martin

10

Başka bir seçenek, vakaların dize temsillerini karşılaştırmak olacaktır:

XCTAssert(String(t1) == String(t2))

Örneğin:

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

3

if caseSwift 3'te çalışan virgülle kullanılan başka bir yaklaşım :

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

Projemde böyle yazdım. Ama fikri nereden bulduğumu hatırlayamıyorum. (Ben şimdi googled ama böyle bir kullanım görmedim.) Herhangi bir yorum mutluluk duyacağız.


2

t1 ve t2 sayı değil, ilişkili değerlere sahip SimpleTokens örnekleridir.

Söyleyebilirsin

var t1 = SimpleToken.Number(123)

Sonra söyleyebilirsin

t1 = SimpleToken.Name(Smith) 

derleyici hatası olmadan.

Değeri t1'den almak için bir switch deyimi kullanın:

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

2

kabul edilen cevapla karşılaştırıldığında 'avantaj', 'ana' anahtar ifadesinde 'varsayılan' bir durum olmamasıdır, bu nedenle numaralandırmanızı diğer durumlarda genişletirseniz, derleyici sizi kodun geri kalanını güncellemeye zorlar.

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

2

Mbpro'nun cevabına genişleyerek, bu yaklaşımın, bazı kenar durumlarıyla ilişkili değerlerle hızlı numaralandırmaların eşitliğini kontrol etmek için nasıl kullandım.

Tabii ki bir anahtar ifadesi yapabilirsiniz, ancak bazen bir satırda bir değer olup olmadığını kontrol etmek güzeldir. Bunu şöyle yapabilirsiniz:

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

Aynı koşulda 2 koşulu karşılaştırmak istiyorsanız, &&işleç yerine virgül kullanmanız gerekir :

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

2

Swift 4.1'den Equatableenumunuza protokol ekleyin ve XCTAssertveya kullanın XCTAssertEqual:

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

-1

Anahtarı kullanarak karşılaştırabilirsiniz

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

İki argümanlı bir anahtar için mükemmel bir yer. Bunun, vaka başına nasıl yalnızca bir kod satırı aldığını görün. Ve kodunuz eşit olmayan iki sayı için başarısız olur.
06:24
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.