[JAVA] Quels sont les effets secondaires? ~ Faisons un petit test unitaire facilement ~

Quels sont les effets secondaires?

Un effet secondaire de la programmation est qu'une fonction modifie l'état (logique) d'un ordinateur. Affecte les résultats obtenus par la suite. Un exemple typique est l'affectation de valeurs à des variables.

[wikipedia](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3% À partir de 82% B0% E3% 83% A9% E3% 83% A0))

En d'autres termes?

C'est un processus qui ne renvoie pas toujours le même résultat même s'il est appelé de la même manière (grosso modo).

Qu'est-ce qui ne va pas?

** Difficile à tester unitaire! !! ** ** (Ci-après, dans cet article, le test automatique du code source à l'aide d'un framework de test tel que JUnit est appelé test unitaire.)

Cela cause beaucoup de problèmes

Montrez-moi un échantillon

Je voulais faire un test unitaire de cette classe, mais cela semble impossible.

Greet.java


@AllArgsConstructor
public class Greet {
    String first;
    String last;
    String gender;

    public void greet() {
        System.out.println(
                getFirstMessage() + forFullName() + getForGender()
        );
    }

    private String getFirstMessage() {
        int hour = LocalDateTime.now().getHour();
        if (6 <= hour && hour < 12) {
            return "Bonjour";
        } else if (12 <= hour && hour < 18) {
            return "Bonjour";
        } else {
            return "Bonne nuit";
        }
    }

    private String getForGender() {
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private String forFullName() {
        return first + " " + last;
    }
}

Main.java


public class Main {
    public static void main(String[] args) {
        Greet greet = new Greet("Yamada", "Takashi", "M");
        greet.greet();
    }
}

Où est-ce mauvais?

Pour ces raisons, je pense que ce "Greet" est plein d'effets secondaires

Donnez-moi un exemple d'effets secondaires

Traitement qui ne renvoie pas toujours le même résultat même s'il est appelé de la même manière

Par exemple, même si vous l'exécutez de la même manière que greet.greet ();, le résultat sera différent selon l'heure de l'exécution.

Comment sera-t-il affiché? Je ne sais pas quand cela sera fait ** quand **!

De plus, par exemple, si vous procédez comme suit, le code des 2ème et 4ème lignes est exactement le même, mais le résultat est différent.

Main.java


Greet greet = new Greet("Yamada", "Takashi", "M");
greet.greet();
greet.gender = 'F';
greet.greet();

Comment cela se rapporte-t-il au fait de ne pas pouvoir tester?

Par exemple, si vous écrivez un test ci-dessous le matin, il est clair qu'il échouera la nuit.

greet.greet() == 'Bonjour Takashi Yamada'

De plus, greet () est void en premier lieu Cela ne me donne même pas l'occasion de comparer les valeurs, car je suis satisfait de la sortie standard et elle ne renvoie rien.

(Bien sûr, vous ne pouvez pas le faire exactement, mais vous avez besoin d'un hack pour l'heure actuelle et d'un hack pour la sortie standard.)

Alors essayez de le réparer

Bien sûr, je vais le réparer Ce code est inacceptable (bien que personnellement)

Jetez un œil à «Salut» et organisez ce que vous faites

Fais-tu 5 choses

  1. Changer le premier mot au fil du temps
  2. Changer le dernier mot en fonction du sexe
  3. Combinez le prénom et le nom avec une demi-largeur
  4. Combinez-les tous avec un demi-angle
  5. Sortie standard

Avant tout, ** l'heure ** et ** la sortie **

Lorsqu'il s'agit de tests unitaires, le problème est de savoir comment implémenter 1 et 5.

Vous ne devriez voir la campagne que le samedi, couper l'alimentation la nuit, le traitement par lots au début de la fin du mois, juger si c'est dans les heures d'ouverture de l'entreprise partenaire, etc. En règle générale, le temps doit être passé de l'extérieur de la logique de décision (selon la structure de la couche et la conception des composants). Dans cet exemple, vous ne pouvez pas tester Greet sans passer le temps de Main.

Ensuite, si le résultat assemblé est sorti en standard, il ne peut pas être testé. Donc le résultat de la logique doit être retourné

Fixons-le

Greet.java


@AllArgsConstructor
public class Greet {
    String first;
    String last;
    String gender;
    LocalDateTime now; //Obtenez-le sans le générer vous-même

    public String greet() {
        return getFirstMessage() + forFullName() + getForGender(); //revenir
    }

    private String getFirstMessage() {
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Bonjour";
        } else if (12 <= hour && hour < 18) {
            return "Bonjour";
        } else {
            return "Bonne nuit";
        }
    }

    private String getForGender() {
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private String forFullName() {
        return first + " " + last;
    }
}

Si vous corrigez les deux ci-dessus, vous pourrez écrire un test qui dit "** Si c'est X heures maintenant, il devrait revenir **".

GreetTest.groovy


class GreetTest extends Specification {
    def test_1() {
        setup:
        def greet = new Greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 12, 30, 00))

        expect:
        greet.greet() == 'Bonjour Takashi Yamada'
    }

    def test_2() {
        setup:
        def greet = new Greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 22, 30, 00))

        expect:
        greet.greet() == 'Bonne nuit Takashi Yamada'
    }
}

Accès sur le terrain

Au fait, corrigez le point où la méthode private accède au champ dans la méthode. Ce n'est pas toujours le cas, et cela peut être plus orienté objet, mais j'écris généralement le code de l'exemple fixe.

L'avantage de le fixer est de clarifier la valeur pertinente et d'éliminer la possibilité que l'état du champ ait été mis à jour en "privé". (Je montrerai un exemple plus tard, mais je vais en faire une méthode statique privée et rendre l'accès aux champs impossible)

Greet.java


@AllArgsConstructor
public class Greet {
    String first;
    String last;
    String gender;
    LocalDateTime now;

    public String greet() {
        return getFirstMessage(now) + forFullName(first, last) + getForGender(gender);
    }

    private String getFirstMessage(LocalDateTime now) {      //N'utilisez que la valeur donnée comme argument
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Bonjour";
        } else if (12 <= hour && hour < 18) {
            return "Bonjour";
        } else {
            return "Bonne nuit";
        }
    }

    private String getForGender(String gender) {             //De même
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private String forFullName(String first, String last) {  //De même
        return first + " " + last;
    }
}

Il est maintenant plus facile de voir de quelle valeur dépend chaque méthode privée Au fait, chaque méthode private ne dépend que de l'argument et renvoie le résultat En d'autres termes, les effets secondaires de la partie méthode privée ont disparu!

La méthode fixe private n'a pas d'accès aux champs Il est donc possible d'en faire une méthode statique

Si static est attaché, this ne peut pas être utilisé, donc "Je vais définir quelques indicateurs pendant le calcul" Il est garanti que les champs comme

Étant donné la signification du mot «statique», le résultat ne change pas de manière dynamique en fonction de l'état, alors pouvez-vous vous sentir soulagé d'une manière ou d'une autre?

(La conversion «statique» est décrite avec l'amélioration ci-dessous)

Staticisation

Eh bien, enfin la finition Seul greet () dépend du champ, mais vous n'avez plus besoin de stocker les valeurs dans le champ séparément.

Greet.java


public class Greet {

    //Le champ est parti

    public static String greet(String first, String last, String gender, LocalDateTime now) { //C'est tout l'argument
        return getFirstMessage(now) + forFullName(first, last) + getForGender(gender);
    }

    private static String getFirstMessage(LocalDateTime now) {
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Bonjour";
        } else if (12 <= hour && hour < 18) {
            return "Bonjour";
        } else {
            return "Bonne nuit";
        }
    }

    private static String getForGender(String gender) {
        if (gender.equals("M")) {
            return "Kun";
        } else {
            return "Chan";
        }
    }

    private static String forFullName(String first, String last) {
        return first + " " + last;
    }

    //Toutes les méthodes sont désormais statiques
}

Cependant, puisque vous n'avez plus besoin de faire «nouveau», vous devez également changer l'appelant.

GreetTest.groovy


class GreetTest extends Specification {
    def test_1() {
        expect:
        Greet.greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 12, 30, 00)) == 'こんにちは Yamada Takashi くん'
    }

    def test_2() {
        expect:
        Greet.greet("Yamada", "Takashi", "M", LocalDateTime.of(2017, 7, 18, 22, 30, 00)) == 'おやすみ Yamada Takashi くん'
    }
}

Maintenant, ce test passera à tout moment, et je ne peux pas simplement l'appeler avec cet argument pour obtenir un autre résultat!

Contrairement à l'orientation objet! ??

Certainement, je me sens comme ça ... En premier lieu, je suis attiré par l'élimination des effets secondaires en raison de l'influence des langages fonctionnels.

Cependant, ce n'est même pas une impression, alors j'y ai réfléchi un instant.

Existe-t-il trois types de classes lors de l'écriture de code orienté objet?

Ouais, c'est le texte que je viens de trouver

  1. Une classe qui n'a pas de valeur et qui juge ou calcule en fonction de la valeur reçue
  2. Une classe qui a une valeur et se comporte en fonction de sa propre valeur
  3. Classes qui les gèrent

Regardons chacun d'eux

Inutile de dire que la classe supérieure est la classe Greet que j'ai corrigée plus tôt. Il n'a pas sa propre valeur, il ne dépend que de ses arguments

Quelle est la deuxième classe Je pense que c'est la classe la plus orientée objet, mais c'est une classe qui exprime des «choses» telles que «nom personnel» et «genre».

Faisons-le apparaître dans le sujet précédent.

First.java


@AllArgsConstructor
public class First {
    @Getter
    private final String value;
}

Last.java


@AllArgsConstructor
public class Last {
    @Getter
    private final String value;
}

Gender.java


public enum Gender {
    M, F
}

User.java


@AllArgsConstructor
public class User {
    private final First first;
    private final Last last;
    private final Gender gender;

    public String asFullNameString() {
        return first.getValue() + " " + last.getValue();
    }

    public boolean isM() {
        return gender == Gender.M;
    }
}

J'ai créé une classe qui représente les «personnes» appelée «utilisateur», et j'ai essayé de déplacer la détermination du sexe et la concaténation des noms complets là-bas.

Je pense que c'est en fait une assez bonne répartition des responsabilités La raison en est qu'il s'est avéré que les parties telles que «Salut» «Comparer le sexe avec« M »» et «Connecter le prénom et le nom avec demi-largeur» ne sont pas réellement liées aux salutations. En effet, tout ce qui est nécessaire pour les salutations est de "traiter un peu le prénom et le nom en fonction de l'heure et du sexe", et "l'homme est" M "ou" connexion demi-largeur "est un processus conforme à la" définition d'une personne ". (Bien sûr, cela peut ne pas être le cas selon les spécifications et la conception)

Et bien sûr, c'est un test unitaire de ʻUser`, c'est absolu

Si vous déplacez "Men Judgment" et "Last Name Concatenation" vers ʻUser et effectuez un test unitaire de ʻUser, le test de Greet ne sera que le jugement de fuseau horaire et Kun-chan.

Greet.java


public class Greet {
    public static String greet(User user, LocalDateTime now) {
        return getFirstMessage(now) + user.asFullNameString() + getForGender(user);
    }

    private static String getFirstMessage(LocalDateTime now) {
        int hour = now.getHour();
        if (6 <= hour && hour < 12) {
            return "Bonjour";
        } else if (12 <= hour && hour < 18) {
            return "Bonjour";
        } else {
            return "Bonne nuit";
        }
    }

    private static String getForGender(User user) {
        if (user.isM()) {
            return "Kun";
        } else {
            return "Chan";
        }
    }
}

Greet est à nouveau rafraîchissant

  1. Classes qui les gèrent

Et last but not least, il correspond cette fois à «Main».

ʻUserest créé et calculé en utilisantGreet` (le code est omis)

Résumé de 3 types

En d'autres termes, cela pourrait ressembler à ceci

  1. Logique
  2. Choses
  3. Traitement

La logique est fondamentalement «statique» et ne devrait pas dépendre de l'état

Les choses contiennent des valeurs et se comportent en fonction d'elles, mais masquent les valeurs elles-mêmes et la logique elle-même (est-ce équivalent à l'encapsulation?)

Il y a une couche de traitement qui permet aux deux de travailler ensemble, les effets secondaires ne sont autorisés que dans la couche de traitement (Puisque les effets secondaires ne peuvent pas être éliminés, je vais le faire ici. Ce test de classe sera fait à l'aide d'un simulacre etc., mais il sera refait bientôt)

De côté? À propos de la frontière entre la logique et les choses

Est-ce la décision de "Greet" de dire "si vous êtes un homme, si vous êtes une femme"? «Utilisateur» N'est-ce pas?

"ʻUser` est combiné avec un espace demi-largeur, mais lors de l'envoi d'un e-mail, il doit être combiné avec un espace pleine largeur."

Tu pourrais penser J'ai pensé pendant un moment

Quand j'ai pensé que c'était "logique?" Ou "chose?", J'ai fini par faire des "choses dédiées à la logique", mais récemment je me suis calmé.

En d'autres termes, créez "ʻUser pour Greet`" et écrivez "Kun-chan" et "Half-width combination" dans la classe d'utilisateurs spécialisée dans le salutation. S'il en va différemment pour les e-mails, nous créerons une classe d'utilisateurs spécifique aux e-mails.

Je pense que c'est une nouvelle division des responsabilités ou une frontière plus claire. Si vous faites cela, le nombre de classes augmentera, mais je pense que le nombre d'items de test n'augmentera pas autant, et surtout, la dépendance diminuera.

Vous n'avez pas besoin de faire quelque chose comme "réévaluer les messages d'accueil en raison de changements dans les spécifications des courriers électroniques"!

Résumé

Exemples d'effets secondaires

Soyez prudent si vous voyez cette zone! !!

Lors du référencement, faites-le en dehors de «logique, choses» et «logique, les choses» ne doivent pas en dépendre N'écrivez pas la valeur assemblée sur place, renvoyez-la une fois à la couche de traitement et réécrivez-la dans la couche de traitement

Guide de jugement facile

Pour faire simple, si le test «logique, chose» requiert ce qui suit, les effets secondaires sont mitigés.

Si ceux-ci sont complètement mis en œuvre, le nombre de codes difficiles à évaluer diminuera, le nombre de tests unitaires augmentera et le rythme de développement s'améliorera.

à la prochaine

Recommended Posts

Quels sont les effets secondaires? ~ Faisons un petit test unitaire facilement ~
Que faire si vous obtenez une erreur «302» dans le code de test du contrôleur dans Rails
Traitons le test unitaire comme la respiration (Apex trigger edition)
Que faire lorsque les modifications du servlet ne sont pas reflétées