Gdy ustawienie testu jest większe niż rzeczywisty test
TL;DR: Zawieszona konfiguracja, która jest używana tylko częściowo, sprawia, że testy są bardziej połączone i trudniejsze do zrozumienia.
TL;DR: Zawieszona konfiguracja, która jest używana tylko częściowo, sprawia, że testy są bardziej połączone i trudniejsze do zrozumienia.
Problemy
- parowanie
- Czytelność
- Zmarnowany czas egzekucji
- Oszukiwanie kontekstu
- Ukryte uzależnienia testowe
- Trudniejsza konserwacja
- Brittle testowe apartamenty
- Zmieszanie zależności
- Powolna egzekucja
- Błędny kontekst
Rozwiązania
- Tworzenie ukierunkowanych metod instalacji
- Zastosuj test-specyficzne fixtures
- Tworzenie minimalnych ustawień
- Wdrażanie metod testowych fabrycznych
Rewitalizacja ️
https://hackernoon.com/improving-the-code-one-line-at-a-time
https://maximilianocontieri.com/refactoring-011-replace-comments-with-tests
Kontekst
Podczas pisania testów można utworzyć dużą metodę konfiguracji, która inicjuje różne obiekty.
Jeśli tylko jeden test używa wszystkich tych obiektów, podczas gdy inne testy używają tylko niewielkiego podzbioru, tworzysz niepotrzebny nadmiar.
Ten powszechny problem występuje, gdy spodziewasz się, że przyszłe testy mogą wymagać rozległej konfiguracji lub gdy kontynuujesz dodawanie do istniejącej konfiguracji bez oceny tego, co jest naprawdę potrzebne.
Testy są trudniejsze do zrozumienia, ponieważ zawierają nieistotne konteksty, a wolniejsze do wykonania, ponieważ inicjalizujesz obiekty, które nie są używane.
Przykładowy kod
Błędne 🙂
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());
}
}
Właściwie 🙂
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);
}
}
Wykrywanie
- [x] Półautomatyczny
Ten zapach można wykryć, porównując to, co jest ustawione w metodach konfiguracji z tym, co jest używane w każdym teście.
Szukaj testów, które wykorzystują mniej niż 50% zainicjowanych obiektów.
Narzędzia pokrycia kodu mogą pomóc w identyfikacji nieużywanych obiektów instalacyjnych, pokazując, które części instalacji nie są wykonywane przez niektóre testy.
Jeśli znajdujesz się pisanie warunków w konfiguracji, aby utworzyć różne konteksty, jest to wyraźny znak, że potrzebujesz konfiguracji specyficznej dla testów.
Tagi ️
- Testowanie
Poziom
- [x] Pośrednik
Dlaczego biżuteria jest ważna ️
Każdy test powinien odzwierciedlać konkretny scenariusz rzeczywisty.
Zapylone ustawienia łamią tę jasność, utrudniając widzenie tego, co jest testowane i zwiększając prawdopodobieństwo błędów.
Ten złamanyBijecjasprawia, że testy są trudniejsze do zrozumienia, ponieważ nie można określić, które aspekty konfiguracji są krytyczne dla testu, a które są tylko hałasem.
Gdy test zawiedzie, będziesz spędzać więcej czasu badając uzależnienia, które mogą nie być istotne dla niepowodzenia.
Badanie staje się bardziej kruche, ponieważ zmiany nieużywanych obiektów mogą nadal przerywać testy, jeśli te obiekty biorą udział w procesie konfiguracji.
I pokolenie
Generatory kodów AI często tworzą ten zapach, gdy generują kompleksowe zestawy testowe, które próbują pokryć wszystkie możliwe scenariusze.
Priorytetem jest kompletność nad koncentracją, co powoduje opuchnięte metody konfiguracji, które inicjują więcej obiektów niż jest to konieczne dla poszczególnych testów.
Jak wykryć
Sztuczna inteligencja może wykryć ten zapach za pomocą prostych instrukcji, takich jak "Optimalizuj moje ustawienia testowe, aby uwzględnić tylko to, co jest potrzebne dla każdego testu".
Nowoczesne narzędzia sztucznej inteligencji mogą porównywać kod konfiguracyjny z użyciem metody testowej i sugerować ukierunkowane refaktory, oddzielając udostępnioną konfigurację od konfiguracji specyficznej dla testów.
Spróbujcie ich! 🙂
Pamiętaj: asystenci AI popełniają wiele błędów
Sugested Prompt: Przełamać testy i ustawienia
Sugested Prompt: Przełamać testy i ustawienia
Without Proper Instructions |
With Specific Instructions |
---|---|
Podsumowanie
Przeładowane ustawienia testowe, które inicjują obiekty wymagane tylko przez kilka testów, utrudniają zrozumienie i utrzymanie pakietu testowego.
Podczas tworzenia ukierunkowanych ustawień, które zawierają tylko to, czego potrzebuje każdy test, poprawia się przejrzystość, szybkość i niezawodność testów.
Pamiętaj, że testy mają na celu udokumentowanie zachowania za pomocą przykładów iZastąp komentarze.
Zbyt dużo nieistotnego kontekstu czyni te przykłady mniej czytelnymi.Czyste testy opowiadają jasną historię bez zbędnych rozpraszania.
Relacje
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
Więcej informacji
Disclaimer
Kodowe zapachy są mojeOpinia.
Kredyty
Zdjęcie przezMarcin SimonidesJestUnsplash
Jeśli musisz stworzyć dużo struktury przed testem, być może testujesz zbyt wiele warstw
Jeśli musisz stworzyć dużo struktury przed testem, być może testujesz zbyt wiele warstw
- James Shore
Ten artykuł jest częścią serii CodeSmell.