c++ - ¿Hay una manera de burlarse de QSqlQuery?
qt unit-testing (2)
Hace poco descubrí gmock y ahora estoy en proceso de "repensar todo el proceso de programación tal como está", agregando pruebas unitarias donde sea posible. Una cosa que me pareció extraña en el proceso es que el módulo QSql que claramente es una dependencia externa de nuestro código, no le da a los desarrolladores herramientas para burlarse de sus aspectos internos. Lo mejor que se me ocurre con este módulo es la base de datos en memoria, que es mucho más difícil de implementar que un simulacro simple y no siempre es posible (considere falsificar paquetes de Oracle con bases de datos en memoria)
Ahora, para mí, eso no es exactamente un problema, hace un tiempo hemos cambiado al envoltorio casero de ocilib que se hereda de la interfaz virtual (y, por lo tanto, es fácil de simular). Pero realmente, ¿no hay manera de burlarse cuando usas el propio módulo QSql de Qt? O más bien, Qt es un marco (realmente bueno), ¿realmente no proporcionan automatización para tales casos de uso o me estoy perdiendo algo?
UPD1: Una pequeña actualización sobre la importancia de la pregunta:
Mi código está MUY en gran medida intercalado con las consultas de Oracle SQL como, por cierto, el código de muchas otras personas. Las pruebas unitarias de este tipo de código son prácticamente imposibles cuando una dependencia externa, que también está muy desarrollada, a veces suministra datos incorrectos. Cuando se rompe la prueba de unidad, desea que sea su código el que tenga la culpa, no Oracle. Es por eso que hice la pregunta original. Si existe / existe una manera de simular la dependencia de forma semi-sencilla utilizando la interfaz qsqlquery, entonces es posible escribir pruebas unitarias para el código utilizando QSql.
UPD2: Aunque, después de una consideración más profunda, debo admitir que el problema podría evitarse con un mejor diseño de código (OO en lugar de funciones gratuitas en algunos lugares) y una mejor separación de entidades. Por lo tanto, virtualmente imposible en UPD1 no estaba realmente justificado. Aunque esto no hace que la pregunta original sea menos importante. Cuando se le asigna la tarea de mantener un código heredado, por ejemplo, burlarse de QtSql es la única forma realista de introducir pruebas en el sistema.
, IMO, tienes 2 enfoques para simular clases de Qt Sql:
- Subclasificación de clases Qt Sql;
- Envuelva las clases de Qt Sql y paselas por las interfaces.
Enfoque # 1:
Generalmente es dolor. Primero, si desea simular QSqlQuery, debe crear subclases para QSqlResult, QSqlDriver y QSqlQuery. Luego, entra otra molestia en el juego, debes establecer condiciones previas, por ejemplo: quieres que tu sql devuelva verdadero en la función exec () de llamada, para este propósito, tu subclase de QSqlDriver tiene que devolver:
class QSqlDriverTest : public QSqlDriver
{
...
virtual bool isOpen() const { return true; }
virtual void setOpenError(bool e) { QSqlDriver::setOpenError(false); }
...
};
Eso es sólo un ejemplo. Hay aún más condiciones previas para llamar a la función next () con éxito. Para descubrirlos, siempre debe mirar el código fuente qt. Así que todo depende de qt. Este enfoque falla porque:
- no es fácil - las pruebas unitarias tienen que ser fáciles;
- usted todavía tiene la dependencia qt;
Enfoque # 2:
Creo que es la mejor y más clara forma de simular consultas, pero necesitas preparar tu código. Usted crea una interfaz ISQLQuery que tiene las mismas funciones que QSqlQuery (la misma para QSqlDriver y QSqlResult). Me gusta esto:
class ISQLQuery // interface wrapper for QSqlQuery
{
public:
~ISQLQuery(){}
...
virtual bool exec() = 0;
virtual QVariant value(int i) const = 0;
virtual const ISQLDriver * driver() const = 0;
...
};
class ISQLDriver // interface wrapper for QSqlDriver
{
public:
~ISQLDriver(){}
...
virtual bool subscribeToNotification(const QString & name) = 0;
...
};
Luego creas implementaciones reales (eso es solo un borrador de idea):
class SQLDriver : public ISQLDriver
{
public:
SQLDriver(const QSqlDriver * driver) : mpDriver(driver){}
...
virtual bool subscribeToNotification(const QString & name)
{ return mpDriver->subscribeToNotification(name); }
...
private:
const QSqlDriver * mpDriver;
};
class SQLQuery : public ISQLQuery
{
public:
SQLQuery(): mDriver(mQuery->driver){}
...
virtual bool exec() { return mQuery.exec(); }
virtual QVariant value(int i) const { return mQuery.value(i); }
virtual const SQLDriver * driver() const { return &mDriver; }
...
private:
QSqlQuery mQuery;
SQLDriver mDriver;
...
};
Hay un ejemplo de cómo usar nuevas clases de SQL, cuando se crean e implementan todas las interfaces:
// some function
{
...
SQLQuery query = SQLFactory::createSQLQuery(); // here you can put your mocks
query.prepare("DROP TABLE table_hell;");
query.exec();
...
}
Te he mostrado la idea principal sin todos los detalles, de lo contrario, la publicación podría volverse enorme. Espero que encuentre útil mi explicación.
Saludos.
Si solo quiere una base de datos SQL en memoria para usar como un backend simulado para QtSQL, puede considerar usar SQLite .
SQLite es una biblioteca en proceso que implementa un motor de base de datos transaccional de SQL autocontenido, sin servidor, de configuración cero. El código para SQLite está en el dominio público y, por lo tanto, es de uso gratuito para cualquier propósito, comercial o privado. SQLite es la base de datos más implementada en el mundo con más aplicaciones de las que podemos contar, incluidos varios proyectos de alto perfil.
La ventaja de usar un intérprete de SQL real detrás de las llamadas de QtSQL es que puede validar la sintaxis de SQL pasada y si la consulta realmente devuelve el resultado esperado.
Si su preocupación es probar consultas de SQL que ejercen funciones específicas de Oracle SQL, entonces no hay otra manera de saber que está utilizando esas funciones correctamente sin probarlas con un servidor Oracle SQL real.