¿Cómo deben las pruebas unitarias configurar las fuentes de datos cuando no se ejecutan en un servidor de aplicaciones?

StackOverflow https://stackoverflow.com/questions/831760

Pregunta

Gracias a todos por su ayuda. Varios de ustedes publicaron (como debería haber esperado) respuestas que indican que todo mi enfoque fue incorrecto, o que el código de bajo nivel nunca debería tener que saber si se está ejecutando o no en un contenedor. Tendería a estar de acuerdo. Sin embargo, estoy tratando con una aplicación heredada compleja y no tengo la opción de hacer una refactorización importante para el problema actual.

Permítanme retroceder y hacer la pregunta que motivó mi pregunta original.

Tengo una aplicación heredada que se ejecuta en JBoss y he realizado algunas modificaciones en el código de nivel inferior. He creado una prueba unitaria para mi modificación. Para ejecutar la prueba, necesito conectarme a una base de datos.

El código heredado obtiene la fuente de datos de esta manera:

(jndiName es una cadena definida)

Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

Mi problema es que cuando ejecuto este código bajo prueba unitaria, el Contexto no tiene fuentes de datos definidas. Mi solución a esto fue tratar de ver si me estoy ejecutando bajo el servidor de aplicaciones y, si no, crear el DataSource de prueba y devolverlo. Si ejecuto el servidor de aplicaciones, entonces uso el código anterior.

Entonces, mi pregunta real es: ¿Cuál es la forma correcta de hacer esto? ¿Hay alguna forma aprobada de que la prueba unitaria pueda configurar el contexto para devolver la fuente de datos adecuada para que el código que se está probando no necesite saber dónde se está ejecutando?


Para contexto: MI PREGUNTA ORIGINAL:

Tengo un código Java que necesita saber si se ejecuta o no con JBoss. ¿Existe una forma canónica para que el código indique si se está ejecutando en un contenedor?

Mi primer enfoque se desarrolló a través de la experimentación y consiste en obtener el contexto inicial y probar que puede buscar ciertos valores.

private boolean isRunningUnderJBoss(Context ctx) {
        boolean runningUnderJBoss = false;
        try {
            // The following invokes a naming exception when not running under
            // JBoss.
            ctx.getNameInNamespace();

            // The URL packages must contain the string "jboss".
            String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs");
            if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) {
                runningUnderJBoss = true;
            }
        } catch (Exception e) {
            // If we get there, we are not under JBoss
            runningUnderJBoss = false;
        }
        return runningUnderJBoss;
    }

Context ctx = new InitialContext();
if (isRunningUnderJboss(ctx)
{
.........

Ahora, esto parece funcionar, pero se siente como un truco. ¿Cuál es el " correcto " manera de hacer esto? Idealmente, me gustaría una forma que funcione con una variedad de servidores de aplicaciones, no solo con JBoss.

¿Fue útil?

Solución

Todo el enfoque se siente mal dirigido a mí. Si su aplicación necesita saber en qué contenedor se está ejecutando, está haciendo algo mal.

Cuando uso Spring puedo pasar de Tomcat a WebLogic y viceversa sin cambiar nada. Estoy seguro de que con la configuración adecuada también podría hacer el mismo truco con JBOSS. Ese es el objetivo para el que dispararía.

Otros consejos

Todo el concepto está de vuelta al frente. El código de nivel inferior no debería estar haciendo este tipo de pruebas. Si necesita una implementación diferente, páselo a un punto relevante.

Alguna combinación de Inyección de dependencias (ya sea a través de Spring, archivos de configuración o argumentos de programa) y el Patrón de fábrica generalmente funcionaría mejor.

Como ejemplo, paso un argumento a mis scripts Ant que configuran archivos de configuración dependiendo de si el oído o la guerra entrarán en un entorno de desarrollo, prueba o producción.

Quizás algo como esto (feo pero puede funcionar)

 private void isRunningOn( String thatServerName ) { 

     String uniqueClassName = getSpecialClassNameFor( thatServerName );
     try { 
         Class.forName( uniqueClassName );
     } catch ( ClassNotFoudException cnfe ) { 
         return false;
     }
     return true;
 } 

El método getSpecialClassNameFor devolvería una clase que es única para cada servidor de aplicaciones (y puede devolver nuevos nombres de clase cuando se agregan más servidores de aplicaciones)

Luego lo usas como:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

¿Quién construye el InitialContext? Su construcción debe estar fuera del código que está intentando probar, o de lo contrario no podrá burlarse del contexto.

Dado que usted dijo que está trabajando en una aplicación heredada, primero refactorice el código para que pueda inyectar fácilmente el contexto o la fuente de datos a la clase. Entonces puede escribir más fácilmente las pruebas para esa clase.

Puede hacer la transición del código heredado al tener dos constructores, como en el código siguiente, hasta que haya refactorizado el código que construye la clase. De esta manera, puede probar Foo más fácilmente y puede mantener el código que usa Foo sin cambios. Luego, puede refactorizar lentamente el código, de modo que el antiguo constructor se elimine por completo y todas las dependencias se inyecten.

public class Foo {
  private final DataSource dataSource;
  public Foo() { // production code calls this - no changes needed to callers
    Context ctx = new InitialContext();
    this.dataSource = (DataSource) ctx.lookup(jndiName);
  }
  public Foo(DataSource dataSource) { // test code calls this
    this.dataSource = dataSource;
  }
  // methods that use dataSource
}

Pero antes de comenzar a hacer esa refactorización, debe tener algunas pruebas de integración para cubrir su espalda. De lo contrario, no puede saber si incluso las refactorizaciones simples, como mover la búsqueda de DataSource al constructor, rompen algo. Luego, cuando el código mejore, sea más comprobable, puede escribir pruebas unitarias. (Por definición, si una prueba toca el sistema de archivos, la red o la base de datos, no es una prueba unitaria, es una prueba de integración).

El beneficio de las pruebas unitarias es que se ejecutan rápidamente, cientos o miles por segundo, y están muy enfocadas en probar solo un comportamiento a la vez. Eso hace posible que se ejecute a menudo (si duda en ejecutar todas las pruebas unitarias después de cambiar una línea, se ejecutan demasiado lentamente) para que obtenga comentarios rápidos. Y debido a que están muy enfocados, sabrás con solo mirar el nombre de la prueba que falla exactamente dónde está el error en el código de producción.

El beneficio de las pruebas de integración es que se aseguran de que todas las partes estén conectadas correctamente. Eso también es importante, pero no puede ejecutarlos muy a menudo porque cosas como tocar la base de datos los hace muy lentos. Pero aún debe ejecutarlos al menos una vez al día en su servidor de integración continua.

Hay un par de formas de abordar este problema. Una es pasar un objeto Context a la clase cuando está bajo prueba unitaria. Si no puede cambiar la firma del método, refactorice la creación del contexto inicial a un método protegido y pruebe una subclase que devuelva el objeto de contexto simulado anulando el método. Eso al menos puede poner a prueba la clase para que pueda refactorizar a mejores alternativas desde allí.

La siguiente opción es hacer que las conexiones de la base de datos sean una fábrica que pueda saber si está en un contenedor o no, y hacer lo apropiado en cada caso.

Una cosa para pensar es: una vez que tenga esta conexión de base de datos fuera del contenedor, ¿qué va a hacer con ella? Es más fácil, pero no es una prueba unitaria si tiene que llevar toda la capa de acceso a datos.

Para obtener más ayuda en esta dirección de mover el código heredado bajo la prueba de la unidad, le sugiero que consulte Trabajar eficazmente con código heredado .

Una manera limpia de hacer esto sería tener oyentes de ciclo de vida configurados en web.xml . Estos pueden establecer banderas globales si lo desea. Por ejemplo, podría definir un ServletContextListener en su web.xml y en el método contextInitialized , establezca un indicador global que está ejecutando dentro de un contenedor. Si el indicador global no está configurado, entonces no se está ejecutando dentro de un contenedor.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top