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))
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).
** 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
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();
}
}
privées
ont des résultats basés sur le champPour ces raisons, je pense que ce "Greet" est plein 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();
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.)
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
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'
}
}
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)
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!
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.
Ouais, c'est le texte que je viens de trouver
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
- Classes qui les gèrent
Et last but not least, il correspond cette fois à «Main».
ʻUserest créé et calculé en utilisant
Greet` (le code est omis)
En d'autres termes, cela pourrait ressembler à ceci
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)
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"!
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
Pour faire simple, si le test «logique, chose» requiert ce qui suit, les effets secondaires sont mitigés.
Json
, insertion de données factices dans la base de données, etc.void
dans" logique, chose "POST
, écriture de fichier, etc.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