Question

Je voudrais tester une classe abstraite. Bien sûr, je peux écrire manuellement maquette qui hérite de la classe.

Puis-je faire cela en utilisant un cadre moqueur (j'utilise Mockito) au lieu de la main-artisanat ma maquette? Comment?

Était-ce utile?

La solution

Le let de suggestion suivante vous testez des classes abstraites sans créer une sous-classe "réel" -. Mock est la sous-classe

utilisation Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), puis se moquer de toutes les méthodes abstraites qui sont invoquées.

Exemple:

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());
    }
}

Note: La beauté de cette solution est que vous ne le faites pas Vous pour mettre en œuvre les méthodes abstraites, tant qu'ils ne sont jamais appelées

.

À mon avis honnête, cela est plus propre que d'utiliser un espion, car un espion nécessite une instance, ce qui signifie que vous devez créer une sous-classe instanciable de votre classe abstraite.

Autres conseils

Si vous avez juste besoin de tester quelques-unes des méthodes concrètes sans toucher aucun des résumés, vous pouvez utiliser CALLS_REAL_METHODS (voir réponse de Morten ), mais si la méthode concrète à l'essai appelle certains des résumés ou des méthodes d'interface inappliquées, cela ne fonctionnera pas - Mockito se plaindra « ne peut pas appeler véritable méthode sur l'interface java. "

(Oui, c'est une conception moche, mais certains cadres, par exemple Tapestry 4, sorte de vous sur la force.)

La solution consiste à inverser cette approche - utiliser le comportement simulé ordinaire (à savoir, tout est moqué / écrasa) et d'utiliser doCallRealMethod() d'appeler explicitement la méthode concrète à l'essai. Par exemple.

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();
    }
}

Mise à jour ajouter:

Pour les méthodes non-vides, vous aurez besoin d'utiliser thenCallRealMethod() au lieu, par exemple:

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

Dans le cas contraire Mockito se plaindra "stubbing inachevé détecté".

Vous pouvez le faire en utilisant un espion (utilisez la dernière version de Mockito 1.8+ bien).

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()));

cadres moqueurs sont conçus pour faciliter la moquer des dépendances de la classe que vous testez. Lorsque vous utilisez un cadre moqueur pour se moquer d'une classe, la plupart des cadres créer dynamiquement une sous-classe, et remplacer la mise en œuvre de la méthode avec le code pour détecter lorsqu'une méthode est appelée et renvoie une valeur fausse.

Lors du test d'une classe abstraite, vous voulez exécuter les méthodes non abstraites du sujet sous test (SUT), donc un cadre moqueur n'est pas ce que vous voulez.

Une partie de la confusion est que la réponse à la question que vous avez lié audit à la main une maquette artisanale qui va de votre classe abstraite. Je ne dirais pas une telle classe une maquette. Une maquette est une classe qui est utilisée pour remplacer une dépendance, est programmée avec les attentes, et peut être interrogé pour voir si ces attentes sont satisfaites.

Au lieu de cela, je vous suggère de définir une sous-classe non abstraite de votre classe abstraite dans votre test. Si cela aboutit à un code trop, que cela peut être un signe que votre classe est difficile à étendre.

Une autre solution serait de faire votre cas de test lui-même abstraite, avec une méthode abstraite pour la création du SUT (autrement dit, le cas de test utiliserait modèle de conception Template Method ).

Essayez d'utiliser une réponse personnalisée.

Par exemple:

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);
    }
}

Il retourne la maquette pour les méthodes abstraites et appeler la méthode réelle des méthodes concrètes.

Ce qui me fait vraiment sentir mal à se moquer des classes abstraites est le fait que ni le constructeur par défaut YourAbstractClass () est appelée (manque super () dans la maquette) ne semble qu'il y ait une façon Mockito par défaut initialiser les propriétés simulacres ( par exemple la liste des propriétés avec ArrayList vide ou LinkedList).

Ma classe abstraite (essentiellement le code source de la classe est généré) ne fournit pas une injection de setter de dépendance pour les éléments de la liste, ni un constructeur où il initialise les éléments de la liste (que j'ai essayé d'ajouter manuellement).

Seuls les attributs de classe l'utilisation initialisation par défaut: Liste privée dep1 = new ArrayList; Liste privée dep2 = new ArrayList

Alors il n'y a pas moyen de se moquer d'une classe abstraite sans utiliser une implémentation réelle de l'objet (par exemple de définition de classe interne en classe de test unitaire, les méthodes abstraites prépondérants) et espionnage au profit de l'objet réel (ce qui fait l'initialisation du champ approprié).

Dommage que seul PowerMock contribuerait ici davantage.

En supposant que votre cours d'essai sont dans le même package (sous une racine source différente) que vos classes en cours de test, vous pouvez simplement créer la maquette:

YourClass yourObject = mock(YourClass.class);

et appeler les méthodes que vous voulez tester tout comme vous le feriez pour tout autre méthode.

Vous devez fournir des attentes pour chaque méthode appelée l'attente sur les méthodes concrètes appelant le super méthode - ne savez pas comment vous feriez cela avec Mockito, mais je crois qu'il est possible avec EasyMock

.

Tout cela fait crée une instance concrète de YouClass et vous permettant d'économiser l'effort de fournir des implémentations vides de chaque méthode abstraite.

En aparté, je trouve souvent utile de mettre en œuvre la classe abstraite dans mon test, où il sert d'exemple d'implémentation que je test via son interface publique, bien que cela dépende de la fonctionnalité fournie par la classe abstraite.

Vous pouvez étendre la classe abstraite avec une classe anonyme dans votre test. Par exemple (JUnit 4):

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.

Vous pouvez instancier une classe anonyme, injecter vos simulacres puis testez cette classe.

@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);
    }
}

Gardez à l'esprit que la visibilité doit être protected pour la myDependencyService de propriété de la ClassUnderTest classe abstraite.

Whitebox.invokeMethod (..) peut être utile dans ce cas.

Mockito permet moqueuse des classes abstraites au moyen de l'annotation @Mock:

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();
        // ...
    }
}

L'inconvénient est qu'il ne peut pas être utilisé si vous avez besoin des paramètres du constructeur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top