¿Cómo probar la capa de acceso a datos?

17

Tengo un método DAO que utiliza Spring para el acceso JDBC. Calcula la tasa de éxito de un vendedor de vender un artículo.

Aquí está el código:

public BigDecimal getSellingSuccessRate(long seller_id) {
    String sql = "SELECT SUM(IF(sold_price IS NOT NULL, 1, 0))/SUM(1) 
                  FROM transaction WHERE seller_id = ?";
    Object[] args = {seller_id};
    return getJdbcTemplate().queryForObject(sql, args, BigDecimal.class);
}

¿Cómo debo probar este método o cualquier método DAO con JUnit? ¿Cuáles son algunas de las mejores prácticas para probar la lógica de acceso a datos? Estoy pensando en probarlo en una base de datos incorporable cargada con algunos datos, pero ¿no deberíamos hacer pruebas de integración similares a un entorno de producción en términos de RDBMS y el esquema?

Miguel
fuente
Echa un vistazo a DBUnit . Está hecho específicamente para resolver su problema.
Sergio

Respuestas:

15

El problema con el uso de una base de datos 'real' para pruebas unitarias es la configuración, desmontaje y aislamiento de las pruebas. No desea tener que crear una base de datos MySQL completamente nueva y crear tablas y datos solo para una prueba unitaria. Los problemas con esto tienen que ver con la naturaleza externa de la base de datos y su base de datos de prueba está inactiva, sus pruebas unitarias fallan. También hay problemas para asegurarse de tener una base de datos única para realizar pruebas. Se pueden superar, pero hay una respuesta más simple.

Burlarse de la base de datos es una opción, sin embargo, no prueba las consultas reales que se ejecutan. Puede usarse como una solución mucho más simple cuando desea asegurarse de que los datos del DAO pasan por el sistema correctamente. Pero para probar el DAO en sí mismo, necesita algo detrás del DAO que tiene los datos y las consultas se ejecutan correctamente.

Lo primero que debe hacer es usar una base de datos en memoria. HyperSQL es una excelente opción para esto porque tiene la capacidad de emular el dialecto de otra base de datos, de modo que las pequeñas diferencias entre las bases de datos permanecen iguales (tipos de datos, funciones y similares). hsqldb también tiene algunas características interesantes para las pruebas unitarias.

db.url=jdbc:hsqldb:file:src/test/resources/testData;shutdown=true;

Esto carga el estado de la base de datos (las tablas, los datos iniciales) del testDataarchivo. shutdown=truecerrará automáticamente la base de datos cuando se cierre la última conexión.

Utilizando la inyección de dependencia , haga que las pruebas unitarias seleccionen una base de datos diferente de la que usan las compilaciones de producción (o prueba o local).

Su DAO luego usa la base de datos inyectada para la cual puede iniciar pruebas contra la base de datos.

Las pruebas unitarias se verán así (un montón de cosas aburridas no incluidas por brevedad):

    @Before
    public void setUpDB() {
        DBConnection connection = new DBConnection();
        try {
            conn = connection.getDBConnection();
            insert = conn.prepareStatement("INSERT INTO data (txt, ts, active) VALUES (?, ?, ?)");
        } catch (SQLException e) {
            e.printStackTrace();
            fail("Error instantiating database table: " + e.getMessage());
        }
    }

    @After
    public void tearDown() {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void addData(String txt, Timestamp ts, boolean active) throws Exception {
        insert.setString(1, txt);
        insert.setTimestamp(2, ts);
        insert.setBoolean(3, active);
        insert.execute();
    }

    @Test
    public void testGetData() throws Exception {
        // load data
        Calendar time = Calendar.getInstance();
        long now = time.getTimeInMillis();
        long then1h = now - (60 * 60 * 1000);  // one hour ago
        long then2m = now - (60 * 1000 * 2);   // two minutes ago
        addData("active_foo", new Timestamp(then1h), true);     // active but old
        addData("inactive_bar", new Timestamp(then1h), false);  // inactive and old
        addData("active_quz", new Timestamp(then2m), true);     // active and new
        addData("inactive_baz", new Timestamp(then2m), false);  // inactive and new

        DataAccess dao = new DataAccess();
        int count = 0;
        for (Data data : dao.getData()) {
            count++;
            assertTrue(data.getTxt().startsWith("active"));
        }

        assertEquals("got back " + count + " rows instead of 1", count, 1);
    }

Y por lo tanto, tiene una prueba unitaria que llama al DAO y está utilizando los datos que se configuraron en una base de datos sobre la marcha que existe durante la duración de la prueba. No tiene que preocuparse por los recursos externos o el estado de la base de datos antes de la ejecución, o la restauración a un estado conocido (bueno, el "estado conocido" es "no existe", que es trivial para volver).

DBUnit puede hacer que gran parte de lo que describí sea un proceso más simple para configurar la base de datos, crear las tablas y cargar los datos. Si iba a necesitar usar la base de datos real por alguna razón, esta es, con mucho, la mejor herramienta para usar.

El código anterior es parte de un proyecto maven que escribí para la prueba de concepto TestingWithHsqldb en github


fuente
2
No sabía sobre la parte en que HSQL puede burlarse del dialecto de otro proveedor de db. Gracias.
Michael
1
@Dog esto se puede hacer a través de las propiedades de la base de datos , como las sql.syntax_mys=trueque cambian la forma en que funciona hsqldb: "Esta propiedad, cuando se establece como verdadera, habilita la compatibilidad con los tipos TEXT y AUTO_INCREMENT y también permite la compatibilidad con algunos otros aspectos de este dialecto". while sql.syntax_ora=true"Esta propiedad, cuando se establece como verdadera, permite la compatibilidad con tipos no estándar. También permite la sintaxis DUAL, ROWNUM, NEXTVAL y CURRVAL y también permite la compatibilidad con algunos otros aspectos de este dialecto".
DBUnit es el camino :)
Silviu Burcea
@SilviuBurcea DBUnit sin duda facilita mucho la 'plomería' de configurar un entorno de prueba de base de datos complejo mucho más fácil que hacerlo a mano. A veces es útil saber cómo hacerlo a mano si es necesario (el enfoque 'a mano' mencionado anteriormente podría migrarse a otros idiomas donde DBUnit no es una opción).
Puedes echar un vistazo a Acolyte
cchantep
2

Primero, nunca debe hacer pruebas en un entorno de producción. Debe tener un entorno de prueba que refleje su entorno de producción y hacer pruebas de integración allí.

Si haces eso, entonces puedes hacer varias cosas.

  • Escriba pruebas unitarias que prueben para ver si el SQL apropiado se está enviando a un elemento simulado utilizando un marco simulado como Mockito. Esto asegurará que su método está haciendo lo que se supone que debe hacer y elimina la integración de la imagen.
  • Escriba scripts de SQL de prueba que demuestren la idoneidad del SQL que probó en sus pruebas unitarias. Esto puede ayudar con cualquier problema de ajuste que pueda encontrar, ya que también puede ejecutar explicaciones y demás según sus scripts de prueba.
  • Use DBUnit, como lo menciona @Sergio.
Matthew Flynn
fuente
Woops cuando dije entorno de producción realmente me refería a una simulación del mismo. Gracias por su respuesta, echaré un vistazo a Mockito porque eso es algo que también he querido aprender.
Michael
1

En nuestro proyecto, cada desarrollador ejecuta una base de datos vacía, su estructura es la misma que la base de datos de producción.

En cada prueba unitaria TestInitialize, creamos una conexión y transacción con la base de datos más algunos objetos predeterminados que necesitamos para cada prueba. Y todo se revierte después del final de cada método o clase.

De esta manera, es posible probar la capa sql. De hecho, cada consulta o llamada a la base de datos debe probarse de esta manera.

La desventaja es que es lento, por lo que lo colocamos en un proyecto separado de nuestras pruebas unitarias regulares. Es posible acelerar esto usando una base de datos en memoria, pero la idea sigue siendo la misma.

Carra
fuente
Si usa una base de datos en memoria, entonces se puede usar un enfoque de creación de caída antes de que se ejecuten todas las suites de prueba en lugar de la transacción de reversión correcta, que es mucho más rápido.
Downhillski
Nunca pensé hacerlo así antes. En nuestras pruebas, la mayoría de las pruebas crean un usuario 'x', que es único. Crear una base de datos una vez significaría cambiar las pruebas para reutilizar esos objetos.
Carra
Lo sé, estamos en la misma página y me gusta tu enfoque. su enfoque garantiza que cada caso de prueba se pueda ejecutar independientemente, independientemente del orden, y cada vez que se ejecute, el estado de la tabla de datos es el mismo.
Downhillski el
Eso es correcto, el orden no importa entonces. Hemos visto que las pruebas fallan antes porque el orden de ejecución de la prueba unitaria es diferente en nuestra PC de compilación y en nuestra máquina local.
Carra