Soyut sınıfları test etmek için Mockito kullanma


213

Soyut bir sınıfı test etmek istiyorum. Tabii, el ile sınıftan miras bir sahte yazabilirsiniz .

Bunu alayımı el yapımı yerine alaycı bir çerçeve (Mockito kullanıyorum) kullanarak yapabilir miyim? Nasıl?


2
Mockito 1.10.12'den itibaren Mockito , soyut dersleri doğrudan casusluk / alaycılığı desteklemektedir:SomeAbstract spy = spy(SomeAbstract.class);
Pesche

6
Mockito 2.7.14 itibariyle, yapıcı argümanları gerektiren soyut klasikleri de alay edebilirsinizmock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
Gediminas

Yanıtlar:


315

Aşağıdaki öneri let bir "gerçek" alt sınıf yaratmadan soyut sınıflar testi var - Sahte olduğu alt sınıf.

kullanın Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), sonra çağrılan soyut yöntemleri alay edin.

Misal:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

Not: Bu çözümün güzelliği kalmamasıdır var yeter ki çağrılan asla gibi soyut yöntemler uygulamak.

Dürüst görüşüme göre, bu bir casus kullanmaktan daha temizdir, çünkü bir casus bir örnek gerektirir, bu da soyut sınıfınızın anlık bir alt sınıfını oluşturmanız gerektiği anlamına gelir.


14
Aşağıda belirtildiği gibi, soyut sınıf test edilmek için soyut yöntemleri çağırdığında bu işe yaramaz, bu genellikle durumdur.
Richard Nichols

11
Bu aslında soyut sınıf soyut yöntemler çağırdığında işe yarar. Mockito yerine doReturn veya doNothing söz dizimini kullanın.
Gonen I

2
Bu tür bir nesneye bağımlılıkları nasıl enjekte edebilirim (gerçek yöntemleri çağıran sahte soyut sınıf)?
Samuel

2
Söz konusu sınıfın örnek başlatıcıları varsa, bu beklenmedik şekillerde davranır. Mockito, başlatıcıları alaylar için atlar, yani satır içi başlatılan örnek değişkenlerin beklenmedik şekilde boş olacağı ve bu da NPE'lere neden olabileceği anlamına gelir.
digitalbath

1
Soyut sınıf yapıcısı bir veya daha fazla parametre alırsa ne olur?
SD

68

Bazı somut yöntemlerden bazılarına herhangi bir özete dokunmadan test etmeniz gerekirse, kullanabilirsiniz CALLS_REAL_METHODS( Morten'in cevabına bakın ), ancak test edilen somut yöntem bazı özetleri veya uygulanmayan arayüz yöntemlerini çağırırsa, bu işe yaramaz. - Mockito şikayet edecek "Java arayüzü gerçek yöntem çağrılamaz."

(Evet, berbat bir tasarım, ancak bazı çerçeveler, örneğin Goblen 4, bunu size zorlar.)

Çözüm, bu yaklaşımı tersine çevirmektir - sıradan sahte davranışı kullanın (yani, her şey alay konusu / saplanmış) ve doCallRealMethod()test edilen somut yöntemi açıkça çağırmak için kullanın . Örneğin

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

Eklemek için güncellendi:

Geçersiz olmayan yöntemler için thenCallRealMethod()bunun yerine kullanmanız gerekir , örneğin:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

Aksi takdirde Mockito "Bitmemiş stubbing tespit edildi."


9
Bu bazı durumlarda işe yarayacaktır, ancak Mockito bu yöntemle temel soyut sınıfın yapıcısını çağırmaz. Bu, beklenmeyen bir senaryo oluşturulmasından dolayı "gerçek yöntem" in başarısız olmasına neden olabilir. Bu nedenle, bu yöntem her durumda da çalışmayacaktır.
Richard Nichols

3
Evet, nesnenin durumuna hiç güvenemezsiniz, sadece çağrılan yöntemdeki kod.
David Moles

Yani nesne yöntemleri devletten ayrılıyor, harika.
haelix

17

Bunu bir casus kullanarak başarabilirsiniz (Mockito 1.8+ 'nın en son sürümünü kullanın).

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

14

Alaycı çerçeveler, test ettiğiniz sınıfın bağımlılıklarını alay etmeyi kolaylaştırmak için tasarlanmıştır. Bir sınıfı alay etmek için alaycı bir çerçeve kullandığınızda, çoğu çerçeve dinamik olarak bir alt sınıf oluşturur ve yöntem uygulamasını bir yöntemin ne zaman çağrıldığını ve sahte bir değer döndürdüğünü algılayan kodla değiştirir.

Soyut bir sınıfı test ederken, Test Altında Öznenin (SUT) soyut olmayan yöntemlerini uygulamak istersiniz, bu nedenle alaycı bir çerçeve istediğiniz şey değildir.

Karışıklığın bir kısmı, bağlantılı olduğunuz sorunun cevabının soyut sınıfınızdan uzanan bir sahte el işi yapmaktır. Böyle bir sınıfa sahte demezdim. Bir alay, bağımlılığın yerine geçen, beklentilerle programlanan ve bu beklentilerin karşılanıp karşılanmadığını görmek için sorgulanabilen bir sınıftır.

Bunun yerine, testinizde soyut sınıfınızın soyut olmayan bir alt sınıfını tanımlamanızı öneririm. Bu, çok fazla kodla sonuçlanırsa, sınıfınızın genişletilmesinin zor olduğunun bir işareti olabilir.

Alternatif bir çözüm, test durumunuzu SUT oluşturmak için soyut bir yöntemle soyut yapmaktır (başka bir deyişle, test durumu Şablon Yöntemi tasarım desenini kullanır).


8

Özel bir cevap kullanmayı deneyin.

Örneğin:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

Soyut yöntemler için alaycılığı döndürecek ve somut yöntemler için gerçek yöntemi çağıracaktır.


5

Soyut sınıfları alay etme konusunda beni gerçekten kötü hissettiren şey, ne varsayılan kurucunuz YourAbstractClass () öğesinin (sahte olarak super () eksik) çağrılması ya da Mockito'da sahte özellikleri başlatmak için herhangi bir yol olmadığı gerçeğidir (örn. List özellikleri) boş ArrayList veya LinkedList ile).

Benim soyut sınıf (temelde sınıf kaynak kodu oluşturulur) liste öğeleri için bir bağımlılık ayarlayıcı enjeksiyon veya liste öğelerini başlattığı bir yapıcı (elle eklemeye çalıştım) sağlamaz.

Yalnızca sınıf öznitelikleri varsayılan başlatmayı kullanır: private List dep1 = new ArrayList; özel Liste dep2 = yeni ArrayList

Bu nedenle, gerçek bir nesne uygulaması (örn. Birim test sınıfında iç sınıf tanımı, soyut yöntemleri geçersiz kılma) kullanmadan ve gerçek nesneyi (doğru alan başlatması yapan) casusluk yapmadan soyut bir sınıfı taklit etmenin YOK yolu yoktur.

Sadece PowerMock'un burada daha fazla yardımcı olması çok kötü.


2

Test sınıflarınızın, test altındaki sınıflarınızla aynı pakette (farklı bir kaynak kök altında) olduğu varsayılarak, alay oluşturabilirsiniz:

YourClass yourObject = mock(YourClass.class);

ve test etmek istediğiniz yöntemleri, tıpkı diğer yöntemleri yaptığınız gibi arayın.

Süper yöntemi çağıran herhangi bir somut yöntem beklentisiyle çağrılan her yöntem için beklentiler sağlamanız gerekir - bunu Mockito ile nasıl yapacağınızdan emin değilim, ancak EasyMock ile mümkün olduğuna inanıyorum.

Tüm yapmanız gereken, somut bir örnek oluşturmak YouClassve her bir soyut yöntemin boş uygulamalarını sağlama çabasından tasarruf etmektir.

Bir kenara, soyut sınıf tarafından sağlanan işlevselliğe bağlı olmasına rağmen, genel sınıfı genel arayüzüyle test ettiğim örnek bir uygulama olarak hizmet ettiği testimde soyut sınıfı uygulamayı yararlı buluyorum.


3
Ancak sahte kullanımı YourClass'ın somut yöntemlerini test etmeyecek, yoksa yanılıyor muyum? Aradığım şey bu değil.
ripper234

1
Bu doğru, soyut sınıfta somut yöntemleri çağırmak istiyorsanız yukarıdakiler işe yaramaz.
Richard Nichols

Özür dilerim, sadece soyut olanları değil, çağırdığınız her yöntem için gerekli olan beklenti hakkında biraz bilgi vereceğim.
Nick Holt

ama o zaman hala somut yöntemleri değil, alayınızı test ediyorsunuz.
Jonatan Cloutier

2

Soyut sınıfı testinizde anonim bir sınıfla genişletebilirsiniz. Örneğin (Junit 4'ü kullanarak):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

2

Mockito, @Mockaçıklama yoluyla soyut sınıfların alay edilmesine izin verir :

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

Dezavantajı yapıcı parametrelerine ihtiyacınız varsa kullanılamaz olmasıdır.


0

Anonim bir sınıf başlatabilir, alaylarınızı enjekte edebilir ve daha sonra bu sınıfı test edebilirsiniz.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Görünürlüğün soyut sınıfın protectedmülkiyeti için olması gerektiğini unutmayın .myDependencyServiceClassUnderTest


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.