Estoy en un proyecto de sistema distribuido escrito en Java donde tenemos algunas clases que corresponden a objetos comerciales muy complejos del mundo real. Estos objetos tienen muchos métodos que corresponden a acciones que el usuario (o algún otro agente) puede aplicar a esos objetos. Como resultado, estas clases se volvieron muy complejas.
El enfoque de la arquitectura general del sistema ha llevado a muchos comportamientos concentrados en algunas clases y muchos escenarios de interacción posibles.
Como ejemplo y para mantener las cosas fáciles y claras, digamos que Robot y Car fueron clases en mi proyecto.
Entonces, en la clase Robot, tendría muchos métodos en el siguiente patrón:
- dormir(); isSleepAvaliable ();
- despierto(); isAwakeAvaliable ();
- caminar (dirección); isWalkAvaliable ();
- disparar (dirección); isShootAvaliable ();
- turnOnAlert (); isTurnOnAlertAvailable ();
- turnOffAlert (); isTurnOffAlertAvailable ();
- recargar(); isRechargeAvailable ();
- apagado(); isPowerOffAvailable ();
- stepInCar (Auto); isStepInCarAvailable ();
- stepOutCar (Auto); isStepOutCarAvailable ();
- auto destrucción(); isSelfDestructAvailable ();
- morir(); isDieAvailable ();
- isAlive (); está despierto(); isAlertOn (); getBatteryLevel (); getCurrentRidingCar (); getAmmo ();
- ...
En la clase Car, sería similar:
- encender(); isTurnOnAvaliable ();
- apagar(); isTurnOffAvaliable ();
- caminar (dirección); isWalkAvaliable ();
- repostar(); isRefuelAvailable ();
- auto destrucción(); isSelfDestructAvailable ();
- choque(); isCrashAvailable ();
- isOperational (); Está encendido(); getFuelLevel (); getCurrentPassenger ();
- ...
Cada uno de estos (Robot y Auto) se implementa como una máquina de estado, donde algunas acciones son posibles en algunos estados y otras no. Las acciones cambian el estado del objeto. Los métodos de acciones se lanzan IllegalStateException
cuando se llama en un estado no válido y los isXXXAvailable()
métodos indican si la acción es posible en ese momento. Aunque algunos son fácilmente deducibles del estado (p. Ej., En el estado de sueño, la vigilia está disponible), otros no (para disparar, debe estar despierto, vivo, tener munición y no viajar en automóvil).
Además, las interacciones entre los objetos también son complejas. Por ejemplo, el automóvil solo puede contener un pasajero Robot, por lo que si otro intenta ingresar, se debe lanzar una excepción; Si el auto choca, el pasajero debe morir; Si el robot está muerto dentro de un vehículo, no puede salir, incluso si el auto está bien; Si el robot está dentro de un automóvil, no puede entrar en otro antes de salir; etc.
El resultado de esto, es como ya dije, estas clases se volvieron realmente complejas. Para empeorar las cosas, hay cientos de escenarios posibles cuando el robot y el automóvil interactúan. Además, gran parte de esa lógica necesita acceder a datos remotos en otros sistemas. El resultado es que las pruebas unitarias se volvieron muy difíciles y tenemos muchos problemas de prueba, uno causando al otro en un círculo vicioso:
- Las configuraciones de los casos de prueba son muy complejas, ya que necesitan crear un mundo significativamente complejo para ejercitarse.
- El número de pruebas es enorme.
- El conjunto de pruebas tarda algunas horas en ejecutarse.
- Nuestra cobertura de prueba es muy baja.
- El código de prueba tiende a escribirse semanas o meses después del código que prueban, o nunca en absoluto.
- Muchas pruebas también se rompen, principalmente porque los requisitos del código probado cambiaron.
- Algunos escenarios son tan complejos que fallan en el tiempo de espera durante la configuración (configuramos un tiempo de espera en cada prueba, en el peor de los casos, 2 minutos de duración e incluso este tiempo de espera, nos aseguramos de que no sea un bucle infinito).
- Los errores se deslizan regularmente en el entorno de producción.
Ese escenario de Robot y Automóvil es una gran simplificación excesiva de lo que tenemos en realidad. Claramente, esta situación no es manejable. Por lo tanto, pido ayuda y sugerencias para: 1, reducir la complejidad de las clases; 2. Simplificar los escenarios de interacciones entre mis objetos; 3. Reduzca el tiempo de prueba y la cantidad de código que se probará.
EDITAR:
Creo que no estaba claro acerca de las máquinas de estado. el robot es en sí mismo una máquina de estados, con estados "durmiendo", "despierto", "recargando", "muerto", etc. El automóvil es otra máquina de estados.
EDITAR 2: en caso de que tenga curiosidad sobre cuál es realmente mi sistema, las clases que interactúan son cosas como Servidor, Dirección IP, Disco, Copia de seguridad, Usuario, Licencia de software, etc. El escenario Robot y Coche es solo un caso que encontré eso sería lo suficientemente simple como para explicar mi problema.