Test Driven Design
@nphumbert – @rnowif
Nadia & Renaud Humbert-Labeaumaz
@nphumbert – @rnowif
Testabilité == Maintenabilité
@nphumbert – @rnowif
Un code facilement testable doit
- pouvoir être testé en isolation
- pouvoir se faire injecter des mocks
- avoir des tests concis
- permettre de créer ses propres mocks
@nphumbert – @rnowif
Tester en isolation
public class FruitService {
public boolean isFruitPresentInBasket(Long basketId, Fruit fruit) {
return new FruitOracleDao()
.getFruitsByBasket(basketId)
.contains(fruit);
}
}
@nphumbert – @rnowif
Tester en isolation
@Test
public void should_find_fruit_in_basket() {
boolean fruitPresent = new FruitService()
.isFruitPresentInBasket(1L, aBanana());
// Impossible à vérifier sans vraie base de données Oracle !
assertThat(fruitPresent).isTrue();
}
@nphumbert – @rnowif
Tester en isolation
public class FruitService {
private final FruitRepository fruitRepository;
public FruitService(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
public boolean isFruitPresentInBasket(Long basketId, Fruit fruit) {
return fruitRepository
.getFruitsByBasket(basketId)
.contains(fruit);
}
}
@nphumbert – @rnowif
Tester en isolation
@Test
public void should_find_fruit_in_basket() {
FruitRepository fruitRepository = mock(FruitRepository.class);
when(fruitRepository.getFruitsByBasket(1L))
.thenReturn(asList(aBanana()));
boolean fruitPresent = new FruitService(fruitRepository)
.isFruitPresentInBasket(1L, aBanana());
assertThat(fruitPresent).isTrue();
}
@nphumbert – @rnowif
Tester en isolation
Rendre le code testable en isolation le rend
- indépendant d'une implémentation particulière grâce à l'injection d'une interface.
- extensible car n'importe quelle implémentation peut être fournie.
@nphumbert – @rnowif
Injecter des mocks
public class FruitService {
public boolean isHealthy(FoodItem foodItem) {
Set<Vitamine> vitamines = emptySet();
if (foodItem instanceof Fruit) {
vitamines = newHashSet(A, B, C, E);
} else if (foodItem instanceof Dairy) {
vitamines = newHashSet(A, B, D);
} else if (foodItem instanceof Candy) {
vitamines = emptySet();
}
return vitamines.size() > 3;
}
}
@nphumbert – @rnowif
Injecter des mocks
@Test
public void should_be_healthy_when_more_than_3_vitamines {
FoodItem foodItem = mock(FoodItem.class);
// Comment indiquer qu'il a plus de 3 vitamines ?
boolean isHealty = fruitService.isHealthy(foodItem);
assertThat(isHealty).isTrue();
}
@nphumbert – @rnowif
Injecter des mocks
public class FruitService {
public boolean isHealthy(FoodItem foodItem) {
Set<Vitamine> vitamines = foodItem.getVitamines();
return vitamines.size() > 3;
}
}
@nphumbert – @rnowif
Injecter des mocks
@Test
public void should_be_healthy_when_more_than_3_vitamines {
FoodItem foodItem = mock(FoodItem.class);
when(foodItem.getVitamines()).thenReturn(newHashSet(A, B, C, D));
boolean isHealty = fruitService.isHealthy(foodItem);
assertThat(isHealty).isTrue();
}
@nphumbert – @rnowif
Injecter des mocks
Injecter des mocks
- est possible seulement si le code testé ne varie pas en fonction de l'implémentation de l'interface qu'il manipule.
- prouve que le code est réutilisable car il est déjà utilisé au moins 2 fois avec des implémentations différentes.
@nphumbert – @rnowif
Avoir des tests concis
Réduire le nombre de tests par classe et les garder concis
- encourage à limiter les responsabilités de la classe testée.
- réduit le nombre de raisons qu'a la classe de changer.
@nphumbert – @rnowif
Créer ses propres mocks
public class FruitService {
public boolean isHealthy(FoodItem foodItem) {
return foodItem.getVitamines() > 3;
}
}
@nphumbert – @rnowif
Créer ses propres mocks
@Test
public void should_be_healthy_when_more_than_3_vitamines {
FoodItem foodItem = new MockFoodItem(newHashSet(A, B, C, D));
boolean isHealty = fruitService.isHealthy(foodItem);
assertThat(isHealty).isTrue();
}
@nphumbert – @rnowif
Créer ses propres mocks
public interface FoodItem {
Set<Vitamine> getVitamines();
Color getColor();
Flavor getFlavor();
Size getSize();
String getName();
boolean isOrganic();
Collection<Allergen> getAllergens();
boolean isExpired();
// ...
}
@nphumbert – @rnowif
Créer ses propres mocks
@nphumbert – @rnowif
Créer ses propres mocks
public class FruitService {
public boolean isHealthy(Nutrient nutrient) {
return nutrient.getVitamines() > 3;
}
}
public interface Nutrient {
Set<Vitamine> getVitamines();
}
@nphumbert – @rnowif
Créer ses propres mocks
@Test
public void should_be_healthy_when_more_than_3_vitamines {
Nutrient nutrient = () -> newHashSet(A, B, C, D);
boolean isHealty = fruitService.isHealthy(nutrient);
assertThat(isHealty).isTrue();
}
@nphumbert – @rnowif
Créer ses propres mocks
Pouvoir créer ses propres mocks
- encourage à limiter la taille des interfaces.
- rend le code plus clair et moins sujet aux erreurs car les interfaces plus cohérentes.
@nphumbert – @rnowif
Conclusion
Du code facilement testable
- est extensible et réutilisable car il ne dépend pas de l'implémentation de l'interface qu'il manipule.
- limite les responsabilités de chaque classe et réduit le nombre de raisons qu'elles ont de changer.
- limite la taille des interfaces en les rendant plus cohérentes et est moins sujet aux erreurs.
@nphumbert – @rnowif
« Le code facilement testable est plus solide et plus maintenable. »
Merci
@nphumbert (nphumbert.github.io)
Crafties @ YouTube
@rnowif (rnowif.github.io)
Test driven design
By Nadia Humbert-Labeaumaz
Test driven design
- 973