Bu Mockito'nun sıfırlama yönteminin uygun bir kullanımı mıdır?


68

Test sınıfımda yaygın olarak kullanılan bir Barnesne oluşturan özel bir yöntemim var . BarYapıcı çağırır someMethod()benim alay nesnede yöntem:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

Bazı test yöntemlerinde someMethodde kontrol etmek istediğim o test tarafından çağrıldım. Aşağıdaki gibi bir şey:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

Bu başarısız olur, çünkü alay konusu nesne someMethodiki kez çağrıldı. Test metotlarımın metotumun yan etkilerini önemsemesini istemiyorum getBar(), bu yüzden sahte nesnemi sonunda sıfırlamak mantıklı olur getBar()mu?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

Soruyorum, çünkü belgeler sahte nesneleri sıfırlamanın genellikle kötü testlerin göstergesi olduğunu gösteriyor. Ancak, bu bana iyi geliyor.

Alternatif

Alternatif seçim arıyor gibi görünüyor:

verify(mockedObject, times(2)).someMethod();

Kanımca, her testi getBar(), kazançlar için beklentileri hakkında bilmeye zorlar .

Yanıtlar:


60

Bunun reset()kullanımın iyi olduğu durumlardan biri olduğuna inanıyorum . Yazdığınız sınama, "bazı şeylerin" tek bir çağrıyı tetiklediğini sınamaktır someMethod(). İfadenin verify()herhangi bir sayıda istila ile yazılması karışıklığa neden olabilir.

  • atLeastOnce() yanlış pozitiflere izin verir, bu da testlerinizin her zaman doğru olmasını istediğiniz gibi kötü bir şeydir.
  • times(2)yanlış pozitifleri engeller, ancak "yapıcı bir tane ekler biliyorum" demekten ziyade iki çağrı beklemiş gibisiniz. Dahası, ek bir çağrı eklemek için kurucuda bir şey değişirse, testin artık yanlış bir pozitif şansı vardır. Ve aramanın kaldırılması testin başarısız olmasına neden olur çünkü test edilen testin yanlış olması yerine test artık yanlış.

Kullanarak reset()yardımcı yönteminde, bu sorunların ikisini kaçının. Bununla birlikte, yaptığınız saplamaları da sıfırlayacağına dikkat etmeniz gerekir, bu yüzden uyarın. reset()Cesaret kırılmasının ana sebebi önlemektir.

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

OP'nin yapmaya çalıştığı şey bu değil. Varsayıyorum, OP kurucunun istila olduğunu doğrulayan bir sınama var. Bu test için, sıfırlama bu tek eylemin izole edilmesine izin verir ve etkisi. Bu birkaç davadan biri reset()olarak yardımcı olabilir. Hepsini kullanmayan diğer seçeneklerin eksileri vardır. OP'nin bu görevde bulunması gerçeği, sadece sıfırlama yöntemini kör kullanmadan değil, durumu düşündüğünü gösteriyor.


17
Mockito'ya sadece doğrulama amacıyla geçmiş etkileşimleri unutmak (..., times (...)) ve saplamayı tutmak için resetInteractions () çağrısı yaptım. Bu, {setup; davranmak; doğrulamak;} başa çıkmak daha kolay. Bu, {setup; resetInteractions; davranmak; doğrula}
Arkadiy

2
Aslında Mockito 2.1'den beri, taslakları sıfırlamadan yapılan çağrıları temizlemek için bir yol sunar:Mockito.clearInvocations(T... mocks)
Colin D Bennett

6

Akıllı Mockito kullanıcıları sıfırlama özelliğini pek kullanmazlar çünkü kötü testlerin işareti olabileceğini bilirler. Normalde, alaylarınızı sıfırlamanız gerekmez, her test yöntemi için yeni alaylar oluşturun.

reset()Lütfen bunun yerine, uzun, fazla belirtilmiş testler üzerinde basit, küçük ve odaklanmış test yöntemleri yazmayı düşünün. İlk potansiyel kod kokusu reset()test yönteminin ortasındadır.

Çıkarılan Mockito docs .

Tavsiyem, kullanmaktan kaçınmaya çalışmanızdır reset(). Bence, bazı Metodları iki kez ararsanız, test edilmelidir (belki bir veritabanı erişimi veya ilgilenmek istediğiniz diğer uzun bir süreçtir).

Bunu gerçekten umursamıyorsanız, şunları kullanabilirsiniz:

verify(mockedObject, atLeastOnce()).someMethod();

Unutmayın ki bu, getBar'dan someMethod işlevini çağırırsanız ve sonrasında değil (bu yanlış bir davranış, ancak test başarısız olmayacaktır) yanlış bir sonuca neden olabileceğini unutmayın.


2
Evet, bu kesin alıntıyı gördüm (sorumu ona bağladım). Şu anda, yukarıdaki örneğimin neden "kötü" olduğu konusunda makul bir argüman henüz görmedim. Bir tane tedarik edebilir misiniz?
Duncan Jones

Sahte nesnelerinizi sıfırlamanız gerekirse, testinizde çok fazla şeyi test etmeye çalışıyorsunuz gibi görünüyor. İki teste bölerek daha küçük şeyleri test edebilirsiniz. Her durumda, neden getBar yönteminde doğrulama yaptığınızı bilmiyorum, test ettiğinizi izlemek zor. Test düşüncenizi sınıfınızın ne yapması gerektiği konusunda tasarlamanızı (bazı Metod'u tam olarak iki kez, en az bir kez, sadece bir kez, hiçbir zaman vb. Çağırmanız gerekiyorsa) tasarlamanızı ve her doğrulamayı aynı yerde yapmanızı öneririm.
greuze

verifyÖzel yöntemimi aramasam bile (ki, muhtemelen oraya ait değilsin) sorunun devam ettiğini vurgulamak için sorumu düzenledim . Cevabınızın değişip değişmeyeceği konusundaki yorumlarınızı bekliyoruz.
Duncan Jones

Sıfırlamayı kullanmak için pek çok iyi neden var, bu durumda bu mockito alıntılarına çok fazla dikkat etmem. Bir test paketi çalıştırırken Spring'in JUnit Class Runner'ı istenmeyen etkileşimlere neden olabilir, özellikle de sahte veritabanı çağrıları içeren testler yapıyorsanız veya yansıması kullanmak istemediğiniz özel yöntemleri içeren çağrılar yapıyorsanız.
Sandy Simonton

Birden çok şeyi test etmek istediğimde genellikle bunu zor buluyorum, ancak JUnit testleri parametrelemek için hiçbir güzel (!) Yol sunmuyor. NUnit'in aksine örneğin ek açıklamalar yapar.
Stefan Hendriks

3

Kesinlikle hayır. Sık sık olduğu gibi, temiz bir test yazarken yaşadığınız zorluk, üretim kodunuzun tasarımı ile ilgili büyük bir kırmızı bayraktır. Bu durumda, en iyi çözüm, Bar'ın kurucusunun herhangi bir yöntem çağırmaması için kodunuzu yeniden gözden geçirmektir.

Yapıcılar, mantık yürütmemeli, inşa etmelidir. Yöntemin döndürdüğü değeri al ve yapıcı parametresi olarak ilet.

new Bar(mockedObject);

dönüşür:

new Bar(mockedObject.someMethod());

Bu, bu mantığı birçok yerde çoğaltmayla sonuçlanırsa, Bar nesnenizden bağımsız olarak test edilebilen bir fabrika yöntemi oluşturmayı düşünün:

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

Bu yeniden yapılandırma işlemi çok zorsa, reset () işlevinin kullanılması iyi bir iştir. Ancak açık olalım - bu, kodunuzun kötü bir şekilde tasarlandığını gösteriyor.

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.