FYI: este artículo es una versión ampliada de mi charla en SQA Days # 25.En base a mi experiencia con colegas, puedo afirmar: la prueba de código de base de datos no es una práctica ampliamente difundida. Esto puede ser potencialmente peligroso. La lógica de DB está escrita por seres humanos al igual que todos los demás códigos "habituales". Por lo tanto, puede haber fallas que pueden causar consecuencias negativas para un producto, empresa o usuarios. Si estos son procedimientos almacenados que ayudan al backend o si se trata de modificar datos de ETL en un almacén, siempre existe un riesgo y las pruebas ayudan a disminuirlos. Quiero decirte qué es tSQLt y cómo nos ayuda a probar el código DB.
El contexto
Hay un gran almacén que utiliza SQL Server y contiene diferentes datos de ensayos clínicos. Se llena de una variedad de fuentes (bases de datos orientadas a documentos principalmente). Muchas ETL transforman datos dentro del almacén en muchas ocasiones. Estos datos se pueden cargar en bases de datos más pequeñas para que las utilicen las aplicaciones web orientadas a pequeñas tareas específicas. Algunos de los clientes del cliente solicitaron implementar API para sus necesidades. Dichas API a menudo usan procedimientos almacenados y consultas diferentes.
En general, hay una gran cantidad de código en el lado de DBMS.
¿Por qué necesitamos esto?
Como puede ver en la introducción, el código DB es parte del código de la aplicación y también puede contener errores.
Supongo que muchos de nosotros estamos familiarizados con la curva de Boehm: los errores siempre son más caros de solucionar más adelante en el proceso. Un error que se realizó en una etapa de desarrollo anterior y se localizó en una posterior puede costar más. Esto se debe a la necesidad de pasar por muchos pasos intermedios (codificación, prueba unitaria, prueba de integración, prueba del sistema, etc.) dos veces: para la depuración y para devolver el código a la etapa donde se encontró. Este efecto también es cierto para el caso del almacén. Si hay un error en un procedimiento ETL y los datos se modifican varias veces, debemos:
- siga todos los pasos de transformación de datos de vuelta a la fuente del problema
- arreglar el problema
- derivar los datos correctos nuevamente (se pueden requerir ediciones manuales adicionales)
- asegúrese de que no haya otros datos rotos causados por el error.
No olvides que no vendemos peluches. Un error en un área como los ensayos clínicos puede dañar no solo los negocios sino también la salud humana.
¿Cómo hacer la prueba?
Como estamos hablando de pruebas de código, nos referimos a pruebas de unidad e integración. Estas cosas son muy repetitivas e implican una regresión persistente. Estrictamente hablando, tales pruebas nunca se realizan manualmente (bueno, excepto algunos casos singulares probablemente).
Bonificación adicional: las pruebas pueden ser materiales de apoyo para la documentación del código. Por ejemplo, los requisitos pueden verse así (hacer clic):
Archivo XLS, 2 columnas con requisitos + información adicional fragmentada en otras columnas + marcado confuso. Puede ser difícil restaurar los deseos iniciales si es necesario. Las pruebas pueden ayudar a registrar los matices de implementación. Por supuesto, no deben considerarse como un reemplazo de la documentación.
Desafortunadamente, la complejidad de las pruebas aumenta con el crecimiento de la complejidad del código, por lo que este efecto se puede suavizar.
Las pruebas pueden ser una capa de seguridad adicional contra fusiones espontáneas. Las pruebas automáticas de CI ayudan con este problema debido a su formalismo.
Entonces, si hemos decidido usar la automatización, debemos elegir una herramienta para ello.
¿Qué usar para las pruebas?
En el caso de las pruebas de código DB, veo 2 enfoques: con tecnología SQL (cuando una herramienta funciona en DBMS directamente) y sin tecnología SQL. Estas son las principales diferencias que encontré:
En el caso de SQL Server, tenemos varias opciones:
La escala "Excelente - Fallido" es subjetiva, lo siento, es difícil encontrar una solución.
"Primera aparición" - la fecha más temprana de aparición del framework que pude encontrar - la primera versión o confirmación.
Como puede ver, las alternativas basadas en SQL fueron abandonadas hace bastante tiempo, y tSQLt es el único producto actualmente compatible. Además, tSQLt gana funcionalmente. Lo único es que TST cuenta con un conjunto de afirmaciones un poco más rico que tSQLt; Sin embargo, dudo que esto pueda superar todas las desventajas.
La documentación de tSQLt tiene algunos matices, los describiré más adelante.
En el mundo sin SQL, las cosas no están tan claras. Se están desarrollando alternativas, aunque no súper activas. DbFit es una herramienta bastante interesante basada en el marco FitNesse. Implica usar el marcado de wiki para escribir pruebas. Slacker también es interesante: se sugiere el enfoque BDD para las pruebas de código DB.
Debo decir sobre aserciones en soluciones no basadas en SQL. A primera vista, la cantidad de afirmaciones es menor, y podemos pensar que tales herramientas son peores. Pero debemos tener en cuenta que son fundamentalmente diferentes de tSQLt, por lo que una mirada tan superficial es incorrecta.
La última fila: "NUnit, etc." - Es más como un recordatorio. Con la ayuda de bibliotecas adicionales, se pueden aplicar muchos marcos de pruebas unitarias habituales al código DB. Hay muchas N / A en esta fila porque esta fila, de hecho, incluye múltiples herramientas. Esa es la fuente de "matices" en la columna "aserciones": diferentes herramientas pueden proporcionar diferentes conjuntos y no hay garantía de que todas las aserciones puedan aplicarse a la base de datos.
Como otra métrica interesante, podemos considerar las
tendencias de Google .
Matices:
- Decidí no incluir Slacker porque este nombre puede significar cosas diferentes (y consultas como "Slacker framework" apenas se ven en el gráfico).
- Solo por curiosidad (y porque una ranura permaneció vacía), agregué la tendencia TST. Pero apenas nos muestra la imagen real porque es la abreviatura que también puede significar cosas diferentes.
- No he incluido NUnit y sus análogos. Estas herramientas son marcos para pruebas de código "habituales", por lo que sus tendencias no son descriptivas para nuestro contexto.
Como puede ver, tSQLt es la herramienta más buscable en la lista. Otra herramienta (menos) popular es DbFit. Otras herramientas tienen una popularidad limitada.
En general, podemos ver que tSQLt brilla en el fondo.
¿Qué es tSQLt?
Es fácil adivinar que tSQLt es un marco de pruebas unitarias basado en SQL. El sitio oficial es
https://tsqlt.org .
Se promete que tSQLt es compatible con SQL Server a partir de 2005 SP2. No he revisado tales revisiones tempranas, pero no veo ningún problema con 2012 en nuestro servidor de desarrollo y 2017 en mi máquina local.
Código abierto, licencia Apache 2.0,
disponible en GitHub . Como de costumbre, podemos bifurcar, contribuir, usar de forma gratuita en proyectos comerciales y, lo que es más importante, no debemos temer al spyware en CLR.
Mecánica
Los casos de prueba son procedimientos almacenados. Se pueden combinar en clases de prueba (conjunto de pruebas en terminología xUnit).
Las clases de prueba no son más que esquemas de base de datos. tSQLt requiere registrarlos con el procedimiento NewTestClass que agrega clases de prueba a una tabla especial.
Es posible determinar un procedimiento de configuración. Dicho procedimiento se ejecutará antes de cada caso de prueba separado.
No se requiere el procedimiento de desmontaje después de la ejecución del caso de prueba. Cada caso de prueba con su configuración se ejecuta en una transacción separada que se revierte después de la recopilación de resultados. Es muy conveniente pero tiene algunas consecuencias negativas: las describiré un poco más adelante.
El marco permite ejecutar casos de prueba uno a la vez, todas las clases de prueba a la vez o incluso todas las clases de prueba registradas con un solo comando.
Características y ejemplos.
No dispuesto a repetir la guía oficial, mostraré las características de tSQLt en los ejemplos.
Descargo de responsabilidad:- los ejemplos son simplificados
- el código original no es completamente mío, son más bien creaciones colectivas
- el ejemplo 2 es ficcionalizado por mí para demostrar características más completamente.
Ejemplo # 1: CsvSql
Lo siguiente se implementó a pedido de uno de los clientes del cliente. Hay consultas SQL almacenadas en los campos de Nvarchar (MAX). Se crea una interfaz de usuario mínima para verlos. Los conjuntos de resultados generados por estas consultas se utilizan en el backend para componer más como archivos CSV. Los archivos CSV pueden solicitarse mediante una llamada API.
Los conjuntos de resultados son grandes y contienen una gran cantidad de columnas. Un ejemplo hipotético de tal conjunto de resultados:
Este conjunto de resultados representa datos de ensayos clínicos. Echemos un vistazo más de cerca al cálculo [ClinicsNum]. Tenemos 2 tablas: [Trial] y [Clinic].
Hay un FK: [Clínica]. [TrialID] -> [Trial]. [TrialID]. Obviamente, es suficiente usar COUNT (*) para obtener varias clínicas:
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
¿Cómo podemos probar tal consulta? Primero, usemos stub FakeTable, lo que hará que nuestro trabajo posterior sea mucho más fácil.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
FakeTable hace una cosa simple: renombra tablas viejas y crea nuevas con el mismo nombre. Los mismos nombres, las mismas columnas, pero sin restricciones ni desencadenantes.
Necesitamos esto porque:
- Test DB puede contener algunos datos que pueden evitar una ejecución correcta de la prueba. FakeTable nos permite no depender de ellos.
- Por lo general, solo necesitamos llenar unas pocas columnas para los propósitos de la prueba. La tabla puede contener muchos de ellos, a menudo con restricciones y desencadenantes. Hacemos que sea más fácil insertar datos más tarde: insertaremos solo lo requerido para la información de la prueba, manteniendo la prueba lo más minimalista posible.
- No habrá ejecuciones de disparador no deseadas, por lo que no debemos preocuparnos por los efectos posteriores.
Luego insertamos los datos de prueba requeridos:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
Derivamos la consulta del DB, creamos la tabla [Actual] y la llenamos con el resultado
establecido desde la consulta.
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT… CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
Ahora, llenamos [Esperado] - nuestros valores esperados:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
Quiero llamar su atención de que solo tenemos una columna en la tabla [Esperado], aunque tenemos el conjunto completo en la columna [Actual].
Esto se debe a una característica útil del procedimiento AssertEqualsTable que usaremos para la verificación de valores.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
Compara solo las columnas que se presentan en ambas tablas. Es muy conveniente en nuestro caso porque la consulta bajo prueba devuelve muchas columnas, cada una conectada con una lógica bastante complicada. No queremos inflar los casos de prueba, por lo que esta característica realmente ayuda. Por supuesto, esta característica es una espada de doble filo. Si se completa [Actual] a través de SELECT TOP 0 y en un momento aparece una columna inesperada, tal caso de prueba no captará esto. Debe escribir cheques adicionales para cubrir esto.
AssertEqualsTable procedimientos gemelos
Vale la pena mencionar que tSQLt contiene 2 procedimientos como AssertEqualsTable. Son AssertEqualsTableSchema y AssertResultSetsHaveSameMetaData. El primero hace lo mismo que AssertEqualsTable pero en los metadatos de las tablas. El segundo hace lo mismo pero en los metadatos de los conjuntos de resultados.
Ejemplo # 2: restricciones
El ejemplo anterior nos mostró cómo podemos eliminar restricciones. Pero, ¿y si necesitamos verificarlos? Técnicamente, las restricciones también son parte de la lógica, y pueden considerarse como un candidato para la cobertura mediante pruebas.
Considere la situación del ejemplo anterior. 2 tablas: [Trial] y [Clinic]; [TrialID] FK:
Intentemos escribir un caso de prueba para verificarlo. Primero, como en el caso anterior, falsificamos las tablas:
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
El objetivo es el mismo: deshacerse de los límites innecesarios. Queremos cheques aislados sin esfuerzo excesivo.
A continuación, devolvemos la restricción que queremos probar usando ApplyConstraint:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
Ahora tenemos una configuración para la verificación. La comprobación en sí misma es que intentar insertar datos provocará una excepción. Para el caso de prueba que pasa necesitamos capturar esta excepción. El controlador de excepciones ExpectException puede ayudar.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
Podemos intentar insertar no insertables después de la configuración del controlador.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
La excepción fue atrapada. Pase de prueba
Aplicar procedimientos gemelos de restricción
La forma de probar los desencadenantes propuestos por los autores de tSQLt es similar a las restricciones de prueba. Podemos usar el procedimiento ApplyTrigger para devolver el activador a la tabla. Después de eso, todo va como en el ejemplo anterior: inicie el disparador, verifique el resultado.
ExpectNoException - el antónimo de ExpectException
Existe un procedimiento ExpectNoException para los casos en que no debe ocurrir una excepción. Funciona de la misma manera que ExpectException, excepto que la prueba falla en caso de que ocurra una excepción.
Ejemplo # 3: Semáforo
Hay algunos procedimientos almacenados y servicios de Windows. El inicio de su ejecución puede ser causado por diferentes eventos externos. Sin embargo, el orden de su ejecución es fijo. Por lo tanto, es necesario implementar el control de acceso en el lado de la base de datos, es decir, un semáforo. En nuestro caso, el semáforo es un grupo de procedimientos almacenados que trabajan juntos.
Veamos un procedimiento dentro del semáforo. Tenemos 2 tablas: [Proceso] y [ProcStatus]:
La tabla [Proceso] contiene una lista de procesos permitidos para la ejecución. [ProcStatus], obviamente, contiene la lista de estados del proceso de la tabla anterior.
Entonces, ¿qué hace nuestro procedimiento? Primero, realiza las siguientes verificaciones:
- Hemos pasado un nombre de proceso como uno de los parámetros de entrada del procedimiento. Este nombre se busca en el campo [Nombre] de la tabla [Proceso].
- Si se ha encontrado el nombre del proceso, verifica el indicador [IsRunable] de la tabla [Proceso].
- Si el indicador está activado, consideramos que el proceso puede ejecutarse. La última comprobación se realiza en la tabla [ProcStatus]. Necesitamos asegurarnos de que el proceso no se esté ejecutando actualmente, lo que significa la ausencia de registros sobre el proceso con el estado "InProg" en la tabla [ProcStatus].
Si todo está bien y se pasan todas las comprobaciones, agregamos un nuevo registro sobre nuestro proceso en la tabla [ProcStatus] con el estado "InProg". El ID de este nuevo registro se devuelve con el parámetro de salida ProcStatusId.
Si algo ha salido mal, esperamos lo siguiente:
- Se envía un correo electrónico a un administrador del sistema.
- ProcStatusId = -1 se devuelve.
- No se agregaron nuevos registros [ProcStatus].
Creemos un caso de prueba para verificar el caso de ausencia de proceso en la tabla [Proceso].
Usamos FakeTable nuevamente. Aquí no es tan crítico, pero puede ser conveniente porque:
- Se garantiza que no habrá datos que puedan interrumpir la ejecución del caso de prueba.
- La verificación adicional de la nueva ausencia de registros [ProcStatus] se simplificará.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
Hay un procedimiento [SendEmail] cuyo nombre habla por sí mismo. Necesitamos atender su llamada. tSQLt sugiere usar el simulacro SpyProcedure para eso.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
SpyProcedure hace lo siguiente:
- Crea una tabla con un nombre que se parece a [dbo]. [ProcedureName_SpyProcedureLog]
- Al igual que FakeTable, reemplaza el procedimiento original con uno generado automáticamente, con el mismo nombre, pero con lógica de registro en su interior. También puede agregar su propia lógica al procedimiento generado si es necesario.
No es difícil adivinar que los registros se graban en la tabla [dbo]. [SendEmail_SpyProcedureLog]. Esta tabla contiene una columna [_ID_] que es para los números de secuencia de las llamadas. Las columnas posteriores se nombran después de los parámetros pasados al procedimiento y se utilizan para recopilarlos, por lo que los valores de los parámetros también se pueden verificar.
Lo último que debemos hacer antes de la llamada del semáforo es crear una variable para almacenar el valor [ProcStatusId] (para ser más exactos, -1, ya que el registro no se agregará).
DECLARE @ProcStatusId BIGINT;
Llamamos al semáforo:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT; -- here we get -1
Ahora tenemos todos los datos necesarios para las verificaciones. Comencemos por verificar
que el mensaje ha sido enviado.
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
En este caso, no verificamos los parámetros pasados y probamos solo el envío. Quiero llamar su atención sobre el procedimiento de falla. Nos permite "oficialmente" fallar un caso de prueba. Si necesita construir una construcción sofisticada, Fail puede ayudarlo.
Ahora verificamos la ausencia de registros en la tabla [ProcStatus] con el procedimiento AssertEmptyTable.
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
Aquí es donde FakeTable que utilizamos al principio nos ayudó. Con él, podemos esperar una tabla vacía y probar usando una sola línea de código. La forma correcta de verificar esto sin falsificar la tabla sería comparar el número de filas antes y después de la ejecución del procedimiento, y eso requeriría más acciones.
Podemos verificar fácilmente la igualdad ProcStatusId = -1 con AssertEquals.
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals es minimalista. Simplemente compara 2 valores, nada extraordinario.
AssertEquals procedimientos gemelos
Tenemos los siguientes procedimientos para la comparación de valores:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- Asertivo
Los nombres se explican por sí mismos, creo. El único procedimiento que quiero enfatizar es AssertEqualsString. Es el procedimiento dedicado a la verificación de valores de cadena. ¿Por qué necesitamos un procedimiento más, considerando los AssertEquals universales dados? La cuestión es que AssertEquals / AssertNotEquals / AssertLike funciona con el tipo SQL_VARIANT. NVARCHAR (MAX) no está incluido en SQL_VARIANT, por lo que los desarrolladores de tSQLt tuvieron que realizar un procedimiento adicional.
Función falsa
En un momento, podemos llamar a FakeFunction un procedimiento similar a SpyProcedure. Este falso permite reemplazar cualquier función por una más simple. Como las funciones de SQL Server funcionan como un tubo de pasta de dientes (el resultado se devuelve a través del único "agujero"), es técnicamente imposible implementar una funcionalidad de registro. El reemplazo de la lógica interna es la única forma disponible.
Trampas
Quiero contarte algunos problemas que puedes enfrentar durante el uso de tSQLt. En este caso, "dificultades" significan algunos problemas causados por restricciones de SQL Server y / o que los desarrolladores de framework no pueden resolver.
Transacciones de reversión y condena
El primer y principal problema que enfrenta nuestro equipo es la reversión de transacciones y la condena. SQL Server no puede revertir la transacción anidada por separado. Siempre revierte todas las transacciones hasta el extremo exterior. Teniendo en cuenta que tSQLt envuelve cada prueba en una transacción separada, puede convertirse en un problema porque la reversión dentro de un procedimiento almacenado puede interrumpir una ejecución de prueba con un error de ejecución no descriptivo.
Como solución alternativa, utilizamos puntos de guardado. La idea es simple. Al principio, verificamos si estamos dentro de una transacción o no. En caso afirmativo, suponemos que es una transacción tSQLt y ponemos un punto de rescate, por lo que volveremos a hacerlo si es necesario. Si no, comenzamos una nueva transacción. De hecho, no permitimos anidar.
El problema se complica por la cancelación de transacciones: puede suceder si se produce una excepción. Una transacción condenada no puede confirmarse ni revertirse a un punto de rescate, por lo que debemos revertirla a la transacción más externa nuevamente.
Teniendo en cuenta los puntos descritos anteriormente, debemos utilizar la siguiente estructura:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Repasemos el código pieza por pieza. Primero, necesitamos determinar si estamos dentro de una transacción o no.
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
Después de obtener el indicador @isNestedTransaction, podemos iniciar el bloque TRY y establecer un punto de guardado o iniciar una transacción según la situación.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Después de hacer algo útil, confirmamos los resultados si se trata de un procedimiento "real" ejecutado.
Por supuesto, si se trata de un caso de prueba, no necesitamos comprometer nada. tSQLt revertirá los cambios al final automáticamente.
Si algo ha salido mal y nos metemos en el bloque CATCH, debemos determinar si la transacción es confirmable o no.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
Podemos retroceder al punto de guardado solo si:
- La transacción es commitable
- Es una prueba de funcionamiento, entonces existe un punto de rescate.
En todos los demás casos, debemos revertir toda la transacción.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
Sí, desafortunadamente, si hemos alcanzado un estado de transacción no confirmable durante una ejecución de prueba, aún obtenemos el error de ejecución.
Faketable y la cuestión de la clave extranjera
Repasemos las tablas familiares [Prueba] y [Clínica]
Recordamos sobre [TrialID] FK. ¿Qué problema puede causar? En los ejemplos anteriores, aplicamos FakeTable en ambas tablas. Si lo usamos solo en uno de ellos, alcanzaremos la siguiente configuración:
Por lo tanto, un intento de insertar un registro en [Clínica] puede fallar incluso si hemos preparado los datos en la versión falsa de [Prueba].
[dbo].[Test_FK_Problem] failed: (Error) The INSERT statement conflicted with the FOREIGN KEY constraint "Trial_Fk". The conflict occurred in database "HabrDemo", table "dbo.tSQLt_tempobject_ba8f36353f7a44f6a9176a7d1db02493", column 'TrialID'.[16,0]{Test_FK_Problem,14}
Conclusión: falso todo o ninguno. En caso de que ninguno, obviamente, debe preparar una base de datos con todos los datos de prueba requeridos.
SpyProcedure sobre procedimientos del sistema
Desafortunadamente, no podemos espiar los procedimientos del sistema:
[HabrDemo].[test_test] failed: (Error) Cannot use SpyProcedure on sys.sp_help because the procedure does not exist[16,10] {tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure,7}
En el ejemplo del semáforo, rastreamos llamadas del procedimiento [SendEmail], que fue creado por nuestros desarrolladores. En este caso, no se requería solo mediante pruebas. Era necesario crear un procedimiento separado porque es necesario preparar algunos datos antes de enviarlos. Pero debe estar mentalmente preparado para escribir un procedimiento de capa intermedia para cumplir con los objetivos de la prueba.
Pros
Instalación rápida
La instalación de tSQLt consta de 2 pasos y toma alrededor de 2 minutos. Debe activar CLR si no está activo actualmente y ejecutar un solo script SQL. Eso es todo: ahora puede agregar su primera clase de prueba y escribir casos de prueba.
Aprendizaje rápido
tSQLt es fácil de aprender. Me llevó un poco más de un día de trabajo. Le pregunté a mis colegas y parece que también toma alrededor de 1 día laboral para otros. Dudo que pueda tomar mucho más tiempo.
Integración rápida de CI
Tomó alrededor de 2 horas configurar la integración de CI en nuestro proyecto. El tiempo puede variar, por supuesto, pero no es un problema en general, y se puede hacer rápidamente.
Un amplio conjunto de instrumentos.
Es subjetivo, pero en mi opinión, la funcionalidad tSQLt es rica y la mayor parte de las necesidades pueden ser cubiertas por ella. Si no es suficiente, siempre puede usar el procedimiento Fail para casos raros y sofisticados.
Documentación conveniente
Las guías oficiales son convenientes y consistentes. Puede comprender fácilmente el uso de tSQLt en un período corto, incluso si es su primera herramienta de prueba de unidad.
Salida clara
La salida de prueba se puede tomar en un formato de texto ilustrativo:
[tSQLtDemo].[test_error_messages] failed: (Failure) Expected an error to be raised. [tSQLtDemo].[test_tables_comparison] failed: (Failure) useful and descriptive error message Unexpected/missing resultset rows! |_m_|Column1|Column2| +---+-------+-------+ |< |2 |Value2 | |= |1 |Value1 | |= |3 |Value3 | |> |2 |Value3 | +----------------------+ |Test Execution Summary| +----------------------+ |No|Test Case Name |Dur(ms)|Result | +--+------------------------------------+-------+-------+ |1 |[tSQLtDemo].[test_constraint] | 83|Success| |2 |[tSQLtDemo].[test_trial_view] | 83|Success| |3 |[tSQLtDemo].[test_error_messages] | 127|Failure| |4 |[tSQLtDemo].[test_tables_comparison]| 147|Failure| ----------------------------------------------------------------------------- Msg 50000, Level 16, State 10, Line 1 Test Case Summary: 4 test case(s) executed, 2 succeeded, 2 failed, 0 errored. -----------------------------------------------------------------------------
También se puede derivar del DB (cliqueable) ...
... o incluso como XML.
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="1" name="tSQLtDemo" tests="3" errors="0" failures="1" timestamp="2019-06-22T16:46:06" time="0.433" hostname="BLAHBLAHBLAH\SQL2017" package="tSQLt"> <properties /> <testcase classname="tSQLtDemo" name="test_constraint" time="0.097" /> <testcase classname="tSQLtDemo" name="test_error_messages" time="0.153"> <failure message="Expected an error to be raised." type="tSQLt.Fail" /> </testcase> <testcase classname="tSQLtDemo" name="test_trial_view" time="0.156" /> <system-out /> <system-err /> </testsuite> </testsuites>
El último formato permite la integración de CI sin ningún problema. Específicamente, usamos tSQLt junto con Atlassian Bamboo.
Apoyo de redgate
Como uno de los profesionales, puedo nombrar el soporte de uno de los mayores proveedores de herramientas de DBA: RedGate. Su complemento de SQL Server Management Studio llamado SQL Test funciona con tSQLt desde el principio. Además, RedGate ayuda al desarrollador principal de tSQLt con dev-environment, según sus palabras en los
grupos de Google .
Contras
No hay tablas temporales falsificadas
tSQLt no permite falsificar tablas temporales. Pensamientos, en caso de necesidad, puede usar un complemento no oficial. Desafortunadamente, este complemento funciona solo con SQL Server 2016+.
Trabajar con bases de datos externas
tSQLt está diseñado para funcionar con el código en la misma base de datos en la que está instalado el marco. Por lo tanto, puede ser imposible usarlo con un DB externo. Al menos, las falsificaciones no funcionarán.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db] AS BEGIN SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] EXEC tSQLt.FakeTable '[AdventureWorks2017].[Person].[Password]' SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] END
Parece que las afirmaciones funcionan, pero su viabilidad no está garantizada, por supuesto.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
Errores de documentación
A pesar de que mencioné anteriormente que las guías son convenientes y consistentes, la documentación tiene algunos problemas. Contiene partes obsoletas.
Ejemplo 1. La
“Guía de inicio rápido” sugiere descargar el marco de SourceForge.
Se mudaron de SourceForge
hasta 2015 .
Ejemplo 2.
La guía ApplyConstraint utiliza un diseño voluminoso con el procedimiento Fail dentro de un ejemplo de captura de excepción. Esto se puede reemplazar con un código simple y claro usando ExpectException.
CREATE PROCEDURE ConstraintTests.[test ReferencingTable_ReferencedTable_FK prevents insert of orphaned rows] AS BEGIN EXEC tSQLt.FakeTable 'dbo.ReferencedTable'; EXEC tSQLt.FakeTable 'dbo.ReferencingTable'; EXEC tSQLt.ApplyConstraint 'dbo.ReferencingTable','ReferencingTable_ReferencedTable_FK'; DECLARE @ErrorMessage NVARCHAR(MAX); SET @ErrorMessage = ''; BEGIN TRY INSERT INTO dbo.ReferencingTable ( id, ReferencedTableId ) VALUES ( 1, 11 ) ; END TRY BEGIN CATCH SET @ErrorMessage = ERROR_MESSAGE(); END CATCH IF @ErrorMessage NOT LIKE '%ReferencingTable_ReferencedTable_FK%' BEGIN EXEC tSQLt.Fail 'Expected error message containing ''ReferencingTable_ReferencedTable_FK'' but got: ''',@ErrorMessage,'''!'; END END GO
Y esto se espera, debido a ...
Abandono parcial
Hubo una pausa prolongada en el desarrollo desde principios de 2016 hasta junio de 2019. Sí, desafortunadamente, esta herramienta está parcialmente abandonada. El desarrollo comenzó lentamente en 2019,
según GitHub . Aunque los Grupos de Google oficiales
tienen un hilo en el que se le preguntó a Sebastian, el desarrollador principal de tSQLt, sobre el futuro del proyecto. La última pregunta se hizo el 2 de marzo de 2019, sin respuesta.
Problema de SQL Server 2017
La instalación de tSQLt puede requerir algunas acciones adicionales si está utilizando SQL Server 2017. Microsoft implementó el primer cambio de seguridad desde 2012 en esta versión. Se ha agregado el indicador de nivel de servidor "Seguridad estricta CLR". Este indicador no permite la creación de conjuntos sin firmar '(incluso SAFE). La descripción detallada merece un artículo separado (y, afortunadamente,
ya tenemos uno bueno; vea también los siguientes artículos en la secuencia. Solo prepárese mentalmente para esto.
Por supuesto, podría atribuir este problema a las "trampas", pero los desarrolladores de tSQLt pueden resolver este problema.
El problema de GitHub ya ha surgido . Aún así, no se ha resuelto desde octubre de 2017.
Alternativas (±) para otros DBMS
tSQLt no es único en su clase. Aunque no puede usarlo en otros DBMS debido a los matices CLR y T-SQL, aún puede encontrar algo similar. Vale la pena mencionar que estas alternativas no están muy cerca de tSQLt, por lo que me refiero al enfoque basado en SQL.
Por ejemplo, los usuarios de PostgreSQL pueden probar
pgTAP . Es una herramienta bien desarrollada y en desarrollo activo que utiliza PL / pgSQL nativo para pruebas y formato de salida TAP. La herramienta similar
MyTap puede ayudarlo con las pruebas en MySQL. Este marco es un poco menos funcional que pgTAP pero aún puede ser útil. Y también está en desarrollo activo. Si es un usuario feliz de Oracle, tiene la oportunidad de usar la herramienta muy poderosa
utPLSQL . Se está desarrollando de manera muy activa y ofrece una gran cantidad de características.
Conclusión
Quería transmitir 2 ideas:
El primero: la utilidad de las pruebas de código DB. No es importante si está utilizando SQL Server, Oracle, MySQL u otra cosa. Si su base de datos contiene lógica no probada, está tomando riesgos. Como todos los demás errores en el resto del código, los errores del código DB pueden dañar el producto y la compañía que lo proporciona.
El segundo: la elección de la herramienta. Para aquellos que trabajan con SQL Server, tSQLt, incluso si no es un ganador del 100%, sin duda merece atención. A pesar del lento desarrollo y algunos problemas, sigue siendo un marco práctico que podría hacer que su trabajo sea mucho más fácil.
Fuentes que me ayudaron (lista no exhaustiva)