Deberías dividir la clase en cuestión.
Cada clase debe hacer una tarea simple. Si su tarea es demasiado complicada para probar, entonces la tarea que realiza la clase es demasiado grande.
Ignorando la tontería de este diseño:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
ACTUALIZAR
El problema con los métodos de copia en una clase es que está violando la encapsulación. Su prueba debe estar verificando si el comportamiento externo del objeto coincide o no con las especificaciones. Lo que ocurra dentro del objeto no es asunto suyo.
El hecho de que FullName use FirstName y LastName es un detalle de implementación. Nada fuera de la clase debería importarle que eso sea cierto. Al burlarse de los métodos públicos para probar el objeto, está asumiendo que se implementa ese objeto.
En algún momento en el futuro, esa suposición puede dejar de ser correcta. Quizás toda la lógica del nombre se reubicará en un objeto Nombre que la persona simplemente llama. Quizás FullName accederá directamente a las variables miembro first_name y last_name en lugar de llamar a FirstName y LastName.
La segunda pregunta es por qué siente la necesidad de hacerlo. Después de todo, tu clase de persona podría probarse algo como:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
No deberías sentir la necesidad de nada para este ejemplo. Si lo haces, entonces estás feliz y bien ... ¡para! No hay ningún beneficio en burlarse de los métodos allí porque de todos modos tienes control sobre lo que hay en ellos.
El único caso en el que parece tener sentido para los métodos que FullName utiliza para burlarse es si de alguna manera FirstName () y LastName () eran operaciones no triviales. Tal vez está escribiendo uno de esos generadores de nombres aleatorios, o FirstName y LastName consultan la base de datos para obtener una respuesta, o algo así. Pero si eso es lo que sucede, sugiere que el objeto está haciendo algo que no pertenece a la clase Persona.
Dicho de otra manera, burlándose de los métodos es tomar el objeto y partirlo en dos pedazos. Se burlan de una pieza mientras se prueba la otra pieza. Lo que está haciendo es esencialmente una ruptura ad-hoc del objeto. Si ese es el caso, simplemente rompa el objeto ya.
Si su clase es simple, no debe sentir la necesidad de burlarse de ella durante una prueba. Si su clase es lo suficientemente compleja como para sentir la necesidad de burlarse, entonces debe dividir la clase en partes más simples.
ACTUALIZAR DE NUEVO
A mi modo de ver, un objeto tiene un comportamiento externo e interno. El comportamiento externo incluye llamadas de valores devueltos a otros objetos, etc. Obviamente, cualquier cosa en esa categoría debe ser probada. (de lo contrario, ¿qué probaría?) Pero el comportamiento interno no debería ser realmente probado.
Ahora se prueba el comportamiento interno, porque es lo que resulta en el comportamiento externo. Pero no escribo pruebas directamente sobre el comportamiento interno, solo indirectamente a través del comportamiento externo.
Si quiero probar algo, creo que debe moverse para que se convierta en un comportamiento externo. Es por eso que creo que si quieres burlarte de algo, debes dividir el objeto para que lo que quieras burlarte esté ahora en el comportamiento externo de los objetos en cuestión.
Pero, ¿qué diferencia hace? Si FirstName () y LastName () son miembros de otro objeto, ¿realmente cambia el problema de FullName ()? Si decidimos que es necesario burlarse de FirstName y LastName, ¿es realmente útil para ellos estar en otro objeto?
Creo que si usas tu enfoque burlón, entonces creas una costura en el objeto. Tiene funciones como FirstName () y LastName () que se comunican directamente con una fuente de datos externa. También tiene FullName () que no tiene. Pero dado que todos están en la misma clase, eso no es evidente. Se supone que algunas piezas no deben acceder directamente a la fuente de datos y otras sí. Su código será más claro si solo separa esos dos grupos.
EDITAR
Retrocedamos un paso y preguntemos: ¿por qué nos burlamos de los objetos cuando probamos?
- Haga que las pruebas se ejecuten de manera consistente (evite acceder a cosas que cambian de una ejecución a otra)
- Evite acceder a recursos caros (no acceda a servicios de terceros, etc.)
- Simplifica el sistema bajo prueba
- Facilite la prueba de todos los escenarios posibles (es decir, cosas como simular fallas, etc.)
- Evite depender de los detalles de otras piezas de código para que los cambios en esas otras piezas de código no rompan esta prueba.
Ahora, creo que las razones 1-4 no se aplican a este escenario. Burlarse de la fuente externa cuando se prueba el nombre completo se ocupa de todas esas razones para burlarse. La única pieza que no se maneja es la simplicidad, pero parece que el objeto es lo suficientemente simple, eso no es una preocupación.
Creo que su preocupación es la razón número 5. La preocupación es que en algún momento en el futuro cambiar la implementación de FirstName y LastName interrumpirá la prueba. En el futuro, FirstName y LastName pueden obtener los nombres de una ubicación o fuente diferente. Pero FullName probablemente siempre lo será FirstName() + " " + LastName()
. Es por eso que desea probar FullName burlándose de FirstName y LastName.
Lo que tienes entonces es un subconjunto del objeto persona que es más probable que cambie que los demás. El resto del objeto usa este subconjunto. Ese subconjunto actualmente obtiene sus datos utilizando una fuente, pero puede obtener esos datos de una manera completamente diferente en una fecha posterior. Pero para mí parece que ese subconjunto es un objeto distinto que intenta salir.
Me parece que si te burlas del método del objeto, lo estás dividiendo. Pero lo está haciendo de manera ad-hoc. Su código no deja en claro que hay dos piezas distintas dentro de su objeto Persona. Entonces, simplemente divida ese objeto en su código real, para que quede claro al leer su código lo que está sucediendo. Elija la división real del objeto que tenga sentido y no intente dividir el objeto de manera diferente para cada prueba.
Sospecho que puede objetar dividir su objeto, pero ¿por qué?
EDITAR
Estaba equivocado.
Debería dividir los objetos en lugar de introducir divisiones ad-hoc burlándose de métodos individuales. Sin embargo, estaba demasiado concentrado en el único método para dividir objetos. Sin embargo, OO proporciona múltiples métodos para dividir un objeto.
Lo que propondría:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
Tal vez eso es lo que estabas haciendo todo el tiempo. Pero no creo que este método tenga los problemas que vi con los métodos de burla porque hemos delineado claramente de qué lado está cada método. Y al usar la herencia, evitamos la incomodidad que surgiría si usáramos un objeto contenedor adicional.
Esto introduce cierta complejidad, y solo para un par de funciones de utilidad probablemente las probaría burlándome de la fuente de terceros subyacente. Claro, corren un mayor riesgo de romperse, pero no vale la pena reorganizarlos. Si tienes un objeto lo suficientemente complejo que necesitas dividirlo, entonces creo que algo como esto es una buena idea.