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."
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.
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. ** **
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.
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é.
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.
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
String
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)
était ʻempty
,Stream
est effectuée deux fois.Foo using
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