2017-05-19 2 views
0

Pour apporter un exemple précis, je PromoCode racine globale qui se compose de PromoCodeUsage entité qui est uniquement contrôlée par cette AR, de sorte que certaines méthodes sur AR sont simplement déléguées à cette entité, comme:Comment tester les méthodes de la racine agrégée qui délègue juste son appel à l'entité AR?

public function useFor(Order $order): void 
{ 
    $this->promoCodeUsage->useFor($order); 
} 

Et certains d'entre eux sont en partie déléguée, comme:

public function applyFor(Order $order): void 
{ 
    if (!$this->published) { 
     throw new NotPublishedPromoCodeCanNotBeApplied(); 
    } 

    $this->promoCodeUsage->applyFor($order); 
} 

Ma suite de tests couvre entièrement tous les comportements PromoCode y compris PromoCodeUsage fonctionnalité, car à cette itération il n'y avait pas PromoCodeUsage et la logique a été mélangé à PromoCode. Puis j'ai refacturé une partie de cette logique en PromoCodeUsage. Cette suite de tests pour PromoCode a eu de nombreux tests et j'étais heureux de pouvoir le diviser aussi (mais ça a bien fonctionné même après avoir séparé des entités). J'ai donc créé une autre suite de tests (PromoCodeUsageTest), où j'ai déplacé une partie des tests de PromoCode.

Mais PromoCodeUsageTest s testent l'entité PromoCodeUsage par le biais du comportement de PromoCode, de la même manière que dans le test d'origine avant la division. Ils ne touchent pas PromoCodeUsage directement. Maintenant, j'ai PromoCodeTest suite avec: enter image description here et suite PromoCodeUsageTest avec: enter image description here

Mais il est en quelque sorte bizarre, que 1) PromoCodeTest Je laisse de côté certains tests (qui sont d'ailleurs) et 2) PromoCodeUsageTest Je suis en fait pas touchant PromoCodeUsage entité. 3) J'utilise le modèle de Roy Osherove pour le nommage des tests, et je ne sais pas quel nom de méthode dois-je utiliser dans le nom du test - à partir de PromoCode ou de PromoCodeUsage? Dans mon cas, ils sont identiques, mais ils pourraient différer et cette idée sent.

Si je réécris PromoCodeUsageTest s pour tester directement l'entité PromoCodeUsage, je me retrouve avec des méthodes non couvertes sur PromoCode (qui sont simplement déléguées à PromoCodeUsage). Donc, cela me ramène à mon approche pour tester PromoCodeUsage à PromoCode AR. Oncle Bob (et autres) dit que c'est une bonne habitude de tester le comportement, pas API. Mon approche est-elle conforme à cela? Parce que je sens de l'odeur dans mon approche, n'est-ce pas? Comment faire mieux?

+1

http://stackoverflow.com/a/153565/54734 – VoiceOfUnreason

+1

* "dans PromoCodeUsageTest Je ne suis pas en train de toucher l'entité PromoCodeUsage" * - pas même dans les assertions? – guillaume31

+0

@ guillaume31 Non, je revendique par AggregateRoot. Et aussi, je ne prétends pas trop, je n'ai plutôt aucune assertion à savoir, que l'action peut être effectuée sans lancer d'exception/erreur quand j'ai déjà testé tous les cas qui suscitent l'exception attendue. Parce que j'ai découvert, je devrais avoir des getters sur mes entités seulement pour être capable d'effectuer ces affirmations et je pense que c'est mauvais, non seulement parce que j'ai des méthodes pour les tests seulement, mais parce que je testerais l'API plutôt que le comportement. – Tom

Répondre

1

Vous avez raison de penser au comportement de test. Je suppose que tout le comportement de votre agrégat est exposé à travers la racine agrégée, il est donc logique de tester à travers la racine. Je vous suggère simplement de nommer vos tests pour décrire le comportement qu'ils testent. N'utilisez pas de noms de méthode dans les noms de test, car ceux-ci pourraient changer - ceci lie vos noms de test à l'implémentation interne de votre code de production.

Si une classe de test devient très volumineuse, il est logique de la diviser en classes plus petites - il n'y a aucune règle selon laquelle vous devez avoir une relation 1: 1 entre les classes de test et de production. Cependant, cela peut suggérer que votre classe, et votre agrégat dans ce cas, pourrait avoir trop de responsabilités et pourrait être divisée en plus petites parties.

+0

Malheureusement, mon AR a vraiment besoin de tout cela, car il a besoin de vérifier atomiquement les invariants. C'est un peu compliqué parce que ces invariants peuvent être dynamiquement définis avec des objets Restriction. Mais merci pour les conseils, certainement utile pour ma confiance et mon inspiration :) – Tom

1

J'ai tendance à voir des Agrégats comme des machines à états et à les tester en conséquence.Ce qui importe, ce n'est pas le test dans le fichier de test, mais le test de tous les états résultants de l'agrégat PromoCode en fonction de l'état de départ et du type d'utilisation/application du code promotionnel que vous utilisez.

Bien sûr, cela peut nécessiter de regarder au plus profond de l'intégrité de l'agrégat, dans les entités dépendantes. Si vous êtes plus à l'aise de mettre dans une classe de test différente tous les tests dont les Asserts regardent PromoCodeUsage par exemple, alors bien, tant que les noms de test reflètent le domaine et pas quelques détails techniques.

+0

J'ai décidé de tester tout le comportement de PromoCode AR, mais je vais séparer les tests en plusieurs classes de Test nommées par des classes spécifiques (entités) dont PromoCode est fait. Mais je suis maintenant bloqué avec comment nommer les tests de chaque IRestriction spécifique dans PromoCodeUsage (quel serveur comme invariant dynamique d'affaires). Donc je suis venu avec quelque chose comme 'XxxOrderSpecificationRestrictionTest' contenant par exemple' testThroughPromoCodeApplyTo_OrderWithUnfitSpecification_SUTWithEmptyOptions_ShouldNotApply'. Mais c'est bizarre. En outre, ce qui est vraiment SUT est, XxxOrderSpecificationRestriction, ou AR Je suis en train de le tester? – Tom

+1

Je n'ai pas tout à fait compris ce que sont les 'Restrictions', mais je dirais que le RA reste le SUT. Il transite dans un nouvel état dans son ensemble ou ne fait pas la transition. – guillaume31