Mockito eşleştiriciler nasıl çalışır?


122

Mockito bağımsız değişken matchers (örneğin any, argThat, eq, same, ve ArgumentCaptor.capture()) hamcrest matchers çok farklı davranır.

  • Mockito eşleştiricileri, herhangi bir eşleştirici kullanıldıktan sonra uzun süre çalıştırılan kodda bile sıklıkla InvalidUseOfMatchersException'a neden olur.

  • Mockito eşleştiriciler, belirli bir yöntemdeki bir bağımsız değişken bir eşleştirici kullanıyorsa, yalnızca tüm bağımsız değişkenler için Mockito eşleştiricilerinin kullanılmasını gerektiren garip kurallara tabidir.

  • Mockito eşleştiriciler, Answers geçersiz kılarken veya (Integer) any()vb. Kullanırken NullPointerException'a neden olabilir .

  • Mockito eşleştiricileriyle kodun belirli şekillerde yeniden düzenlenmesi, istisnalar ve beklenmeyen davranışlar üretebilir ve tamamen başarısız olabilir.

Mockito eşleştiricileri neden böyle tasarlandı ve nasıl uygulanıyor?

Yanıtlar:


236

Mockito matchers statik yöntemler ve bu yöntemlere aramalar argümanlar için durmak için aramalar sırasında whenve verify.

Hamcrest eşleştiricileri (arşivlenmiş sürüm) (veya Hamcrest tarzı eşleştiriciler), nesne Eşleştiricinin ölçütleriyle eşleşirse doğru döndüren Matcher<T>bir yöntemi uygulayan ve ortaya çıkaran durumsuz, genel amaçlı nesne örnekleridir matches(T). Yan etkilerden arındırılmış olmaları amaçlanmıştır ve genellikle aşağıdaki gibi iddialarda kullanılırlar.

/* Mockito */  verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));

Mockito eşleştiriciler, Hamcrest tarzı eşleştiricilerden ayrı olarak mevcuttur, böylece eşleşen ifadelerin açıklamaları doğrudan yöntem çağrılarına sığar : Mockito eşleştiricileri T, Hamcrest eşleme yöntemlerinin Eşleştirici nesnelerini (türden Matcher<T>) döndürdüğü yerde geri döner .

Mockito matchers gibi statik yöntemlerle çağrılır eq, any, gtve startsWithüzerinde org.mockito.Matchersve org.mockito.AdditionalMatchers. Mockito sürümlerinde değişen adaptörler de vardır:

  • Mockito 1.x için, Matchersöne çıkan bazı aramalar ( intThatveya gibi argThat), Hamcrest eşleştiricilerini doğrudan parametre olarak kabul eden Mockito eşleştiricileridir. ArgumentMatcher<T>uzatılmış org.hamcrest.Matcher<T>iç hamcrest temsil kullanılır ve bir oldu olan hamcrest eşleştirici temel sınıf yerine mockito Eşleştirici herhangi bir tür.
  • Mockito 2.0+ için Mockito artık Hamcrest'e doğrudan bağımlı değil. artık uygulanmayan ancak benzer şekillerde kullanılan nesneler Matchersolarak ifade edilen intThatveya argThatsarmalayan çağrılar . Gibi hamcrest adaptörler ve hala mevcuttur, ancak taşındı yerine.ArgumentMatcher<T>org.hamcrest.Matcher<T>argThatintThatMockitoHamcrest

Eşleştiricilerin Hamcrest veya sadece Hamcrest tarzı olmasına bakılmaksızın, şu şekilde uyarlanabilirler:

/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));

Yukarıdaki ifadede: foo.setPowerLevelkabul eden bir yöntemdir int. is(greaterThan(9000))Bir döndüren Matcher<Integer>bir şekilde hangi olmaz iş, setPowerLevelargüman. Mockito eşleştirici intThathamcrest tarzı eşleştirici ve iadeler bir o sarar into kadar olabilir bağımsız değişken olarak görünür; Mockito eşleştiricileri gt(9000), örnek kodun ilk satırında olduğu gibi , tüm ifadeyi tek bir çağrıya sarar.

Eşleştiriciler ne yapar / ne yapar?

when(foo.quux(3, 5)).thenReturn(true);

Argüman eşleştiriciler kullanılmadığında, Mockito argüman değerlerinizi kaydeder ve bunları equalsyöntemleriyle karşılaştırır .

when(foo.quux(eq(3), eq(5))).thenReturn(true);    // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different

Bir eşleştiriciyi anyveya gt(daha büyük) olarak çağırdığınızda, Mockito, Mockito'nun bu eşitlik kontrolünü atlamasına ve seçtiğiniz eşleşmeyi uygulamasına neden olan bir eşleştirme nesnesi depolar. Bu durumda, argumentCaptor.capture()daha sonraki inceleme için argümanını kaydeden bir eşleştirici depolar.

Eşleyiciler geri kukla değerleri , örneğin, sıfır, boş koleksiyonları ya da null. Mockito, 0 için anyInt()veya any(Integer.class)boş bir List<String>için gibi güvenli, uygun bir kukla değer döndürmeye çalışır anyListOf(String.class). Tip silme yüzünden olsa da, mockito herhangi bir değer ama geri dönmek için tür bilgileri yoksun nulliçin any()veya argThat(...)"otomatik Unbox" bir çalışan eğer bir NullPointerException neden olabilir, nullilkel bir değer.

Eşleştiriciler parametre değerlerini sever eqve gtalır; ideal olarak, bu değerler stubbing / doğrulama başlamadan önce hesaplanmalıdır. Başka bir aramayla alay ederken bir sahte arama yapmak, saplama işlemini engelleyebilir.

Eşleştirme yöntemleri dönüş değerleri olarak kullanılamaz; ifadesine yolu yoktur thenReturn(anyInt())veya thenReturn(any(Foo.class))Mockito örneğin,. Mockito'nun, stubbing çağrılarında hangi örneğin döneceğini tam olarak bilmesi gerekir ve sizin için keyfi bir dönüş değeri seçmez.

Uygulama ayrıntıları

Eşleştiriciler, ArgumentMatcherStorage adlı bir sınıfta bulunan bir yığın içinde saklanır (Hamcrest tarzı nesne eşleştiriciler olarak) . MockitoCore ve Matchers'ın her biri , statik olarak bir ThreadLocal tutan MockingProgress örneklerini içeren bir ThreadSafeMockingProgress örneğine sahiptir . Somut bir ArgumentMatcherStorageImpl içeren bu MockingProgressImpl'dir . Sonuç olarak, sahte ve eşleştirici durumu statiktir, ancak Mockito ve Matchers sınıfları arasında tutarlı bir şekilde iş parçacığı kapsamına sahiptir.

Çoğu eşleştirici aramalar sadece gibi matchers için bir istisna ile, bu yığınına eklemek and, orvenot . Bu , bir yöntemi çağırmadan önce soldan sağa argümanları değerlendiren Java'nın değerlendirme sırasına mükemmel bir şekilde karşılık gelir (ve buna dayanır) :

when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6]      [5]  [1]       [4] [2]     [3]

Bu irade:

  1. anyInt()Yığına ekleyin .
  2. gt(10)Yığına ekleyin .
  3. lt(20)Yığına ekleyin .
  4. Kaldır gt(10)ve lt(20)ekle and(gt(10), lt(20)).
  5. Çağrı foo.quux(0, 0), (aksi belirtilmedikçe) varsayılan değeri döndürür false. Mockito dahili quux(int, int)olarak en son çağrı olarak işaretler .
  6. when(false)Çağrının argümanını atan ve 5'te quux(int, int)tanımlanan saplama yöntemine hazırlayan çağrı , yalnızca iki geçerli durum yığın uzunluğu 0 (eşitlik) veya 2 (eşleştiriciler) ve yığın üzerinde iki eşleştirici vardır (adım 1 ve 4), bu nedenle Mockito, yöntemi any()ilk argümanı ve and(gt(10), lt(20))ikinci argümanı için bir eşleştirici ile saplar ve yığını temizler.

Bu, birkaç kuralı gösterir:

  • Mockito quux(anyInt(), 0)ve arasındaki farkı söyleyemez quux(0, anyInt()). İkisi de quux(0, 0)yığında bir int eşleştiriciye sahip bir çağrı gibi görünüyor . Sonuç olarak, bir eşleştirici kullanırsanız, tüm bağımsız değişkenleri eşleştirmeniz gerekir.

  • Çağrı emri sadece önemli değil, tüm bunların yürümesini sağlayan şey . Eşleştiricileri değişkenlere çıkarmak genellikle işe yaramaz, çünkü genellikle arama sırasını değiştirir. Ancak eşleştiricileri yöntemlere çıkarmak harika çalışıyor.

    int between10And20 = and(gt(10), lt(20));
    /* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
    // Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
    
    public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
    /* OK */  when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
    // The helper method calls the matcher methods in the right order.
  • Yığın, Mockito'nun onu çok dikkatli bir şekilde denetleyemeyeceği kadar sık ​​değişir. Yığını yalnızca Mockito veya bir taklit ile etkileşime girdiğinizde kontrol edebilir ve hemen kullanıldıklarını veya yanlışlıkla terk edildiklerini bilmeden eşleştiricileri kabul etmesi gerekir. Teorik olarak, yığın, bir whenveya çağrısının dışında her zaman boş olmalıdır verify, ancak Mockito bunu otomatik olarak kontrol edemez. İle manuel olarak kontrol edebilirsiniz Mockito.validateMockitoUsage().

  • Bir çağrıda when, Mockito aslında söz konusu yöntemi çağırır; bu, yöntemi bir istisna atacak şekilde (veya sıfır olmayan veya boş olmayan değerler gerektirdiğinde) bir istisna atar. doReturnve doAnswer(vs) yok olup , gerçek bir yöntem çağırmak ve genellikle faydalı bir alternatiftir.

  • Stubbing'in ortasında bir sahte yöntem çağırmış olsaydınız (örneğin, bir eqeşleştirici için bir cevabı hesaplamak için ), Mockito bunun yerine yığın uzunluğunu bu çağrıya göre kontrol eder ve muhtemelen başarısız olur.

  • Son bir yöntemi saplamak / doğrulamak gibi kötü bir şey yapmaya çalışırsanız , Mockito gerçek yöntemi çağıracak ve yığında fazladan eşleştiriciler bırakacaktır . finalYöntem çağrısı bir özel durum olmayabilir, ama bir alabilirsiniz InvalidUseOfMatchersException mock ile başıboş matchers sonraki etkileşim dan.

Yaygın sorunlar

  • InvalidUseOfMatchersException :

    • Eşleştiriciler kullanıyorsanız, her bağımsız değişkenin tam olarak bir eşleştirici çağrısı olup olmadığını ve bir arama whenveya verifyçağrı dışında eşleştirici kullanmadığınızı kontrol edin . Eşleştiriciler asla stubed dönüş değerleri veya alanlar / değişkenler olarak kullanılmamalıdır.

    • Bir eşleştirme argümanı sağlamanın bir parçası olarak sahte çağırmadığınızı kontrol edin.

    • Bir eşleştirici ile son bir yöntemi saplamaya / doğrulamaya çalışmadığınızı kontrol edin. Bu, yığın üzerinde bir eşleştirici bırakmanın harika bir yoludur ve son yönteminiz bir istisna oluşturmadıkça, alay ettiğiniz yöntemin nihai olduğunu anladığınız tek zaman bu olabilir.

  • İlkel bağımsız değişkenlerle NullPointerException: 0 (Integer) any()döndürürken null any(Integer.class)döndürür; Tamsayı yerine bir NullPointerExceptionbekliyorsanız, bu a neden olabilir int. Her durumda, anyInt()sıfıra dönecek ve ayrıca otomatik kutulama adımını atlayacak olanı tercih edin.

  • NullPointerException veya diğer istisnalar: Çağrılar when(foo.bar(any())).thenReturn(baz)gerçekte çağırır foo.bar(null) ; bu, boş bir bağımsız değişken alırken bir istisna atmaya başlamış olabilirsiniz. doReturn(baz).when(foo).bar(any()) Öğesine geçmek , stublanmış davranışı atlar .

Genel sorun giderme

  • MockitoJUnitRunner'ı kullanın veya validateMockitoUsagesizin tearDownveya @Afteryönteminizi açıkça çağırın (koşucunun sizin için otomatik olarak yapacağı). Bu, eşleştiricileri kötüye kullanıp kullanmadığınızı belirlemenize yardımcı olacaktır.

  • Hata ayıklama amacıyla, validateMockitoUsagekodunuza doğrudan çağrı ekleyin . Bu, yığında herhangi bir şey varsa atacaktır, bu kötü bir semptom için iyi bir uyarıdır.


2
Bu yazı için teşekkürler. When / thenReturn formatına sahip bir NullPointerException, doReturn / when olarak değiştirene kadar bana sorunlara neden oluyordu.
yngwietiger

11

Kendi sorunlarımdan birine çözüm ararken bu soruyu bulduğum için Jeff Bowman'ın mükemmel cevabına sadece küçük bir katkı:

Bir yönteme yapılan bir çağrı, birden fazla sahte wheneğitimli çağrıyla eşleşiyorsa , whençağrıların sırası önemlidir ve en genişten en özele doğru olmalıdır. Jeff'in örneklerinden birinden başlayarak:

when(foo.quux(anyInt(), anyInt())).thenReturn(true);
when(foo.quux(anyInt(), eq(5))).thenReturn(false);

(muhtemelen) istenen sonucu sağlayan sıradır:

foo.quux(3 /*any int*/, 8 /*any other int than 5*/) //returns true
foo.quux(2 /*any int*/, 5) //returns false

When çağrılarını tersine çevirirseniz, sonuç her zaman olacaktır true.


2
Bu yararlı bir bilgi olsa da , eşleştiricileri değil , saplama ile ilgilidir , bu nedenle bu soruda bir anlam ifade etmeyebilir. Sıra önemlidir, ancak yalnızca son tanımlanan eşleme zinciri kazanır : Bu, birlikte var olan koçanların genellikle en az özelden en azına özel olarak bildirildiği anlamına gelir, ancak bazı durumlarda tek bir test durumunda özel olarak alay edilen davranışın çok geniş bir şekilde geçersiz kılınmasını isteyebilirsiniz. , bu noktada geniş bir tanımın en son gelmesi gerekebilir.
Jeff Bowman

1
@JeffBowman Ben bu sorunun mantıklı olduğunu düşündüm, çünkü soru mockito eşleştiricilerle ilgili ve eşleştiriciler stubing sırasında kullanılabilir (örneklerinizin çoğunda olduğu gibi). Google'da bir açıklama aramak beni bu soruya getirdiğinden, bu bilgiye burada sahip olmanın yararlı olduğunu düşünüyorum.
tibtof
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.