Mockito'yu bazı yöntemleri alay etmek için kullanın, ancak diğerlerini değil


402

Mockito'yu bir sınıfta bazı yöntemlerle alay etmenin, başkalarının yapmamasının bir yolu var mı?

Örneğin, bu (kuşkusuz çelişkili) Stocksınıfta alay etmek getPrice()ve getQuantity()değerleri döndürmek istiyorum (aşağıdaki test snippet'inde gösterildiği gibi) ama getValue()çarpımı Stocksınıfta kodlanmış olarak gerçekleştirmek istiyorum

public class Stock {
  private final double price;
  private final int quantity;

  Stock(double price, int quantity) {
    this.price = price;
    this.quantity = quantity;
  }

  public double getPrice() {
    return price;
  }

  public int getQuantity() {
    return quantity;
  }
  public double getValue() {
    return getPrice() * getQuantity();
  }

  @Test
  public void getValueTest() {
    Stock stock = mock(Stock.class);
    when(stock.getPrice()).thenReturn(100.00);
    when(stock.getQuantity()).thenReturn(200);
    double value = stock.getValue();
    // Unfortunately the following assert fails, because the mock Stock getValue() method does not perform the Stock.getValue() calculation code.
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}

4
Bunu neden yapmak istiyorsun? Sınıfı test ediyor olmalısınız (bu durumda hiç alay etmemelisiniz) ya da farklı bir sınıfı test ederken alay etmelisiniz (bu durumda işlevsellik yok). Neden kısmi bir takas yaparsın?
weltraumpirat

3
Tamam, bu gerçek şeyin küçük bir örneğidir. Gerçekte, ben tutarlı değerler geçirerek, veritabanına bir çağrı önlemek için çalışıyorum, ama diğer yöntemler bu bu değerleri ile doğru çalıştığını doğrulamak istiyorum. Bunu yapmanın daha iyi bir yolu var mı?
Victor Grazi

5
Kesinlikle: Veritabanı çağrılarınızı ayrı bir sınıfa taşıyın (etki alanı mantığı ve veritabanı erişimi aynı sınıfta olmamalıdır; iki farklı endişe kaynağıdır), arabirimini ayıklayın, etki alanı mantık sınıfından bağlanmak için bu arabirimi kullanın ve yalnızca test sırasında arayüz.
weltraumpirat

1
Tamamen katılıyorum, üçüncü taraf kütüphaneler de dahil olmak üzere buraya kod gobs yüklemeden tüm resmi açıklamak zor.
Victor Grazi

1
Muhtemelen yapabilirsin. Ancak, bu "bunu yapmanın daha iyi bir yolu" olmaz: Veritabanı kodunuz, uygulamanızın geri kalanından gizlemek istediğiniz bir uygulama ayrıntısıdır, muhtemelen farklı bir pakete geçebilirsiniz. Her netice ifadesini her değiştirdiğinizde alan mantığınızı yeniden derlemek zorunda kalmazsınız, değil mi?
weltraumpirat

Yanıtlar:


643

Sorunuzu doğrudan yanıtlamak için, evet, bazı yöntemleri başkalarıyla alay etmeden alay edebilirsiniz. Buna kısmi alay denir . Daha fazla bilgi için kısmi alaylarla ilgili Mockito belgelerine bakın .

Örneğin, testinizde aşağıdakine benzer bir şey yapabilirsiniz:

Stock stock = mock(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
when(stock.getValue()).thenCallRealMethod();  // Real implementation

Belirtmedikçe Bu durumda, her bir metot uygulanması, alay edilir thenCallRealMethod()içinde when(..)maddesi.

Ayrıca sahte yerine casus ile başka bir yol var :

Stock stock = spy(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
// All other method call will use the real implementations

Bu durumda, alay konusu bir davranışı tanımlamanız dışında tüm yöntem uygulaması gerçektir when(..).

when(Object)Önceki örnekteki gibi casusla kullandığınızda önemli bir tuzak vardır . Gerçek yöntem çağrılır (çünkü stock.getPrice()daha önce when(..)çalışma zamanında değerlendirilir ). Yönteminiz çağrılmaması gereken bir mantık içeriyorsa, bu bir sorun olabilir. Önceki örneği şöyle yazabilirsiniz:

Stock stock = spy(Stock.class);
doReturn(100.00).when(stock).getPrice();    // Mock implementation
doReturn(200).when(stock).getQuantity();    // Mock implementation
// All other method call will use the real implementations

Başka bir olasılık kullanmak olabilir org.mockito.Mockito.CALLS_REAL_METHODS, örneğin:

Stock MOCK_STOCK = Mockito.mock( Stock.class, CALLS_REAL_METHODS );

Bu delege, gerçek uygulamalara arabulucu olmayan çağrıları delege ediyor.


Uygulanması beri Ancak, örneğin, ben, hala başarısız olacağına inanıyoruz getValue()dayanır quantityve priceyerine getQuantity()ve getPrice()sen alay ettik budur.

Başka bir olasılık, alayları tamamen önlemektir:

@Test
public void getValueTest() {
    Stock stock = new Stock(100.00, 200);
    double value = stock.getValue();
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}

21
Bence bu cevap yanlış. Sınıfı KİLİTLEMEMEK için nesnenin bir örneğini SPY yapmanız gerekir.
GaRRaPeTa

2
@GaRRaPeTa Casusluk ve alay etmenin hem makul alternatifler olduğunu söyleyebilirim. OP bu durum için en iyisinin hangisi olduğunu söylemek zor, çünkü OP bunun basitleştirilmiş bir örnek olduğunu söylüyor.
Jon Newmuis

1
Kısmi alaycı kabin "Casus" tarafından daha iyi bir şekilde sağlanacağı için "Sahte" yerine "Casus" olmamalı.
Tarun Sapra

2
Stock stock = spy(Stock.class);Bu yanlış görünüyor, spyyöntem sadece sınıfları değil nesneleri kabul ediyor gibi görünüyor.
Paramvir Singh Karwal

4
1 arasındaki farkı işaret için doReturn(retval).when(spyObj).methodName(args)vewhen(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious

140

Bir sınıfın kısmi alaycılığı, mockito'daki Spy aracılığıyla da desteklenir

List list = new LinkedList();
List spy = spy(list);

//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);

//using the spy calls real methods
spy.add("one");
spy.add("two");

//size() method was stubbed - 100 is printed
System.out.println(spy.size());

Kontrol 1.10.19ve 2.7.22ayrıntılı bir açıklama için dokümanlar.


37

Dokümanlara göre :

Foo mock = mock(Foo.class, CALLS_REAL_METHODS);

// this calls the real implementation of Foo.getSomething()
value = mock.getSomething();

when(mock.getSomething()).thenReturn(fakeValue);

// now fakeValue is returned
value = mock.getSomething();

2
Testten kontrol etmem gereken birkaç kişi hariç, gerçek uygulamanın tüm yöntemler için çağrıldığı bir sahte kurulumun nasıl yapıldığını gösterdiğiniz için teşekkür ederiz.
bigh_29

class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); }Bu işe yaramıyor. Nedeni ne olursa olsun, "ne zaman" yürütüldüğünde, aslında alay edilmesi gereken yöntemi yürütür. Kod:
Lance Kind

3
Sorun "ne zaman" dır. "Ne zaman" aslında kısmen alay etmek istediğiniz şeyi yürütür. Bundan kaçınmak için bir alternatif var: doReturn (). Docs.mockito.googlecode.com/hg/1.9.5/org/mockito/… adresindeki doReturn () işlevine bakın
Lance Kind

18

org.mockito.Mockito.CALLS_REAL_METHODSDokümanlara göre ne istiyorsun :

/**
 * Optional <code>Answer</code> to be used with {@link Mockito#mock(Class, Answer)}
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations.
 * <p>
 * This implementation can be helpful when working with legacy code.
 * When this implementation is used, unstubbed methods will delegate to the real implementation.
 * This is a way to create a partial mock object that calls real methods by default.
 * <p>
 * As usual you are going to read <b>the partial mock warning</b>:
 * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects.
 * How does partial mock fit into this paradigm? Well, it just doesn't... 
 * Partial mock usually means that the complexity has been moved to a different method on the same object.
 * In most cases, this is not the way you want to design your application.
 * <p>
 * However, there are rare cases when partial mocks come handy: 
 * dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.)
 * However, I wouldn't use partial mocks for new, test-driven & well-designed code.
 * <p>
 * Example:
 * <pre class="code"><code class="java">
 * Foo mock = mock(Foo.class, CALLS_REAL_METHODS);
 *
 * // this calls the real implementation of Foo.getSomething()
 * value = mock.getSomething();
 *
 * when(mock.getSomething()).thenReturn(fakeValue);
 *
 * // now fakeValue is returned
 * value = mock.getSomething();
 * </code></pre>
 */

Böylece kodunuz şöyle görünmelidir:

import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class StockTest {

    public class Stock {
        private final double price;
        private final int quantity;

        Stock(double price, int quantity) {
            this.price = price;
            this.quantity = quantity;
        }

        public double getPrice() {
            return price;
        }

        public int getQuantity() {
            return quantity;
        }

        public double getValue() {
            return getPrice() * getQuantity();
        }
    }

    @Test
    public void getValueTest() {
        Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
        when(stock.getPrice()).thenReturn(100.00);
        when(stock.getQuantity()).thenReturn(200);
        double value = stock.getValue();

        assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
    }
}

Aşağıdaki gibi görünen Stock stock = mock(Stock.class);çağrılara çağrı org.mockito.Mockito.mock(Class<T>):

 public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}

Değerin belgeleri RETURNS_DEFAULTSşunları söyler:

/**
 * The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
 * Typically it just returns some empty value. 
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations. 
 * <p>
 * This implementation first tries the global configuration. 
 * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
 */

1
İyi tespit ... ama neden böyle kullandığınızı sorabilir miyim withSettings()...? Görünüşe göre org.mockito.internal.stubbing.answers.CallsRealMethods()(örneğin) bu işi yapabilir ... ve bu sınıf için javadoc, kısmi alaylar için kullanıldığını söylüyor ...
mike kemirgen

3
Ayrıca ... bu, burada diğer cevapların karşılaştığı problemle karşılaşmayacak mı: yani thenReturnyöntemi (bu örnekte olmasa da sorunlara neden olabilir) yürütecek ve bu doReturndurumda tercih edilebilir ...?
mike kemirgen

4

Mockito'nun casus yöntemini kullanarak kısmi alay etmek, yukarıdaki cevaplarda belirtildiği gibi, probleminizin çözümü olabilir. Bir dereceye kadar, somut kullanım durumunuz için DB aramasını taklit etmenin daha uygun olabileceğini kabul ediyorum. Deneyimlerime göre, bu, her zaman mümkün değildir - en azından diğer geçici çözümler olmadan - çok hantal veya en azından kırılgan olarak kabul edeceğim. Kısmi alaycılığın Mockito'nun müttefik sürümleriyle çalışmadığını unutmayın. En az 1.8.0 kullanıyorsunuz.

Ben sadece bu soruyu göndermek yerine orijinal soru için basit bir yorum yazmış olurdu, ama StackOverflow buna izin vermiyor.

Bir şey daha var: Burada birçok kez bir soru sorulmasının, en azından sorunu anlamaya çalışmadan, "Bunu neden yapmak istiyorsunuz?" Özellikle kısmi alaycılığa ihtiyaç duyulduğunda, nerede yararlı olacağını hayal edebileceğim çok fazla kullanım durumu var. Bu yüzden Mockito'lu çocuklar bu işlevselliği sağladı. Bu özellik elbette aşırı kullanılmamalıdır. Ancak, aksi takdirde çok karmaşık bir şekilde kurulamayan test durumu kurulumları hakkında konuştuğumuzda, casusluk kullanılmalıdır.


2
Bu cevabın kısmen bir fikir olduğunu hissediyorum. Lütfen düzenlemeyi düşünün.
soundslikeodd

2
Ailenin yeni üyesini neşelendirmek için seçildi. Bu -ve bölgesi elde etmeye gerek yok, orada teknik olarak yanlış bir şey yok veya yanlış dil / ton. Yeni üyelere karşı nazik olun.
Saurabh Patil
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.