java - test - ¿Cómo evitar los usuarios en el desarrollo basado en pruebas?
tdd ejemplo java (6)
En primer lugar, ¿conoce la clase java.awt.Rectangle
que explica cómo se trata este problema en la Biblioteca de tiempo de ejecución de Java?
En segundo lugar, creo que los valores reales de TDD son primero que aleja su enfoque del "cómo hago este detalle específico con los datos que supongo que está presente así" a "cómo llamo al código y qué resultado hago". Espero volver ". El enfoque tradicional es "arreglar los detalles, y luego descubriremos cómo llamar al código", y hacerlo al revés le permite encontrar más rápido si algo simplemente no puede hacerse o no.
Esto es muy importante en el diseño de las API, que probablemente también sea la razón por la que ha encontrado que su resultado está débilmente acoplado.
El segundo valor es que sus pruebas son "comentarios vivientes" en lugar de comentarios antiguos, intactos. La prueba muestra cómo debe llamarse su código y puede verificar instantáneamente que se comporta como se especifica. Esto no es relevante para lo que pregunta, pero debe demostrar que las pruebas tienen más propósitos que llamar ciegamente algún código que haya escrito hace un tiempo.
Estoy aprendiendo el desarrollo impulsado por pruebas, y me he dado cuenta de que obliga a los objetos débilmente acoplados, lo cual es básicamente una buena cosa. Sin embargo, esto también a veces me obliga a proporcionar accesos para propiedades que normalmente no necesitaría, y creo que la mayoría de las personas que están en SO están de acuerdo en que los accesorios generalmente son un signo de mal diseño. ¿Es esto inevitable al hacer TDD?
Aquí hay un ejemplo, el código de dibujo simplificado de una entidad sin TDD:
class Entity {
private int x;
private int y;
private int width;
private int height;
void draw(Graphics g) {
g.drawRect(x, y, width, height);
}
}
La entidad sabe cómo dibujarse a sí misma, eso es bueno. Todo en un lugar. Sin embargo, estoy haciendo TDD, así que quiero verificar si mi entidad fue movida correctamente por el método "fall ()" que estoy a punto de implementar. Aquí está cómo se vería el caso de prueba:
@Test
public void entityFalls() {
Entity e = new Entity();
int previousY = e.getY();
e.fall();
assertTrue(previousY < e.getY());
}
Tengo que mirar el estado interno del objeto (bueno, al menos lógicamente) y ver si la posición se actualizó correctamente. Como en realidad está en el camino (no quiero que mis casos de prueba dependan de mi biblioteca de gráficos), moví el código de dibujo a una clase "Renderer":
class Renderer {
void drawEntity(Graphics g, Entity e) {
g.drawRect(e.getX(), e.getY(), e.getWidth(), e.getHeight());
}
}
Suavemente acoplado, bien. Incluso puedo reemplazar el renderizador con uno que muestra la entidad de una manera completamente diferente. Sin embargo, tuve que exponer el estado interno de la entidad, es decir, los descriptores de acceso de todas sus propiedades, de modo que el renderizador pudiera leerlo.
Siento que esto fue específicamente forzado por TDD. ¿Qué puedo hacer sobre esto? ¿Es mi diseño aceptable? ¿Necesita Java la palabra clave "friend" de C ++?
Actualizar:
¡Gracias por tus valiosas aportaciones hasta ahora! Sin embargo, me temo que he elegido un mal ejemplo para ilustrar mi problema. Esto fue completamente inventado, ahora demostraré uno que está más cerca de mi código real:
@Test
public void entityFalls() {
game.tick();
Entity initialEntity = mockRenderer.currentEntity;
int numTicks = mockRenderer.gameArea.height
- mockRenderer.currentEntity.getHeight();
for (int i = 0; i < numTicks; i++)
game.tick();
assertSame(initialEntity, mockRenderer.currentEntity);
game.tick();
assertNotSame(initialEntity, mockRenderer.currentEntity);
assertEquals(initialEntity.getY() + initialEntity.getHeight(),
mockRenderer.gameArea.height);
}
Esta es una implementación basada en un juego de bucle de un juego donde una entidad puede caerse, entre otras cosas. Si golpea el suelo, se crea una nueva entidad.
El "mockRenderer" es una implementación simulada de una interfaz "Renderer". Este diseño fue parcialmente forzado por TDD, pero también por el hecho de que voy a escribir la interfaz de usuario en GWT, y no hay un dibujo explícito en el navegador (todavía), así que no creo que sea posible. la clase de Entidad para asumir esa responsabilidad. Además, me gustaría mantener la posibilidad de llevar el juego a Java / Swing nativo en el futuro.
Actualización 2:
Pensando en esto un poco más, tal vez está bien cómo es. Tal vez está bien que la entidad y el dibujo estén separados y que la entidad cuente otros objetos lo suficiente como para ser dibujados. Quiero decir, ¿de qué otra manera podría lograr esta separación? Y realmente no veo cómo vivir sin eso. Incluso los grandes programadores orientados a objetos usan objetos con getters / setters a veces, especialmente para algo así como un objeto de entidad. Quizás getter / setter no son todos malvados. ¿Qué piensas?
Entonces, si no hubiera movido el método de draw(Graphics)
de Entity
, tenía un código perfectamente comprobable. Solo necesita inyectar una implementación de Graphics
que informa el estado interno de la Entity
a su arnés de prueba. Solo mi opinión sin embargo.
Lo que desea probar es cómo responderá su objeto a determinadas llamadas, no cómo funcionará internamente.
Por lo tanto, no es realmente necesario (y sería una mala idea) acceder a campos / métodos no accesibles.
Si desea ver la interacción entre una llamada a un método y uno de los argumentos, debe burlarse de dicho argumento de forma tal que pueda comprobar si el método funcionó como usted quería.
Los programadores pragmáticos discuten tell, no preguntes . No quiere saber sobre la entidad, quiere que se dibuje. Dile que se dibuje en los gráficos dados.
Podría refactorizar el código anterior para que la entidad haga el dibujo, lo cual es útil si la entidad no es un rectángulo sino un círculo.
void Entity::draw(Graphics g) {
g.drawRect(x,y, width, height);
}
Luego verificaría que g tuviera los métodos correctos solicitados en sus pruebas.
Creo que tu ejemplo tiene mucho en común con el ejemplo utilizado aquí:
http://www.m3p.co.uk/blog/2009/03/08/mock-roles-not-objects-live-and-in-person/
Usando su ejemplo original y reemplazando la Entidad con Hero, fall () con jumpFrom (Balcony), y draw () como moveTo (Room) se vuelve notablemente similar. Si usa el enfoque de objeto falso, sugiere Steve Freeman, su primera implementación no fue tan mala después de todo. Creo que @Colin Hebert ha dado la mejor respuesta cuando señaló en esta dirección. No hay necesidad de exponer nada aquí. Usas los objetos simulados para verificar si el comportamiento del héroe ha tenido lugar.
Tenga en cuenta que el autor del artículo ha escrito conjuntamente un gran libro que puede ayudarlo a:
http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627
Hay algunos buenos documentos disponibles libremente como PDF por los autores sobre el uso de objetos falsos para guiar su diseño en TDD.
Usted dice que siente que la clase Renderer que se le ocurrió fue "específicamente forzada" por TDD. Entonces, veamos a dónde te llevó TDD. De una clase Rectangle que fue responsable de sus coordenadas y de dibujarse a sí misma, a una clase Rectangle que tiene la Responsabilidad Individual de mantener sus coordenadas y un Renderer que tiene la Responsabilidad Individual de, bueno, renderizar un Rectángulo. A eso nos referimos cuando decimos Test Driven: esta práctica afecta su diseño. En este caso, lo condujo a un diseño que se apegaba más estrictamente al Principio de Responsabilidad Individual , un diseño al que no habría acudido sin las pruebas. Creo que eso es algo bueno. Creo que estás practicando TDD bien, y creo que está funcionando para ti.