Cuando su configuración de prueba es mayor que la prueba real
TL;DR: La configuración inundada que solo se utiliza parcialmente hace que sus pruebas sean más acopladas y más difíciles de entender.
TL;DR: La configuración inundada que solo se utiliza parcialmente hace que sus pruebas sean más acopladas y más difíciles de entender.
Problemas
- El Coupling
- Legibilidad
- Tiempo de ejecución perdido
- Configuración del contexto engañoso
- Test de dependencias ocultas
- Mantenimiento más difícil
- Suite de pruebas Brittle
- Confusión de dependencias
- Ejecución más lenta
- Contexto engañoso
Soluciones
- Creación de métodos de configuración enfocados
- Aplica fixturas específicas de ensayo
- Crea configuraciones mínimas
- Implementación de métodos de prueba de fábrica
Recuperación ️
https://hackernoon.com/improving-the-code-one-line-at-a-time
https://maximilianocontieri.com/refactoring-011-replace-comments-with-tests
Contexto
Cuando escribe pruebas, puede crear un método de configuración grande que inicia varios objetos.
Si solo una prueba utiliza todos estos objetos, mientras que otras pruebas utilizan solo un pequeño subconjunto, se crea un overhead innecesario.
Este problema común ocurre cuando se espera que futuras pruebas necesiten una configuración extensa, o cuando se continúa añadiendo a una configuración existente sin evaluar lo que realmente se necesita.
Las pruebas son más difíciles de entender porque contienen un contexto irrelevante, y más lentas de ejecutar porque se inician objetos que no se utilizan.
Código de muestreo
equivocado 🙂
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());
}
}
Derecho
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);
}
}
Detección
- [x] Semi-automático
Puede detectar este olor comparando lo que está configurado en los métodos de configuración con lo que se utiliza en cada prueba.
Busque pruebas que utilicen menos del 50% de los objetos inicializados.
Las herramientas de cobertura de código pueden ayudar a identificar los objetos de configuración no utilizados mostrando qué partes de la configuración no se ejecutan por ciertas pruebas.
Si te encuentras escribiendo condicionales en la configuración para crear diferentes contextos, es una señal clara de que necesitas una configuración específica de prueba en su lugar.
Tags ️
- La prueba
Nivel
- [x] El Intermedio
¿Por qué es importante la bijeción ️
Cada prueba debe reflejar un escenario específico del mundo real.
Las configuraciones hinchadas rompen esta claridad, lo que dificulta ver lo que se está probando y aumenta la posibilidad de errores.
Este rotoBijeciónhace que las pruebas sean más difíciles de entender porque no se puede determinar qué aspectos de la configuración son críticos para la prueba y cuáles son sólo ruido.
Cuando una prueba falla, pasarás más tiempo investigando dependencias que pueden no ser relevantes para el fracaso.
La prueba se vuelve más frágil ya que los cambios a los objetos no utilizados todavía pueden romper las pruebas si esos objetos participan en el proceso de configuración.
La generación
Los generadores de código de IA a menudo crean este olor cuando generan equipos de prueba completos que tratan de cubrir todos los escenarios posibles.
Priorizan la completitud sobre el enfoque, lo que resulta en métodos de configuración hinchados que inicializan más objetos de los necesarios para las pruebas individuales.
Detección
La IA puede detectar este olor con instrucciones simples como "Optimizar mi configuración de prueba solo para incluir lo que se necesita para cada prueba".
Las herramientas modernas de IA pueden comparar el código de configuración con el uso del método de prueba y sugerir refactorings dirigidos, separando la configuración compartida de la configuración específica de la prueba.
¡Pruébelo!
Recuerda: los asistentes de IA cometen muchos errores
Prompt sugerido: romper las pruebas y la configuración
Prompt sugerido: romper las pruebas y la configuración
Without Proper Instructions |
With Specific Instructions |
---|---|
Conclusión
Las configuraciones de prueba sobrecargadas que inicializan objetos necesarios solo para unos pocos tests hacen que su suite de pruebas sea más difícil de entender y mantener.
Cuando crea configuraciones focalizadas que contienen sólo lo que cada prueba necesita, mejora la claridad, la velocidad y la fiabilidad de sus pruebas.
Recuerde que las pruebas tienen como objetivo documentar el comportamiento a través de ejemplos ySustituye los comentarios.
Demasiado contexto irrelevante hace que esos ejemplos sean menos legibles.Pruebas limpias cuentan una historia clara sin distracciones innecesarias.
Relaciones
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
Más información
Disclaimer
Los olores son míosOpinión.
Créditos
Fotografía porMarcin SimónidesesUnosplash
Si tiene que crear mucha estructura antes de una prueba, tal vez esté probando a través de demasiadas capas
Si tiene que crear mucha estructura antes de una prueba, tal vez esté probando a través de demasiadas capas
por James Shore
Este artículo es parte de la serie CodeSmell.