2017-09-29 14 views
1

je rencontre un moment difficile à déterminer comment aborder un problème avec les tests unitaires. J'ai peu ou pas d'expérience en «tests unitaires». J'essaye de changer le classUnderTest seulement si absolument nécessaire avec des changements minimes. J'utilise JUnit 4, et je suis prêt à essayer d'utiliser Mockito, JMockit ou d'autres bibliothèques qui aideraient à tester efficacement et efficacement les unités. J'utilise JDBC pour la base de données.Comment Java Test Unit d'une classe complexe

Problème:

Dans le ClassUnderTest, je suis accès à une base de données statique . J'essaie d'utiliser des cas de test unitaires avec une config valide et une config invalide. Quelle est la bonne façon d'aborder ce problème?

Quelques possibles solutions que j'ai fait des recherches sont:

  1. D'une certaine façon en passant, ou 'injecter' un ObjectXResult faux de classe ObjectX
  2. Mock une base de données (si même possible avec statique référence db, utilisez une servlet de test unitaire distincte faisant référence à une fausse classe SpecializedDao?)
  3. Utilisez Mockito avec injectMocks, espion, lorsque (methodcallwithparameters) thenReturn résultat, ou une autre mise en œuvre Mockito fournit.
  4. Ouvert à toute autre solution.

Problème:

Dans le ClassUnderTest, je me sers un autre traitement complexe class2 qui fait beaucoup de travail comme ceci:

result = class2.execute(x, x, x, x, x, x); 

class2-t traiter des choses et renvoie un résultat ENUM ou quelques exceptions. Comment puis-je gérer cela avec garder le champ de ce test unitaire spécifique sur ClassUnderTest. class2 accède à la base de données, et fait beaucoup de travail qui est la raison pour laquelle il est sa propre classe, mais je pense que les trois cas de test final dépendent du traitement pour tester soigneusement ClassUnderTest.

Merci pour porter avec moi, j'ai essayé de faire de la question avec autant de clarté que possible.

+1

Il semble que vous ayez quelques couches à tester ici. Au sein de mon organisation, nous utilisons une architecture comme couche de service (qui appelle la couche dao) et une couche de dao (qui appelle réellement les appels jdbc). En ce qui concerne les tests unitaires, pensez à séparer votre classe de junits par classe. Donc pour votre couche de service qui fait la «logique métier» réelle, vous pouvez utiliser quelque chose comme EasyMock pour simuler les appels DAO. Cela vous permettrait d'isoler la logique spécifique à votre couche de service. Pour votre couche DAO, vous pouvez envisager d'utiliser quelque chose comme une base de données intégrée (DERBY) que vous testez par rapport à – Jason

+0

Si vous ne pouvez absolument pas modifier la classe, PowerMockito vous permet de vous moquer des méthodes statiques. Mais j'ai tendance à considérer la nécessité d'utiliser PowerMockito comme un échec de conception - la testabilité aurait dû être l'un des critères de conception en premier lieu. –

Répondre

1

Ma recommandation, si vous vous souciez de développer bon, les tests utiles, est de ne pas fausse la base de données, ou d'autres classes complexes votre code peut interagir avec.

Au lieu de cela, penser aux scénarios spécifiques d'affaires que votre code en cours de test est destiné à rendre, et écrire un réaliste (ie, sans remplacer les classes/composants réels avec bon marché - et souvent erronées - imitations d'entre eux) et (du point de vue des exigences du monde réel) pour chaque scénario.

Cela dit, si vous voulez toujours se moquer de cette classe de DAO, il peut se faire comme suit en utilisant JMockit:

@Test 
public void exampleTest(@Mocked SpecializedDao mockDao) { 
    ObjectX objectResult = new ObjectX(); 

    new Expectations() {{ 
     mockDao.currentObjectXByTimestamp(anyX, anyY, anyZ); result = objectResult; 
    }}; 


    // Call the SUT. 
} 

(anyX, etc. ne sont pas l'argument JMockit réel champs correspondant, bien sûr - les vrais sont anyInt, anyString, any, etc.)

3

Vous devez utiliser quelque chose comme Mockito dans tous les cas, donc je vais supposer que vous avez cela.

Premier problème:

test du code qui implique des appels statiques peut être une douleur. Vous pourriez inclure un certain code octet manipulant la bibliothèque comme PowerMock, mais ça ne vaut pas le coup à mon avis. Ce que vous pouvez faire est de placer l'appel statique dans une méthode package-local, puis de le remplacer par spy. Quelque chose comme:

//in class under test: 
SpecializedDato getSpecializedDao() { 
    return SpecializedDao.getSpecializedDao(); 
} 

//in test: 
import static org.mockito.Mockito.*; 
//... 
final SpecializedDao daoMock = mock(SpecializedDao.class); 
final ClassUnderTest classUnderTest = spy(new ClassUnderTest()); 
doReturn(daoMock).when(classUnderTest).getSpecializedDao(); 

Deuxième problème:

Si vous trouvez que vos tests deviennent très complexes, il est probablement dû à la classe en cours de test étant trop complexe. Voyez si vous pouvez extraire la fonctionnalité dans d'autres classes plus petites. Ensuite, vous n'avez qu'à vérifier que ces classes plus petites sont appelées.

+0

Ok génial, je vais essayer de mettre en œuvre cela. Vous vouliez saisir SpecializedDao au lieu de SpecializedDato? Ou est-ce l'objet de test d'accès aux données, le faux de SpecializedDao? – weteamsteve

+0

Ouais .. mise à jour réponse .. – Tobb

3

Une bonne règle est de ne jamais se connecter à des sources externes à partir de vos JUnits. Tout ce qui fonctionne, comme les connexions à une base de données, vous pouvez les utiliser à l'aide de Mockito. En utilisant Mockito, vous pouvez vous moquer de toutes les classes que vous ne souhaitez pas tester. Dans votre cas, vous pouvez simplement vous moquer de ces classes lourdes et renvoyer un résultat attendu.

+1

Et si Mockito ne vous convient pas, il y a aussi JMockit –

1

À mon bureau, nous utilisons généralement quelques solutions pour rendre une classe complexe plus testable.

Le moins souhaitable est le SEUL qui fonctionnera sans modifier la classe en cours de test - lourd moqueur. Vous vous moquez de tout en dehors de votre classe. Si vous avez un membre statique dans la classe, vous pouvez le définir avec une réflexion avant de commencer (peut-être la réponse à votre question).

Cette approche est difficile et fragile, mais c'est à peu près la seule solution qui fonctionne si vous ne pouvez pas modifier la classe testée.

Une exception mineure est une version légèrement différente du mocking - étendant la classe à tester et remplaçant toutes les choses avec lesquelles votre classe interagit. Ce n'est pas toujours efficace pour la statique, mais cela fonctionne bien si votre classe a été conçue en utilisant des getters.

Si vous pouvez modifier votre classe, nous utilisons quelques techniques pour aider:

1) Évitez tout le temps statics. Si vous devez avoir un singleton, assurez-vous qu'il est testable soit en utilisant un cadre d'injection ou en construisant vous-même une structure de mini-injection (une carte statique pré-remplie devrait être un bon début). la logique métier dans les fonctions pures et nous appelons les classes contenant toutes ces classes logiques "Règles". Nos règles peuvent généralement être tracées directement aux exigences et ils ne fournissent aucune de leurs propres données, tout est transmis. Ils sont généralement appelés par une façade OO qui contient des données connexes et d'autres codes.

3) Réduire la complexité et les interactions de vos classes. Séparez l'accès à la base de données des calculs. Regardez chaque classe et assurez-vous qu'elle fait exactement une chose bien. Cela n'aide pas directement mais vous remarquerez que les tests d'écriture seront plus simples si vous suivez cette règle.

1

Sans solution d'articulation sur une structure de simulation, un modèle standard pour traiter la statique qui n'implique que des changements minimes à CuT est d'extraire la méthode statique derrière une interface, et la classe qui accède à la méthode statique accède maintenant à méthode sur l'interface. Une instance de cette interface est injectée dans le CuT qui, lorsqu'il n'est pas testé, est une petite classe de wrapper qui délègue directement à la méthode statique d'origine.

Maintenant, vous avez une interface avec laquelle travailler, et vous pouvez injecter l'instance de cette interface via un setter ou une injection de constructeur, et Bob est votre oncle. Cette astuce standard implique un peu de "truc" supplémentaire - une interface, et une petite classe wrapper implémentant l'interface qui délègue au câblage statique, et un peu de code ou de DI qui obtient cette classe d'implémentation dans la partie production de le code à injecter sur le CuT via le constructeur ou le setter.

Ensuite, dans votre test unitaire, vous avez un crochet pour injecter un faux, ou un talon créé à la main, ou n'importe quel style de Test Double que vous voulez.

Ceci est un modèle très courant lorsqu'il s'agit d'une bibliothèque tierce hors de votre contrôle, qui aime utiliser la statique.

1

J'ai récemment publié une vidéo sur youtube qui illustre certaines des étapes mentionnées par Jim Weaver. Il est en allemand mais pourrait être utile. Dans cette vidéo, je présente une interface pour surmonter le problème avec l'appel statique de l'accès à la base de données.

https://www.youtube.com/watch?v=KKYro-HGRyk

La deuxième vidéo montre une approche utile si vous ne pouvez pas modifier le code source de la méthode appelée.

https://www.youtube.com/watch?v=fFLfx8qsBdI