Mockito kullanarak bir sınıfın üye değişkenlerini alay etme


136

Özellikle geliştirme ve birim testleri konusunda acemiyim. Sanırım ihtiyacım oldukça basit, ancak başkalarının bu konudaki düşüncelerini bilmek isterim.

Bunun gibi iki sınıfım olduğunu varsayalım -

public class First {

    Second second ;

    public First(){
        second = new Second();
    }

    public String doSecond(){
        return second.doSecond();
    }
}

class Second {

    public String doSecond(){
        return "Do Something";
    }
}

Diyelim ki test First.doSecond()yöntemine birim testi yazıyorum . Ancak, farz edin ki, ben Second.doSecond()de bu şekilde Mock sınıfı yapmak istiyorum. Bunu yapmak için Mockito kullanıyorum.

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

Alay etmenin etkili olmadığını ve iddianın başarısız olduğunu görüyorum. Test etmek istediğim bir sınıfın üye değişkenleriyle alay etmenin bir yolu yok mu? ?

Yanıtlar:


86

Üye değişkenlere erişmenin bir yolunu sağlamanız gerekir, böylece bir taklidi iletebilirsiniz (en yaygın yöntemler bir ayarlayıcı yöntem veya bir parametre alan bir kurucu olacaktır).

Kodunuz bunu yapmanın bir yolunu sunmuyorsa, TDD (Test Driven Development) için yanlış bir şekilde hesaba katılır.


4
Teşekkürler. Anladim. Merak ediyorum, daha sonra birçok dahili yöntemin, alay edilmesi gereken sınıfların, ancak önceden bir setXXX () aracılığıyla ayarlanması gerekmeyen sınıfların olduğu durumlarda, alay kullanarak entegrasyon testlerini nasıl gerçekleştirebilirim.
Anand Hemmige

2
Test yapılandırmasıyla bir bağımlılık enjeksiyon çerçevesi kullanın. Yapmaya çalıştığınız entegrasyon testinin bir dizi diyagramını çizin. Sekans diyagramını gerçekten kontrol edebileceğiniz nesnelere çarpın. Bu, yukarıda gösterdiğiniz bağımlı nesne anti-modeline sahip bir çerçeve sınıfıyla çalışıyorsanız, nesneyi ve onun kötü faktörlü üyesini sıra diyagramı açısından tek bir birim olarak görmeniz gerektiği anlamına gelir. Daha test edilebilir hale getirmek için kontrol ettiğiniz herhangi bir kodun faktörlemesini ayarlamaya hazır olun.
kittylyst

9
Sevgili @kittylyst, evet muhtemelen TDD açısından veya herhangi bir rasyonel bakış açısından yanlış. Ancak bazen bir geliştirici, hiçbir şeyin mantıklı olmadığı yerlerde çalışır ve sahip olduğu tek hedef, atadığınız hikayeleri tamamlayıp uzaklaşmaktır. Evet, yanlış, mantıklı değil, vasıfsız insanlar kilit kararları alıyor ve her şeyi. Yani, günün sonunda, anti-paternler çok şey kazanır.
amanas

1
Bunu merak ediyorum, eğer bir sınıf üyesinin dışarıdan belirlenmesi için bir nedeni yoksa, neden sadece onu test etmek için bir pasör yaratalım? Buradaki 'İkinci' sınıfın aslında test edilecek nesnenin oluşturulması sırasında başlatılan bir Dosya Sistemi yöneticisi veya aracı olduğunu hayal edin. Birinci sınıfı test etmek için bu FileSystem yöneticisiyle dalga geçmek ve onu erişilebilir kılmak için sıfır nedenim var. Bunu Python'da yapabilirim, öyleyse neden Mockito ile olmasın?
Zangdar

66

Kodunuzu değiştiremezseniz bu mümkün değildir. Ama bağımlılık enjeksiyonunu seviyorum ve Mockito bunu destekliyor:

public class First {    
    @Resource
    Second second;

    public First() {
        second = new Second();
    }

    public String doSecond() {
        return second.doSecond();
    }
}

Testiniz:

@RunWith(MockitoJUnitRunner.class)
public class YourTest {
   @Mock
   Second second;

   @InjectMocks
   First first = new First();

   public void testFirst(){
      when(second.doSecond()).thenReturn("Stubbed Second");
      assertEquals("Stubbed Second", first.doSecond());
   }
}

Bu çok hoş ve kolaydır.


2
Bunun diğerlerinden daha iyi bir cevap olduğunu düşünüyorum çünkü InjectMocks.
sudocoder

Benim gibi yeni başlayan biri olarak belirli kitaplıklara ve çerçevelere güvenmek çok komik. Ben bunu bana gösterene kadar ... Bu sadece yeniden tasarlamak ihtiyacı olduğunu belirten Bad fikir oldu üstlendiğini olduğunu (çok net ve temiz bir şekilde) Mockito gerçekten de mümkün.
mike rodent

9
Nedir @Resource ?
IgorGanapolsky

3
@IgorGanapolsky @ Kaynak, Java Spring çerçevesi tarafından oluşturulan / kullanılan bir nottur. Bahara bunun Spring tarafından yönetilen bir fasulye / nesne olduğunu göstermenin bir yolu. stackoverflow.com/questions/4093504/resource-vs-autowired baeldung.com/spring-annotations-resource-inject-autowire Sahte bir şey değildir, ancak test dışı sınıfta kullanıldığı için Ölçek.
Grez.Kev

Bu cevabı anlamıyorum. Bunun mümkün olmadığını söylüyorsun, sonra mümkün olduğunu mu gösterdin? Burada tam olarak ne mümkün değil?
Goldname

35

Kodunuza yakından bakarsanız second, testinizdeki özelliğin Secondbir taklit değil , hala bir örneği olduğunu görürsünüz (taklidi firstkodunuzda iletmezsiniz ).

En basit yol second, Firstsınıf içi için bir ayarlayıcı oluşturmak ve onu açık bir şekilde taklit etmek olacaktır.

Bunun gibi:

public class First {

Second second ;

public First(){
    second = new Second();
}

public String doSecond(){
    return second.doSecond();
}

    public void setSecond(Second second) {
    this.second = second;
    }


}

class Second {

public String doSecond(){
    return "Do Something";
}
}

....

public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");


First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}

Bir diğeri, bir Secondörneği Firstyapıcı parametresi olarak iletmektir.

Kodu değiştiremiyorsanız, tek seçeneğin yansımayı kullanmak olacağını düşünüyorum:

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");


    First first = new First();
    Field privateField = PrivateObject.class.
        getDeclaredField("second");

    privateField.setAccessible(true);

    privateField.set(first, sec);

    assertEquals("Stubbed Second", first.doSecond());
}

Ancak, muhtemelen kontrol etmediğiniz kod üzerinde testler yapmak ender rastlanan bir durum olduğu için (yine de harici bir kitaplığı test etmeniz gereken bir senaryo hayal edebilirsiniz, çünkü yazarı bunu yapmadı :))


Anladım. Muhtemelen ilk önerinizle gideceğim.
Anand Hemmige

Sadece merak ediyorum, uygulama düzeyinde veya paket düzeyinde bir nesne / yöntemle dalga geçmenin farkında olduğunuz herhangi bir yol veya API var mı? ? Sanırım söylediğim şey, yukarıdaki örnekte 'İkinci' nesneyle dalga geçtiğimde, testin yaşam döngüsü boyunca kullanılan her Second örneğini geçersiz kılmanın bir yolu var mıdır. ?
Anand Hemmige

@AnandHemmige aslında ikinci olan (kurucu) daha temizdir, çünkü gereksiz "İkinci" örnekler oluşturmaktan kaçınır. Sınıflarınız bu şekilde güzelce ayrıştırılır.
soulcheck

10
Mockito, alaylarınızı özel değişkenlere enjekte etmenize izin veren bazı güzel ek açıklamalar sağlar. İkinciye @Mockek açıklama ekleyin ve İlk ile ilk açıklama ekleyin @InjectMocksve İlk olarak başlatıcıda ilk örneği oluşturun. Mockito, eşleme türüne sahip özel alanların ayarlanması da dahil olmak üzere, İkinci taklidi İlk örneğe enjekte etmek için bir yer bulmayı otomatik olarak yapacaktır.
jhericks

@Mock1.5 civarındaydı (belki daha erken, emin değilim). 1.8.3 tanıtılan @InjectMocksyanı sıra @Spyve @Captor.
jhericks

7

Üye değişkenini değiştiremezseniz, bunun diğer yolu powerMockit kullanmak ve

Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);

Şimdi sorun şu ki, yeni Second için HERHANGİ bir çağrı aynı alay konusu olayı döndürecektir. Ama basit durumda bu işe yarayacak.


6

Mockito süper kurucuları çağırmadığı için özel bir değerin ayarlanmadığı aynı sorunu yaşadım. İşte yansıma ile alay etmeyi nasıl artırıyorum.

İlk olarak, bu yansıma yöntemleri dahil olmak üzere birçok yararlı araç içeren bir TestUtils sınıfı oluşturdum. Yansıma erişiminin her seferinde uygulanması biraz risklidir. Bu yöntemleri, bir nedenden ötürü alay paketi olmayan projelerde kodu test etmek için oluşturdum ve onu dahil etmeye davet edilmedim.

public class TestUtils {
    // get a static class value
    public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(classToReflect, fieldNameValueToFetch);
            reflectField.setAccessible(true);
            Object reflectValue = reflectField.get(classToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // get an instance value
    public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
            Object reflectValue = reflectField.get(objToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // find a field in the class tree
    public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField = null;
            Class<?> classForReflect = classToReflect;
            do {
                try {
                    reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
                } catch (NoSuchFieldException e) {
                    classForReflect = classForReflect.getSuperclass();
                }
            } while (reflectField==null || classForReflect==null);
            reflectField.setAccessible(true);
            return reflectField;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
        }
        return null;
    }
    // set a value with no setter
    public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameToSet);
            reflectField.set(objToReflect, valueToSet);
        } catch (Exception e) {
            fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
        }
    }

}

Sonra sınıfı böyle bir özel değişkenle test edebilirim. Bu, sizin de kontrolünüz olmayan sınıf ağaçlarının derinlikleriyle alay etmek için kullanışlıdır.

@Test
public void testWithRectiveMock() throws Exception {
    // mock the base class using Mockito
    ClassToMock mock = Mockito.mock(ClassToMock.class);
    TestUtils.refectSetValue(mock, "privateVariable", "newValue");
    // and this does not prevent normal mocking
    Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
    // ... then do your asserts
}

Kodumu gerçek projemden burada, sayfada değiştirdim. Bir veya iki derleme sorunu olabilir. Sanırım genel fikri anladınız. Kodu almaktan ve yararlı bulursanız kullanmaktan çekinmeyin.


Kodunuzu gerçek bir kullanım durumuyla açıklayabilir misiniz? Genel sınıf tobeMocker () {özel ClassObject classObject; } Burada classObject değiştirilecek nesneye eşittir.
Jasper Lankhorst

Örneğinizde, ToBeMocker örneği = new ToBeMocker (); ve ClassObject someNewInstance = new ClassObject () {@Override // harici bağımlılık gibi bir şey}; sonra TestUtils.refelctSetValue (örnek, "classObject", someNewInstance); Alay etmek için neyi geçersiz kılmak istediğinizi bulmanız gerektiğini unutmayın. Diyelim ki bir veritabanınız var ve bu geçersiz kılma bir değer döndürecektir, böylece seçim yapmanız gerekmez. Son zamanlarda, mesajı gerçekten işlemek istemediğim ancak aldığından emin olmak istediğim bir servis otobüsüm vardı. Böylece, özel veri yolu örneğini bu şekilde ayarladım - Yardımcı oldu mu?
dave

Bu yorumda biçimlendirme olduğunu hayal etmeniz gerekecek. Kaldırıldı. Ayrıca, özel erişimi kilitleyeceği için bu Java 9 ile çalışmayacaktır. Resmi bir yayına sahip olduktan ve gerçek sınırlarıyla çalışabildiğimizde diğer bazı yapılarla çalışmak zorunda kalacağız.
dave

1

Pek çok kişi kodunuzu daha test edilebilir hale getirmek için yeniden düşünmenizi tavsiye etti - iyi bir tavsiye ve genellikle benim önereceğimden daha basit.

Kodu daha test edilebilir hale getirmek için değiştiremiyorsanız, PowerMock: https://code.google.com/p/powermock/

PowerMock, Mockito'yu genişletir (böylece yeni bir sahte çerçeve öğrenmenize gerek kalmaz), ek işlevsellik sağlar. Bu, bir kurucunun bir taklit döndürmesi yeteneğini içerir. Güçlü, ancak biraz karmaşık - bu yüzden dikkatli kullanın.

Farklı bir Mock koşucusu kullanıyorsunuz. Ve kurucuyu çağıracak sınıfı hazırlamanız gerekir. (Bunun yaygın bir sorun olduğuna dikkat edin - inşa edilen sınıfı değil, kurucuyu çağıran sınıfı hazırlayın)

@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})

Ardından, test kurulumunuzda, yapıcının bir taklit döndürmesini sağlamak için whenNew yöntemini kullanabilirsiniz.

whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));

0

Evet, aşağıdaki testin gösterdiği gibi bu yapılabilir (geliştirdiğim JMockit alay API'si ile yazılmıştır):

@Test
public void testFirst(@Mocked final Second sec) {
    new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

Ancak Mockito ile böyle bir test yazılamaz. Bunun nedeni, alay konusu sınıfın bir alt sınıfının oluşturulduğu Mockito'da alay etme yöntemidir; yalnızca bu "sahte" alt sınıfın örnekleri alay edilmiş davranışa sahip olabilir, bu nedenle test edilen kodun başka bir örnek yerine bunları kullanmasını sağlamanız gerekir.


3
Soru, JMockit'in Mockito'dan daha iyi olup olmadığı değil, Mockito'da nasıl yapılacağıydı. Rekabeti mahvetme fırsatı aramak yerine daha iyi bir ürün geliştirmeye devam edin!
TheZuck

8
Orijinal poster sadece Mockito kullandığını söylüyor; sadece Mockito'nun sabit ve zor bir gereklilik olduğu ima edilir, bu nedenle JMockit'in bu durumu idare edebileceğine dair ipucu o kadar da uygunsuz değildir.
Bombe

0

Mockito'da Spring'den ReflectionTestUtils'e bir alternatif istiyorsanız , şunu kullanın:

Whitebox.setInternalState(first, "second", sec);

Stack Overflow'a hoş geldiniz! OP'nin sorusunu sağlayan başka cevaplar da vardır ve bunlar yıllar önce yayınlanmıştır. Bir yanıt gönderirken, lütfen özellikle eski soruları yanıtlarken veya diğer yanıtlar hakkında yorum yaparken yeni bir çözüm veya çok daha iyi bir açıklama eklediğinizden emin olun.
help-info.de
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.