Wenn Ihr Test-Setup größer ist als der tatsächliche Test
TL;DR: Eine überwältigende Einrichtung, die nur teilweise verwendet wird, macht Ihre Tests gekoppelter und schwieriger zu verstehen.
TL;DR: Eine überwältigende Einrichtung, die nur teilweise verwendet wird, macht Ihre Tests gekoppelter und schwieriger zu verstehen.
Probleme
- Coupling
- Lesbarkeit
- Verlorene Ausführungszeit
- Irreführender Setup-Kontext
- Hidden Test Abhängigkeiten
- härtere Wartung
- Die Brittle Test Suite
- Verwirrende Abhängigkeiten
- Langsamere Exekutive
- Irreführender Kontext
Lösungen
- Erstellen von fokussierten Setup-Methoden
- Test-spezifische Fixierungen anwenden
- Minimale Setups erstellen
- Implementierung von Fabrik-Testmethoden
Reaktionen ️
https://hackernoon.com/improving-the-code-one-line-at-a-time
https://maximilianocontieri.com/refactoring-011-replace-comments-with-tests
Zusammenhang
Wenn Sie Tests schreiben, können Sie eine große Setup-Methode erstellen, die verschiedene Objekte initialisiert.
Wenn nur ein Test alle diese Objekte verwendet, während andere Tests nur eine kleine Untergruppe verwenden, erstellen Sie unnötige Überschüsse.
Dieses häufige Problem tritt auf, wenn Sie erwarten, dass zukünftige Tests eine umfangreiche Einrichtung benötigen, oder wenn Sie eine bestehende Einrichtung hinzufügen, ohne zu bewerten, was wirklich benötigt wird.
Die Tests sind schwieriger zu verstehen, da sie irrelevanten Kontext enthalten, und langsamer zu führen, weil Sie Objekte initialisieren, die nicht verwendet werden.
Sample Code
Falsch 🙂
public class TVSeriesTest {
private MovieSeries theEthernaut;
private List<Character> characters;
private List<Episode> episodes;
private User user;
private UserPreferences preferences;
private RatingSystem ratingSystem;
private StreamingService streamingService;
private List<Review> reviews;
@BeforeEach
public void setUp() {
// Create a complex movie series with many characters
characters = new ArrayList<>();
characters.add(new Character("Juan Salvo", "Richard Darin"));
characters.add(new Character("Helen", "Carla Peterson"));
characters.add(new Character("Favalli", "Cesar Troncoso"));
// Create episodes
episodes = new ArrayList<>();
episodes.add(
new Episode("The Snow", 2025, 121));
episodes.add(
new Episode("The Hands Strikes Back", 2027, 124));
// Create user with preferences
preferences = new UserPreferences();
preferences.setPreferredGenre("Science Fiction");
preferences.setPreferredLanguage("English");
preferences.setSubtitlesEnabled(true);
user = new User("JohnDoe", "john@example.com", preferences);
// Create rating system with reviews
ratingSystem = new RatingSystem(10);
reviews = new ArrayList<>();
reviews.add(
new Review(user, "The Snow", 9, "Classic!"));
reviews.add(
new Review(user, "The Hands Strikes Back", 10, "Best one!"));
ratingSystem.addReviews(reviews);
// Create streaming service
streamingService = new StreamingService("Netflix");
streamingService.addMovieSeries("The Eternaut");
// Finally create the movie series with all components
theEthernaut =
new TVSeries("The Ethernaut", characters, episodes);
theEthernaut.setRatingSystem(ratingSystem);
theEthernaut.setAvailableOn(streamingService);
// This method is too long. That is another smell
}
@Test
public void testTVSeriesRecommendation() {
// This test uses almost everything from the setup
RecommendationEngine engine = new RecommendationEngine();
List<Episode> recommended =
engine.recommendations(user, theEternaut);
assertEquals(2, recommended.size());
assertEquals("The Hands Strikes Back",
recommended.get(0).title());
// You are testing the recomendation Engine
// This is not this object's responsibility
}
@Test
public void testEpisodeCount() {
// This test only needs the episodes count
assertEquals(2, theEthernaut.episodes().size());
}
@Test
public void testCharacterLookup() {
// This test only needs the characters
// And not the rest of the setup
Character juan = theEternaut.findCharacterByName("Juan Salvo");
assertNotNull(juan);
assertEquals("Juan Salvo", juan.actor());
}
}
Richtig 🙂
public class TVSeriesTest {
// No shared setup
@Test
public void testRecommendation() {
// Create only what's needed for this specific test
// And move this test with the behavior
TVSeries theEternaut = createTheEternautSeries();
User homer = createUserWithPreferences();
addReviewsForUser(theEternaut, homer);
RecommendationEngine engine = new RecommendationEngine();
List<Episode> recommended =
engine.recommendations(homer, theEternaut);
assertEquals(2, recommended.size());
assertEquals("The Hands Strikes Back",
recommended.get(0).title());
}
@Test
public void testEpisodeCount() {
// Only create what's needed - just the episodes
TVSeries theEternaut = new TVSeries("The Ethernaut");
theEternaut.addEpisode(
new Episode("The Snow", 2025, 121));
theEternaut.addEpisode(
new Episode("The Hands Strikes Back", 2027, 124));
assertEquals(2, theEternaut.episodes().size());
}
@Test
public void testCharacterLookup() {
// Only create what's needed - just the characters
TVSeries theEternaut = new TVSeries("The Eternaut");
theEternaut.addCharacter(
new Character("Juan Salvo", "Richard Darin"));
theEternaut.addCharacter(
new Character("Helen", "Carla Peterson"));
Character juan = theEternaut.findCharacterByName("Juan Salvo");
assertNotNull(juan);
assertEquals("Richard Darin", juan.actor());
}
// Helper methods for specific test setup needs
private TVSeries createTheEternautTVSeries() {
TVSeries series = new TVSeries("The Eternaut");
series.addEpisode(
new Episode("The Snow", 2025, 121));
series.addEpisode(
new Episode("The Hands Strikes Back", 2027, 124));
return series;
}
private User createUserWithPreferences() {
UserPreferences preferences = new UserPreferences();
preferences.setPreferredGenre("Science Fiction");
preferences.setPreferredLanguage("English");
return new User("JohnDoe", "john@example.com", preferences);
}
private void addReviewsForUser(TVSeries series, User user) {
RatingSystem ratingSystem = new RatingSystem(10);
ratingSystem.addReview(
new Review(user, "The Snow", 9, "Classic!"));
ratingSystem.addReview(
new Review(user, "The Hands Strikes Back", 10, "Best one!"));
series.setRatingSystem(ratingSystem);
}
}
Erkundung
- [x] halbautomatisch
Sie können diesen Geruch erkennen, indem Sie vergleichen, was in den Setup-Methoden eingerichtet ist, mit dem, was in jedem Test verwendet wird.
Suchen Sie nach Tests, die weniger als 50% der initialisierten Objekte verwenden.
Code-Coverage-Tools können ungenutzte Setup-Objekte identifizieren, indem sie zeigen, welche Teile der Setup durch bestimmte Tests nicht ausgeführt werden.
Wenn Sie feststellen, dass Sie in der Konfiguration Bedingungen schreiben, um verschiedene Kontexte zu erstellen, ist dies ein klares Zeichen dafür, dass Sie stattdessen eine testspezifische Konfiguration benötigen.
Tags ️
- Testen
Ebene
- [x] Zwischenzeit
Warum die Bijection wichtig ist ️
Jeder Test sollte ein spezifisches reales Szenario widerspiegeln.
Überschwemmte Setups brechen diese Klarheit, was es schwierig macht zu sehen, was getestet wird und die Wahrscheinlichkeit von Fehlern erhöht.
Diese gebrochenenBijectionTests werden schwieriger zu verstehen, da Sie nicht bestimmen können, welche Aspekte der Installation für den Test entscheidend sind und welche nur Lärm sind.
Wenn ein Test fehlschlägt, verbringen Sie mehr Zeit damit, Abhängigkeiten zu untersuchen, die für den Fehler möglicherweise nicht relevant sind.
Der Test wird brüchiger, da Änderungen an unbenutzten Objekten immer noch Tests durchbrechen können, wenn diese Objekte am Setup-Prozess teilnehmen.
Die Generation
KI-Code-Generatoren schaffen diesen Geruch oft, wenn sie umfassende Testgeräte generieren, die versuchen, alle möglichen Szenarien zu decken.
Sie priorisieren Vollständigkeit über Fokus, was zu aufgeblähten Setup-Methoden führt, die mehr Objekte initialisieren, als für einzelne Tests erforderlich sind.
Die Erkennung
AI kann diesen Geruch mit einfachen Anweisungen wie "Optimieren Sie meine Test-Setup nur, um zu enthalten, was für jeden Test benötigt wird."
Moderne KI-Tools können den Setup-Code mit der Verwendung von Testmethoden vergleichen und gezielte Refactorings vorschlagen, was die geteilte Einrichtung von der testspezifischen Einrichtung trennt.
Probieren Sie es aus!
Denken Sie daran: AI-Assistenten machen viele Fehler
Vorschlag Prompt: Brechen Sie die Tests und die Installation
Vorschlag Prompt: Brechen Sie die Tests und die Installation
Without Proper Instructions |
With Specific Instructions |
---|---|
Schlussfolgerung
Überlastete Test-Setups, die Objekte initialisieren, die nur von wenigen Tests benötigt werden, machen Ihre Test-Suite schwieriger zu verstehen und zu pflegen.
Wenn Sie gezielte Setups erstellen, die nur das enthalten, was jeder Test benötigt, verbessern Sie die Klarheit, Geschwindigkeit und Zuverlässigkeit Ihrer Tests.
Denken Sie daran, dass Tests das Verhalten durch Beispiele undKommentare ersetzen.
Zu viel irrelevanter Kontext macht diese Beispiele weniger lesbar.Reine Tests erzählen eine klare Geschichte ohne unnötige Ablenkungen.
Beziehung
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xxv
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xi-sit35t1
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xxiii
https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xli
Weitere Informationen
Disclaimer
Code Gerüche sind meineMeinungen.
Kredite
Foto vonMarkus SimonidesistUnsplash
Wenn Sie vor einem Test viel Struktur erstellen müssen, testen Sie möglicherweise zu viele Schichten
Wenn Sie vor einem Test viel Struktur erstellen müssen, testen Sie möglicherweise zu viele Schichten
von James Shore
Dieser Artikel ist Teil der CodeSmell-Serie.