[JAVA] Ai-je besoin d'un test si je fais DDD dans une langue avec un type?

Lors d'une précédente table ronde sur le DDD, on m'a demandé "Si vous faites du DDD dans un langage statique, avez-vous besoin d'un test?", Et plus de gens ont été surpris d'entendre "oui / peut-être". J'ai donc décidé de régler un peu le problème.

En conclusion, ma théorie est: "Bien sûr, il y a de nombreux avantages à compiler, mais arrêter de penser ne résout pas tout, et il y a beaucoup d'apprentissage, il est donc préférable de l'écrire."

Le branchement conditionnel est sujet aux bogues même s'il a un type

Par exemple, une modification qui augmente «sinon si» est plutôt dangereuse s'il n'y a qu'un type.

Plan.java


public enum Plan {
    PLAN_1,
    PLAN_2,
    PLAN_3;
}

Price.java


public class Price {
    private final int value;

    public static Price of(Plan plan) {
        if (plan == Plan.PLAN_1)
            return new Price(100);

        else if (plan == Plan.PLAN_2)
            return new Price(200);

        else
            return new Price(300);
    }
}

J'ai ajouté PLAN_4 à cela.

Je voulais que le prix soit de 400 yens, mais j'ai oublié d'ajouter «sinon».

public enum Plan {
    PLAN_1,
    PLAN_2,
    PLAN_3,
+   PLAN_4;
}

Bien sûr, la compilation est terminée et PLAN_4 coûte 300 yens.

effrayé. très effrayant.

De côté

C'est un exemple idiot, mais ce n'est pas une blague.

Il n'est pas rare que les produits se souviennent d'ajouter «sinon», comme «prix», «article détaillé», «texte de courrier», «code système», etc. selon le «plan». ** Il est plutôt imprudent d'empêcher ce type de fuite à grande échelle. ** **

Alors qu'est-ce que tu fais

Ecrire un test devrait être un peu mieux.

Il peut être détecté si tous les éléments de enum (Plan.values ()) sont bien entrelacés dans le code de test.

PriceTest.groovy


//Le code de test utilisé groovy et spock

class PriceTest extends Specification {
    def "#plan -> #exp"() {
        expect:
        Price.of(plan) == new Price(exp)

        where:
        plan        || exp
        Plan.PLAN_1 || 100
        Plan.PLAN_2 || 200
        Plan.PLAN_3 || 300
    }

    def guard() {
        expect:
        Plan.values().length == 3
    }
}

C'est simple, mais si le test qui dépend de Plan contient == 3, vous remarquerez quand Plan augmente ou diminue.

Je n'utilise pas «sinon» pour juger des prix.

Price.java


        else if (plan == Plan.PLAN_3)
            return new Price(300);
        
        else
            throw new RuntimeException("match error");

Si vous avez du code de test, vous le remarquerez.

Organiser: j'ai peur ici

La fraude par argument peut être bénéfique

Au contraire, si vous le faites bien, vous pouvez parfaitement éviter de tels bugs.

Exemple 1

MailService.java


public class MailService {
    public void send(String itemName, String userName) {
        String mailBody = MailBodyFactory.create(itemName, userName);
        ...
    }
}

Exemple 2

FooService.java


public class FooService {
    FooRepository fooRepository;

    public void replace(Foo usingOne) {
        Foo newOne = Foo.newOne();
        
        fooRepository.replace(newOne, usingOne);
    }
}

Ce qui ne va pas, c'est ...

Exemple 1

MailFactory.java


public class MailBodyFactory {
    public static String create(String userName, String itemName) {
        return String.format("%s%Merci d'avoir acheté s", userName, itemName);
    }
}

Exemple 2

FooRepository.java


public interface FooRepository {
    void replace(Foo usingOne, Foo newOne);
}

L'ordre des arguments a été inversé.

Un autre côté

Pensiez-vous "Savez-vous!"? Mais la plupart du temps, je ne me souviens pas de l'ordre des arguments du code produit, et il est inattendu fatigant de l'implémenter soigneusement.

De plus, ce genre de chose est très mauvais car il bouge mal.

Alors qu'est-ce que tu fais

N'abusez pas de String et créez des classes pour ʻUserName et ʻItemName. Alors vous ne pouvez jamais l'inverser.

Foo semble avoir créé une classe de domaine, mais divisons-la par état, par exemple.

public interface FooRepository {
-   void replace(Foo usingOne, Foo newOne);
+   void replace(UsingFoo usingFoo, NewFoo newFoo);
}

Ce ne sera jamais le contraire. (J'ai écrit cet article avec l'élan sur les classes par état → [Arrêtons l'entité divine qui exprime tous les statuts dans une classe!](Https://qiita.com/suzuki-hoge/items / d3ea5940898d85bbc03e)))

C'est un guide approximatif, mais je pense qu'il est préférable de taper ** là où vous essayez d'expliquer avec des noms de variables **.

Il est beaucoup plus facile d'obtenir la ligne rouge, et l'éditeur pense également judicieusement aux candidats complémentaires.

Au fait, j'ai déjà écrit une histoire similaire, donc si vous êtes intéressé, jetez-y également un œil. Cela fait du bien d'avoir différentes classes pour les mêmes valeurs de table

Organiser: j'ai peur ici

Vous pouvez trouver cela difficile à utiliser si vous avez un test

Par exemple, ce code

CampaignCode.java


public enum CampaignCode {
    CODE_A, CODE_B;

    public static CampaignCode create() {
        if (LocalDateTime.now().getDayOfMonth() < 15) {
            return CODE_A;
        } else {
            return CODE_B;
        }
    }
}

Quand j'essaye d'écrire un code de test, now me gêne et je devrais dire" Oh, je ne peux pas écrire un test ... ?? ".

Cela signifie que le produit que vous fabriquez ne fonctionnera qu'avec now, donc il interférera avec les tests d'autres pièces, et il ne sera pas flexible dans les tests de simulation et d'intégration.

** Le code de test utilise cette méthode en premier **, donc c'est une bonne chance de remarquer quelque chose comme "ça? Cette méthode est-elle difficile à utiliser?"

(Dans cet exemple, now doit être passé comme argument)

Résumé

en conclusion

Par exemple, dans le cas d'énumération, des avertissements et des erreurs de compilation se produisent en fonction de la langue, mais je pensais que le flux principal du lieu était "La compilation rend-elle le code de test inutile?", J'ai donc pensé à l'écrire comme une antithèse. J'ai fait.

Cet article est ma théorie personnelle.

Si vous voulez imiter les paroles d'une personne que vous avez entendue récemment, si vous pensez "J'ai entendu dire que vous n'avez pas besoin de test, n'est-ce pas vraiment?", Écrivez le test avec obéissance. Je pense. C'est tout.

Recommended Posts

Ai-je besoin d'un test si je fais DDD dans une langue avec un type?
J'ai créé un programme de jugement des nombres premiers en Java
J'ai écrit un programme de jugement des nombres premiers en Java
Sortie true avec if (a == 1 && a == 2 && a == 3) en Java (identifiant invisible)
J'ai écrit un test avec Spring Boot + JUnit 5 maintenant
Je veux FlashAttribute au printemps même si j'ai défini un proxy inverse! (ne pas faire)
Que faire si vous obtenez une erreur «302» dans le code de test du contrôleur dans Rails
J'ai écrit un test CRUD avec SpringBoot + MyBatis + DBUnit (Partie 1)
Que faire lorsqu'une exception java.io.IOException se produit dans GlassFish
J'ai recherché un framework web avec Gem en Ruby
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3
Je suis tombé sur un gars avec deux points dans les rails
Que faire si vous obtenez une erreur gcc dans Docker
Que faire si j'écris une clause finally dans la syntaxe try-with-resources?
Je ne peux pas créer une classe Java avec un nom spécifique dans IntelliJ
Créer un formulaire de relation parent-enfant avec form_object (j'ai également écrit un test)
J'ai récemment créé une application js dans le langage courant de Dart
J'ai créé un PDF avec Java.
J'ai créé une interface graphique avec Swing
Que faire si vous obtenez un avertissement groovy dans Thymeleaf Layout
Je souhaite sélectionner plusieurs éléments avec une disposition personnalisée dans la boîte de dialogue
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3 (édition PowerMockito)
Je ne peux pas construire si je définis la destination de construction sur un simulateur avec XCode12!
Je voulais mettre en place un diaporama de manière à la mode avec slick.
Je souhaite afficher un PDF chinois (coréen) avec des rapports fins
J'ai écrit une fonction Lambda en Java et l'ai déployée avec SAM
Je veux ForEach un tableau avec une expression Lambda en Java