Acabo de leer un extracto del libro "Crecimiento de software orientado a objetos" que explica algunas razones por las cuales no se recomienda burlarse de la clase concreta.
Aquí hay un código de muestra de una prueba unitaria para la clase MusicCentre:
public class MusicCentreTest {
@Test public void startsCdPlayerAtTimeRequested() {
final MutableTime scheduledTime = new MutableTime();
CdPlayer player = new CdPlayer() {
@Override
public void scheduleToStartAt(Time startTime) {
scheduledTime.set(startTime);
}
}
MusicCentre centre = new MusicCentre(player);
centre.startMediaAt(LATER);
assertEquals(LATER, scheduledTime.get());
}
}
Y su primera explicación:
El problema con este enfoque es que deja implícita la relación entre los objetos. Espero que ya hayamos aclarado que la intención del desarrollo basado en pruebas con objetos simulados es descubrir relaciones entre objetos. Si subclase, no hay nada en el código de dominio para hacer visible dicha relación, solo métodos en un objeto. Esto hace que sea más difícil ver si el servicio que respalda esta relación podría ser relevante en otro lugar y tendré que volver a hacer el análisis la próxima vez que trabaje con la clase.
No puedo entender exactamente lo que quiere decir cuando dice:
Esto hace que sea más difícil ver si el servicio que respalda esta relación podría ser relevante en otro lugar y tendré que volver a hacer el análisis la próxima vez que trabaje con la clase.
Entiendo que el servicio corresponde al MusicCentre
método llamado startMediaAt
.
¿Qué quiere decir con "en otro lugar"?
El extracto completo está aquí: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html
Respuestas:
El autor de esa publicación está promoviendo el uso de interfaces sobre el uso de clases miembro.
It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.
La relación que le preocupa volver a descubrir más tarde es el hecho de que la clase MediaCentre no necesita todo el objeto CdPlayer. Su afirmación es que al usar una interfaz (presumiblemente limitada a solo iniciar | detener) es más fácil entender cuál es realmente la interacción.
"en otro lugar" simplemente significa que otros objetos pueden tener relaciones igualmente limitadas, y no se requiere el objeto miembro completo; un subconjunto de la funcionalidad envuelta a través de una interfaz debería ser suficiente.
El reclamo comienza a tener más sentido cuando explota toda la funcionalidad potencial:
Ahora su afirmación de "Solo necesito comenzar y detener" tiene más sentido. El uso del objeto miembro concreto en lugar de una interfaz hace que sea menos claro para los futuros desarrolladores lo que realmente se necesita. Ejecutar pruebas unitarias de MediaCentre en todas las demás funciones dentro de CdPlayer es un desperdicio de esfuerzo de prueba ya que pertenecen al estado "no me importa". Si la
Record
función no funcionaba en este caso, realmente no nos importa, ya que no es necesaria. Pero un futuro mantenedor no necesariamente sabría eso basado en el código, tal como está escrito.En última instancia, la premisa del autor es usar solo lo que se necesita y dejar en claro a los futuros mantenedores lo que se requería antes. El objetivo es minimizar el reproceso / reanálisis del módulo de código durante el mantenimiento posterior.
fuente
Después de pensarlo mucho, obtengo una posible interpretación de esta cita:
El "servicio" citado corresponde al "hecho de la programación". Esto podría expresarse mediante una interfaz bien nombrada y "centrada en un rol" llamada "ScheduledDevice" o expresada implícitamente por una implementación de un método concreto que no dependa de ninguna interfaz.
En el ejemplo anterior, la programación se expresa por todo el objeto con todas las funciones llamado
CDPlayer
. Por lo tanto, todavía conduce a una relación implícita entreMusicCentre
"hecho de programación".Entonces, si comenzamos a inyectar clases concretas y burlarnos de ellas en objetos de alto nivel; cuando queremos probar estos, tenemos que analizar cada objeto "concreto" inyectado para ver si presenta una relación específica que TENEMOS QUE BURLAR porque están OCULTOS (implícitos). Por el contrario, la codificación SIEMPRE a través de la interfaz permite al desarrollador averiguar directamente qué tipo de relación está a punto de cumplir el objeto de alto nivel y, por lo tanto, detectar características que deben burlarse para aislar la prueba unitaria.
fuente
El servicio al que me refería aquí era CDPlayer.scheduleToStartAt (). Eso es lo que llama MediaCentre: el colaborador que necesita para funcionar. MediaCentre es el objeto bajo prueba.
La idea es que si hago explícito de qué depende MediaCentre, no una clase de implementación, puedo darle un nombre a ese rol de dependencia y hablar sobre ello. Todo lo que MediaCentre necesita saber es que habla con ScheduledDevices. A medida que el resto del sistema cambia, no necesitaré alterar el MediaCentre a menos que cambien sus características.
¿Eso ayuda?
fuente