Recientemente estaba TDDing un método de fábrica. El método consistía en crear un objeto simple o un objeto envuelto en un decorador. El objeto decorado podría ser de uno de varios tipos, todos extendiendo StrategyClass.
En mi prueba, quería comprobar si la clase del objeto devuelto es la esperada. Eso es fácil cuando el objeto simple regresó, pero ¿qué hacer cuando está envuelto dentro de un decorador?
Codifico en PHP para poder usarlo ext/Reflection
para encontrar una clase de objeto envuelto, pero me pareció que era demasiado complicado, y de alguna manera contra las reglas de TDD.
En su lugar, decidí introducir getClassName()
que devolvería el nombre de la clase del objeto cuando se llama desde StrategyClass. Sin embargo, cuando se llama desde el decorador, devolvería el valor devuelto por el mismo método en el objeto decorado.
Algún código para hacerlo más claro:
interface StrategyInterface {
public function getClassName();
}
abstract class StrategyClass implements StrategyInterface {
public function getClassName() {
return \get_class($this);
}
}
abstract class StrategyDecorator implements StrategyInterface {
private $decorated;
public function __construct(StrategyClass $decorated) {
$this->decorated = $decorated;
}
public function getClassName() {
return $this->decorated->getClassName();
}
}
Y una prueba PHPUnit
/**
* @dataProvider providerForTestGetStrategy
* @param array $arguments
* @param string $expected
*/
public function testGetStrategy($arguments, $expected) {
$this->assertEquals(
__NAMESPACE__.'\\'.$expected,
$this->object->getStrategy($arguments)->getClassName()
)
}
//below there's another test to check if proper decorator is being used
Mi punto aquí es: ¿está bien introducir tales métodos, que no tienen otro uso que hacer que las pruebas unitarias sean más fáciles? De alguna manera no me parece bien.
Respuestas:
Mi pensamiento es no, no deberías hacer nada solo porque es necesario para la comprobabilidad. Muchas de las decisiones que las personas toman tienen un beneficio para la capacidad de prueba y la capacidad de prueba puede incluso ser el beneficio principal, pero debería ser una buena decisión de diseño sobre otros méritos. Esto significa que algunas propiedades deseadas no son comprobables. Otro ejemplo es cuando necesita saber qué tan eficiente es alguna rutina, por ejemplo, su Hashmap utiliza un rango distribuido uniformemente de valores hash; nada en la interfaz externa podría decirle eso.
En lugar de pensar, "¿estoy obteniendo la clase de estrategia correcta" piensa "la clase que obtengo realiza lo que esta especificación está tratando de probar?" Es bueno cuando puedes probar la tubería interna pero no tienes que hacerlo, solo prueba girar la perilla y ver si tienes agua fría o caliente.
fuente
Mi opinión sobre esto es: a veces hay que volver a trabajar un poco el código fuente para que sea más comprobable. No es lo ideal y no debería ser una excusa para abarrotar la interfaz con funciones que se supone que solo deben usarse para las pruebas, por lo que la moderación es generalmente la clave aquí. Tampoco desea estar en la situación en la que los usuarios de su código de repente usan las funciones de la interfaz de prueba para interacciones normales con su objeto.
Mi forma preferida de manejar esto (y tengo que disculparme por no poder mostrar cómo hacerlo en PHP, ya que codifico principalmente en lenguajes de estilo C) es proporcionar las funciones de 'prueba' de una manera que no sean expuesto al mundo exterior por el objeto mismo, pero se puede acceder por objetos derivados. Para fines de prueba, derivaría una clase que manejaría la interacción con el objeto que realmente quiero probar y que la prueba unitaria use ese objeto en particular. Un ejemplo de C ++ se vería así:
Clase de tipo de producción:
Prueba de clase auxiliar:
De esa manera, al menos está en una posición en la que no tiene que exponer las funciones de tipo 'prueba' en su objeto principal.
fuente
Hace unos meses, cuando coloqué mi lavavajillas recién comprado, salió mucha agua de su manguera, me di cuenta de que esto probablemente se deba al hecho de que se probó correctamente en la fábrica de la que provenía. No es raro ver agujeros de montaje y cosas en la maquinaria que están allí, solo con el propósito de probar en una línea de ensamblaje.
La prueba es importante, si es necesario, simplemente agregue algo para ello.
Pero intente algunas de las alternativas. Su opción basada en la reflexión no es tan mala. Podría tener un acceso virtual protegido a lo que necesita y crear una clase derivada para probar y afirmar. Quizás pueda dividir su clase y probar una clase de hoja resultante directamente. Ocultar el método de prueba con una variable del compilador en su código fuente también es una opción (apenas conozco PHP, no estoy seguro de si eso es posible en PHP).
En su contexto, puede decidir no probar la composición adecuada dentro del decorador, sino probar el comportamiento esperado que debe tener la decoración. Quizás esto se centre un poco más en el comportamiento esperado del sistema y no tanto en las especificaciones técnicas (¿qué le compra el patrón decorador desde una perspectiva funcional?).
fuente
Soy un novato TDD absoluto, pero parece depender del método que se agregue. Por lo que entiendo de TDD, se supone que sus pruebas "impulsarán" la creación de su API hasta cierto punto.
Cuando esta bien:
Cuando no está bien:
fuente