FYI: Este artículo es una versión ampliada de mi informe sobre SQA Days # 25.Según mi experiencia de comunicación con colegas, puedo decir que probar el código en una base de datos no es una práctica común. Esto puede ser un peligro potencial. La lógica en la base de datos está escrita por las mismas personas que escriben el código "regular". En consecuencia, los errores también pueden estar presentes allí, y también pueden tener consecuencias negativas para el producto, el negocio y los consumidores. No importa si se trata de procedimientos almacenados que ayudan al backend o de ETL que convierten datos en almacenamiento; existe un riesgo y las pruebas pueden reducirlo significativamente. Quiero decirle qué es tSQLt y cómo nos ayuda a probar el código en SQL Server.
Contexto
Hay un gran almacén en SQL Server que contiene varios datos de investigación clínica. Se llena de varias fuentes (principalmente bases de datos orientadas a documentos). Dentro del servidor, los datos se convierten repetidamente usando ETL. En el futuro, estos datos se pueden cargar en bases de datos más pequeñas para que las utilicen las aplicaciones web que resuelven algunos pequeños problemas específicos. Algunos de los clientes del cliente también solicitan una API para sus necesidades internas. En la implementación de tales API, a menudo se utilizan procedimientos almacenados y consultas.
En general, el código está en el lado DBMS en orden.
¿Por qué se necesita todo esto?
Como ya se entendió de la introducción, el código en la base de datos es el mismo código
aplicaciones, como el resto, y también puede haber errores.
Creo que muchas personas son conscientes de la dependencia del precio del error en el momento de su descubrimiento, cuyo descubrimiento generalmente se atribuye a Barry Bohem. Un error cometido en una etapa temprana y detectado en una etapa posterior puede ser más costoso debido a la necesidad de pasar por muchas etapas (codificación, unidad, integración, prueba del sistema, etc.) repetidamente tanto para localizar el error como para devolver el código corregido a La etapa en la que se identificó el problema. Este efecto también es relevante para el caso del almacén. Si un error se introdujo en algún ETL y los datos sufren múltiples transformaciones, entonces si se detecta un error en los datos, deberá:
- Siga todos los pasos de la conversión de regreso a la localización del problema.
- Soluciona el problema.
- Vuelva a obtener los datos corregidos (no se excluyen las correcciones manuales).
- Verifique que los datos incorrectos causados por el error no aparecieron en otro lugar.
No olvides que no vendemos peluches. Un error en un campo como la investigación clínica puede causar daños no solo a las empresas, sino también a la salud de las personas.
¿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 ensayales e implican una regresión constante. Estrictamente hablando, nadie realiza tales pruebas manualmente (bueno, tal vez con la excepción de algunos casos completamente degenerados).
Una buena ventaja: las pruebas pueden ser un material auxiliar al documentar el código. Por cierto, los requisitos del cliente pueden verse así (hacer clic):
Excel, dos columnas con requisitos + información de soporte dispersa en otras columnas + marcado diferido, lo cual es más confuso de lo que ayuda. Si es necesario, restaurar los deseos originales puede ser difícil. Las pruebas pueden ayudar a capturar con mayor precisión los matices de la implementación (por supuesto, no debe considerarlos como el equivalente de la documentación).
Desafortunadamente, con la complejidad del código, la complejidad de las pruebas crece y este efecto puede nivelarse.
Las pruebas pueden servir como una capa adicional de seguridad contra las morsas espontáneas. Las autoevaluaciones en CI debido al formalismo ayudan a hacer frente a este problema.
Si nuestra elección recayó en el camino de la automatización, entonces debemos decidir las herramientas para su implementación.
¿Cómo hacer la prueba?
En el caso de probar el código en la base de datos, distingo dos enfoques: impulsado por SQL, es decir, funcionando directamente en el DBMS, y no impulsado por SQL. Pude resaltar los siguientes matices:
En SQL Server, tenemos alguna opción:
Las evaluaciones de "bueno-malo" son subjetivas, lo siento, sin esto, en ninguna parte.
Explicación: "Primera aparición" es la fecha más temprana en el camino de la vida del marco que logré encontrar, es decir, la primera versión o confirmación.
Puede notar que las alternativas basadas en SQL se han abandonado por bastante tiempo, y tSQLt es la única opción compatible. Funcionalmente, tSQLt también gana. Lo único es que, en términos de afirmaciones, TST cuenta con una opción ligeramente más rica que tSQLt, que, sin embargo, es poco probable que supere sus desventajas.
Hay matices en la documentación de tSQLt, pero hablaré de esto más adelante.
En el mundo sin SQL, las cosas no son tan sencillas. Se están desarrollando alternativas, aunque no súper activas. DbFit es una herramienta interesante basada en el marco de FitNesse. Ofrece pruebas de escritura en el marcado de wiki. Slacker también es una cosa curiosa: el enfoque BDD al escribir pruebas para la base de datos.
Discutiré las aserciones en sistemas no basados en SQL. Exteriormente, hay menos de ellos, y uno podría decir que son peores debido a esto. Pero aquí vale la pena considerar que son fundamentalmente diferentes de tSQLt. No todo es tan simple.
La última línea es "NUnit, etc." - Es más bien un recordatorio. Muchos de los marcos de pruebas unitarias familiares en el trabajo diario pueden usarse en bases de datos auxiliares usando bibliotecas auxiliares. La tabla tiene mucha N / A, porque esta línea, de hecho, incluye muchas herramientas. Los "matices" en la columna de afirmación provienen del mismo punto: en diferentes herramientas, su conjunto puede variar y la cuestión de la aplicabilidad a la base de datos está abierta.
Como otra métrica interesante, podemos considerar las
tendencias de Google .
Matices:
- No incluí Slacker, porque este nombre puede significar muchas cosas (y solicitudes como "Slacker framework" no son particularmente visibles en los gráficos).
- En aras de la curiosidad, se agregó la tendencia TST, pero tampoco refleja mucho el estado de las cosas, ya que es una abreviatura que significa muchas cosas diferentes.
- No incluí NUnit y sus análogos, ya que estos eran originalmente marcos para probar el código de las aplicaciones, y sus tendencias no son indicativas de nuestro contexto.
En general, podemos decir que tSQLt se ve favorablemente en el contexto de los análogos.
¿Qué es tSQLt?
tSQLt, como se puede adivinar, es un marco de prueba de unidad basado en SQL.
→
Sitio oficialEl soporte de SQL Server ha sido anunciado desde 2005 SP2. Nunca he podido mirar tan lejos en el pasado, pero tenemos 2012 en el servidor de desarrollo, tengo localmente 2017 - no hubo problemas.
El código abierto, una licencia de Apache 2.0,
está disponible en GitHub . Puede bifurcar, contrabandear, usar de forma gratuita en proyectos comerciales y, lo más importante, no tener miedo a los marcadores en el CLR.
Mecánica de trabajo
Los casos de prueba son procedimientos almacenados. Se combinan en clases de prueba (conjunto de pruebas en términos de xUnit).
Las clases de prueba no son más que esquemas de bases de datos regulares. Se diferencian de otros esquemas por registro en las tablas marco. Puede realizar dicho registro llamando a un procedimiento: tSQLt.NewTestClass.
Dentro de la clase de prueba, también es posible definir un procedimiento de configuración que se ejecutará antes de ejecutar cada caso de prueba individual.
No se requiere un procedimiento de desmontaje para restaurar el sistema al finalizar el caso de prueba. Cada caso de prueba junto con el procedimiento de Configuración se realiza en una transacción separada, que se revierte después de recopilar los resultados. Esto es muy conveniente, pero tiene algunos efectos negativos, que discutiremos a continuación.
El marco le permite ejecutar casos de prueba individuales, clases de prueba completas o todas las clases de prueba registradas a la vez.
Características por ejemplo
Al no tener el deseo de volver a contar una guía oficial ya simple, hablaré sobre las posibilidades del marco utilizando ejemplos.
Descargo de responsabilidad:- los ejemplos están simplificados;
- en el original, no todo el código de prueba fue escrito por mí, es más bien el fruto de la creatividad colectiva;
- El ejemplo 2 se inventó para demostrar más completamente las capacidades de tSQLt.
Ejemplo 1: CsvSql
A pedido de uno de los clientes del cliente, se implementó lo siguiente. La base de datos en los campos de Nvarchar (MAX) almacena consultas SQL. Para ver estas consultas, se atornilla una interfaz mínima. El backend utiliza los conjuntos de resultados que devuelven estas solicitudes para generar el archivo CSV para que se devuelva mediante la llamada API.
Los conjuntos de resultados son bastante pesados y contienen muchas columnas. Un ejemplo condicional de tal conjunto de resultados:
Este conjunto de resultados son algunos datos de ensayos clínicos. Echemos un vistazo más de cerca a cómo se considera ClinicsNum: la cantidad de clínicas involucradas en el estudio. Tenemos dos tablas: [Prueba] y [Clínica]:
Hay FK: [Clínica]. [TrialID] -> [Trial]. [TrialID]. Obviamente, para calcular el número de clínicas, solo necesitamos el COUNT habitual (*).
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
¿Cómo probamos tal solicitud? Para empezar, podemos usar el práctico código auxiliar: FakeTable, que simplificará en gran medida el trabajo posterior.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
FakeTable hace una cosa simple: renombra tablas viejas y crea nuevas en su lugar. Los mismos nombres, las mismas columnas, pero sin restricción'ov y trigger'ov.
¿Por qué necesitamos esto?
- Puede haber algunos datos en la base de datos de prueba que pueden interferir con las pruebas. Gracias a FakeTable, no dependemos de ellos.
- Para la prueba, como regla general, debe completar solo algunas columnas. Puede haber muchos de ellos en la tabla, y algunos tendrán restricciones. De esta manera, simplificamos la instalación posterior de los datos de prueba: solo insertaremos los que sean realmente necesarios para el caso de prueba.
- Definitivamente no afectaremos a ningún disparador al insertar datos de prueba y no tenemos que preocuparnos por los efectos posteriores no deseados.
Luego, inserte los datos de prueba que necesitamos:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
Obtenemos nuestra consulta de la base de datos, creamos la tabla Actual y la llenamos con el conjunto de resultados de nuestra consulta:
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT… CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
Relleno esperado - valores esperados:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
Quiero llamar su atención sobre el hecho de que en la tabla Esperada solo tenemos una columna, mientras que en Actual tenemos un conjunto completo.
Esto se debe a la función del procedimiento AssertEqualsTable, que usaremos para verificar los valores.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
Compara solo aquellas columnas que están presentes en ambas tablas que se comparan. Esto es muy conveniente en nuestro caso, ya que la consulta de prueba devuelve muchas columnas, cada una de las cuales tiene su propia lógica, a veces muy confusa. No queremos inflar los casos de prueba, por lo que esta función es muy útil para nosotros. Por supuesto, esta es una espada de doble filo. Si en Actual se llena automáticamente un conjunto de columnas a través de SELECT TOP 0 y en algún momento sale repentinamente una columna adicional, entonces ese caso de prueba no captará este momento. Para manejar tales situaciones, necesita hacer verificaciones adicionales.
Procedimientos hermanos AssertEqualsTable
Vale la pena mencionar que tSQLt contiene dos procedimientos similares a AssertEqualsTable. Estos son AssertEqualsTableSchema y AssertResultSetsHaveSameMetaData. El primero hace lo mismo que AssertEqualsTable, pero en los metadatos de la tabla. El segundo hace una comparación similar, pero en los metadatos de los conjuntos de resultados.
Ejemplo 2: restricciones
En el ejemplo anterior, vimos cómo puede eliminar la restricción. Pero, ¿y si necesitamos verificarlos? Técnicamente, esto también es parte de la lógica, y también puede considerarse como un candidato para la cobertura de la prueba.
Considere la situación del ejemplo anterior. Dos tablas: [Prueba] y [Clínica]; [TrialID] FK:
Intentemos escribir un caso de prueba para probar esta restricción. Al principio, como la última vez, falsificamos mesas.
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
El objetivo es el mismo: eliminar las restricciones innecesarias. Queremos cheques aislados sin gestos innecesarios.
A continuación, devolvemos la restricción que necesitamos al lugar mediante el procedimiento ApplyConstraint:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
Aquí hemos reunido una configuración conveniente para la verificación directa. La verificación en sí misma consistirá en el hecho de que un intento de insertar datos conducirá inevitablemente a una excepción. Para que el caso de prueba funcione correctamente, necesitamos detectar esta misma excepción. El controlador de excepciones ExpectException nos ayudará con esto.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
Después de instalar el controlador, puede intentar insertar el no insertable.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
Excepción atrapada. Pase de prueba
Procedimientos de la hermana ApplyConstraint
Los desarrolladores de TSQLt nos ofrecen un enfoque similar para probar los desencadenantes. Puede usar el procedimiento ApplyTrigger para devolver un activador a la tabla falsa. Además, todo es como en el ejemplo anterior: activamos el activador, verificamos el resultado.
ExpectNoException - el antónimo de ExpectException
Para los casos en los que definitivamente no debería ocurrir una excepción, existe un procedimiento ExpectNoException. Funciona de la misma manera que una ExpectException, excepto que la prueba se considera fallida si ocurre una excepción.
Ejemplo 3: semáforo
La situación es la siguiente. Hay una serie de procedimientos almacenados y servicios de Windows. El comienzo de su ejecución puede ser causado por varios eventos externos. En este caso, el orden permisible de su ejecución es fijo. Se utiliza un semáforo para diferenciar el acceso a las tablas de la base de datos. Es un grupo de procedimientos almacenados.
Por ejemplo, considere uno de estos procedimientos. Tenemos dos mesas:
La tabla [Proceso] contiene una lista de procesos permitidos para la ejecución, [ProcStatus] - una lista de estados de estos procesos.
¿Qué hace nuestro procedimiento? Cuando se llama, se produce una serie de comprobaciones:
- El nombre del proceso a iniciar, pasado en el parámetro del procedimiento, se busca en el campo [Nombre] de la tabla [Proceso].
- Si se encontró el nombre del proceso, se verifica si actualmente es posible iniciarlo: el indicador [IsRunable] de la tabla [Proceso].
- Si resultó que el proceso es aceptable para la ejecución, queda por asegurarse de que aún no se esté ejecutando. En la tabla [ProcStatus], se verifica la ausencia de registros sobre este proceso con el estado = 'InProg'.
Si todo está bien, entonces se agrega un nuevo registro sobre este proceso con el estado 'InProg' a ProcStatus (esto se considera un lanzamiento), la ID de este registro se devuelve con el parámetro ProcStatusId. Si falla alguna verificación, entonces esperamos lo siguiente:
- Se envía una carta al administrador del sistema.
- Devuelve ProcStatusId = -1.
- No se agrega una nueva entrada en [ProcStatus].
Escribamos un caso de prueba para probar el caso cuando el proceso no está en la lista de los aceptables.
Para mayor comodidad, aplique inmediatamente FakeTable. Aquí no es tan fundamentalmente importante, pero puede ser útil:
- Estamos garantizados para deshacernos de cualquier información que pueda interferir con la ejecución correcta del caso de prueba.
- Simplificaremos más la verificación de entradas faltantes en ProcStatus.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
Para enviar un mensaje, se utiliza el procedimiento [SendEmail] escrito por nuestros programadores. Para verificar el envío de una carta a los administradores, debemos atender su llamada. Para este caso, tSQLt nos ofrece usar SpyProcedure.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
SpyProcedure hace lo siguiente:
- Crea una tabla con el formato [dbo]. [SendEmail_SpyProcedureLog].
- Al igual que FakeTable, reemplaza el procedimiento original con el suyo, con el mismo nombre, pero que contiene la lógica de registro. Si lo desea, puede agregar cualquiera de sus propias lógicas.
Como puede suponer, el registro se produce en la tabla [dbo]. [SendEmail_SpyProcedureLog]. Esta tabla contiene la columna [_ID_]: el número de secuencia de la llamada al procedimiento. Las columnas posteriores llevan los nombres de los parámetros pasados al procedimiento, y los valores pasados en las llamadas se recopilan en ellos. Si es necesario, también se pueden verificar.
Lo último que debemos hacer antes de llamar al semáforo y verificar es crear una variable en la que colocaremos el ID de registro de la tabla [ProcStatus] (más precisamente, -1, porque el registro no se agregará).
DECLARE @ProcStatusId BIGINT;
Llama al semáforo:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT;
Eso es todo, ahora tenemos todos los datos necesarios para la verificación. Comencemos por verificar el envío.
letras:
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
En este caso, decidimos no verificar los parámetros transmitidos durante el envío, sino simplemente verificar el hecho mismo. Le llamo la atención sobre el procedimiento tSQLt.Fail. Le permite fallar "oficialmente" el caso de prueba. Si necesita construir una construcción específica, tSQLt.Fail le permitirá hacer esto.
Luego, verifique la ausencia de entradas en [dbo]. [ProcStatus]:
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
Aquí es donde la FakeTable que aplicamos al principio nos ayudó. Gracias a él, podemos esperar el vacío. Sin ella, para una verificación precisa, deberíamos, en el buen sentido, comparar el número de registros antes y después del semáforo.
Igualdad ProcStatusId = -1 podemos verificar fácilmente con AssertEquals:
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals es minimalista: simplemente compara dos valores, nada sobrenatural.
AssertEquals Procedimientos para hermanos
Para comparar valores, contamos con una serie de procedimientos:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- Asertivo
Creo que sus nombres hablan por sí mismos. Lo único que vale la pena señalar es la existencia de un procedimiento AssertEqualsString separado. La cuestión es que AssertEquals / AssertNotEquals / AssertLike funciona con SQL_VARIANT, y NVARCHAR (MAX) no se aplica a él, y por lo tanto, los desarrolladores de tSQLt tuvieron que asignar un procedimiento separado para probar NVARCHAR (MAX).
Función falsa
FakeFunction con algo de estiramiento puede llamarse un procedimiento similar a SpyProcedure. Este falso le permite reemplazar cualquier función con la más simple necesaria. Dado que las funciones en SQL Server funcionan según el principio de un tubo con pasta de dientes, dan el resultado a través del "único agujero tecnológico", entonces, desafortunadamente, no se proporciona ninguna funcionalidad de registro. Solo un reemplazo para la lógica.
Trampas
Vale la pena señalar algunas dificultades que puede encontrar al trabajar con tSQLt. En este caso, por dificultades me refiero a algunos problemas problemáticos que surgieron debido a las limitaciones de SQL Server y / o que los desarrolladores del marco no pueden resolver.
Cancelación / corrupción de transacciones
El primer y más importante problema que enfrentó nuestro equipo fue la cancelación de transacciones. SQL Server no sabe cómo deshacer las transacciones anidadas por separado, solo todo en su conjunto, hasta el más externo. Dado el hecho de que tSQLt envuelve cada caso de prueba en una transacción separada, esto se convierte en un problema. Después de todo, una reversión de transacción dentro del procedimiento de prueba puede interrumpir la ejecución de la prueba, causando un error de ejecución no descriptivo.
Para evitar este problema, utilizamos puntos de guardado. La idea es simple. Antes de comenzar una transacción en el procedimiento de prueba, verificamos si ya estamos dentro de la transacción. Si resulta que sí, estamos suponiendo que se trata de una transacción tSQLt, coloque savepoint en lugar de comenzar. Luego, si es necesario, volveremos a este punto de guardado, y no al comienzo de la transacción. Anidar como tal no lo es.
El problema se complica por la corrupción de la transacción. Si de repente algo salió mal y la excepción funcionó, entonces la transacción puede quedar condenada. Tal transacción no solo se puede confirmar, sino que también se revierte al punto de guardado, solo se revierte todo.
Dado todo lo anterior, debe aplicar el siguiente diseño:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Considere el código en partes. Primero necesitamos determinar si estamos dentro de una transacción:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
Después de recibir el indicador @isNestedTransaction, ejecute el bloque TRY y configure el punto de guardado o el inicio de la transacción, según la situación.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Después de que hayamos hecho algo útil, confirme, si este es un comienzo "real" del procedimiento.
Por supuesto, si este es un lanzamiento de un caso de prueba, no necesitamos comprometer nada. Al final de la ejecución, tSQLt simplemente revertirá todos los cambios. Si de repente algo salió mal y entramos en el bloque CATCH, entonces lo primero que debe hacer es averiguar si nuestra transacción puede incluso confirmarse.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
Podemos retroceder al punto de guardado solo si
- transacción commitable
- se lleva a cabo una prueba, es decir El punto de salvar existe.
En otros casos, necesitamos revertir toda la transacción.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
Sí, desafortunadamente, si durante una ejecución de prueba obtuvimos una transacción no confirmable, aún recibimos un error en la ejecución del caso de prueba.
FakeTable y problema con clave externa
Considere las tablas familiares [Prueba] y [Clínica]:
Recordamos el [TrialID] FK. ¿Qué problemas puede causar esto? En los ejemplos dados anteriormente, aplicamos FakeTable a ambas tablas a la vez. Si lo aplicamos solo en [Prueba], obtenemos la siguiente situación:
Un intento de insertar una entrada en [Clínica], de esta manera, puede llegar a ser un fracaso (incluso si preparamos todos los datos necesarios en la versión falsa de la tabla [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: debes fingir todo o no falsificar nada. En el segundo caso, es obvio que la base debe estar preparada de antemano para la prueba.
SpyProcedure sobre procedimientos del sistema
Desgraciadamente, espiar las llamadas a los procedimientos del sistema fallará.
[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 al procedimiento [SendEmail] escrito por nuestros desarrolladores. En este caso, escribir un procedimiento separado se debe a la necesidad de recopilar y procesar información adicional antes de enviar mensajes directamente. En general, uno debe estar mentalmente preparado para el hecho de que puede tener que escribir procedimientos de capa intermedia para algunos procedimientos del sistema con el único fin de realizar pruebas.
Ventajas
Instalación rápida
La instalación se realiza en 2 etapas y dura aproximadamente 2 minutos. Solo necesita activar el CLR en el servidor, si aún no lo ha hecho, y ejecutar un solo script. Todo, puede agregar la primera clase de prueba y escribir casos de prueba.
Desarrollo rápido
tSQLt es una herramienta fácil de aprender. Me tomó un pequeño día dominarlo. Le pregunté a mis colegas que trabajaban con el marco, y resultó que todos pasarían aproximadamente un día.
Implementación rápida en CI
Tomó aproximadamente 2 horas configurar la integración en CI en nuestro proyecto. El tiempo, por supuesto, puede variar, pero en general esto no es un problema, y la integración puede llevarse a cabo muy rápidamente.
Amplia gama de herramientas
Esta es una evaluación subjetiva, pero, en mi opinión, la funcionalidad proporcionada por tSQLt es bastante rica y cubre la mayor parte de las necesidades en la práctica. Para casos raros cuando no hay suficientes falsificaciones y afirmaciones incorporadas, hay, por supuesto, tSQLt.Fail.
Documentación conveniente
La documentación oficial es conveniente y consistente. Con su ayuda, puede comprender fácilmente la esencia del uso de tSQLt en poco tiempo, incluso si esta es su primera herramienta de prueba unitaria.
Salida conveniente
Los datos se pueden obtener en una forma de texto muy clara:
[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 puede extraer de la base de datos (cliqueable) ...
... u obtener en formato 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>
La última opción le permite integrar fácilmente pruebas en CI. En particular, todo funciona para nosotros bajo Atlassian Bamboo.
Soporte Redgate
Las ventajas incluyen soporte para un proveedor tan grande de herramientas DBA como RedGate. SQL Test, su complemento para SQL Server Management Studio, funciona con tSQLt desde el primer momento. Además, RedGate brinda asistencia al desarrollador principal de tSQLt con el entorno de desarrollo, como afirma el propio desarrollador en los
grupos de Google .
Desventajas
No hay falsificaciones de mesa temporales
tSQLt no permite tablas temporales falsas. Si es necesario, puede usar el
complemento no oficial . Desafortunadamente, este complemento solo es compatible con SQL Server 2016+.
Sin acceso a bases de datos externas
No funcionará mantener una base separada solo para almacenar el marco. tSQLt está diseñado para probar lo que contiene en la misma base de datos. Falso, por desgracia, no funcionará.
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
Las afirmaciones parecen funcionar, pero nadie garantiza su rendimiento, por supuesto.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
Errores de documentación
A pesar de que escribí anteriormente sobre la consistencia y accesibilidad de la documentación, también contiene problemas. Hay algunos momentos desactualizados en él.
Ejemplo 1. La
“Guía de inicio rápido” sugiere descargar el marco de SourceForge. Se despidieron de SourceForge
en 2015 .
Ejemplo 2. La
guía ApplyConstraint del ejemplo utiliza la construcción pesada con el procedimiento Fail para detectar la excepción, que sería más fácil y visual de reemplazar con 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 es natural, porque tiene lugar ...
Abandono parcial
Hay un largo descanso en el desarrollo de tSQLt desde principios de 2016 hasta junio de 2019. Sí, desafortunadamente, esta herramienta está parcialmente abandonada. En 2019, poco a poco, a
juzgar por GitHub , el desarrollo aún avanzó. Aunque los Grupos de Google oficiales
tienen un hilo en el que se le preguntó directamente a Sebastian, el desarrollador principal de tSQLt, sobre el destino del desarrollo. La última pregunta se hizo el 2 de marzo de 2019, la respuesta aún no se ha recibido.
Problema con SQL Server 2017
Si está utilizando SQL Server 2017, entonces para usted, posiblemente instalar tSQLt requerirá alguna manipulación adicional.
El caso es que, por primera vez desde 2012, SQL Server realizó cambios de seguridad. En el nivel del servidor, se agregó el indicador "Seguridad estricta de CLR", que prohíbe la creación de ensamblajes sin firmar (incluso SEGURO). Una descripción detallada del problema merece un artículo separado (y, afortunadamente, todo ya está bien descrito y los artículos posteriores de la serie). Solo prepárate mentalmente para ello.Por supuesto, este inconveniente podría atribuirse a "dificultades", cuya solución no depende de los desarrolladores de tSQLt, pero es posible resolver este problema a nivel de marco, aunque requiere un poco de tiempo. GitHub ya tiene un problema , sin embargo, con su permiso, se ha retrasado desde octubre de 2017 (consulte la subcláusula anterior).Alternativas (±) para otros DBMS
. tSQLt . , (CLR, T-SQL SQL ), , . , tSQLt , SQL-powered .
, PostgreSQL
ptTAP . «» PL/pgSQL TAP. MySQL , —
MyTAP . Oracle,
utPLSQL- Una herramienta de desarrollo muy poderosa y activa (incluso diría, más que).Conclusión
Quizás, con toda la información anterior, quería transmitir dos pensamientos principales.El primero es la utilidad de probar el código en una base de datos. Ya sea que esté sentado en SQL Server, Oracle o MySQL, no importa. Si tiene una cierta cantidad de lógica no probada almacenada en la base de datos, entonces asume riesgos adicionales. Los errores en el código de la base de datos son capaces, como los errores en el resto del código, de causar daños al producto y, como resultado, a la compañía que lo suministra.La segunda idea es elegir una herramienta. Si usted, como yo, trabaja con SQL Server, entonces tSQLt es, si no 100% ganador, entonces definitivamente vale la pena su atención. Incluso a pesar del lento desarrollo reciente, sigue siendo una herramienta relevante que facilita enormemente las pruebas.Fuentes que me ayudaron (lista incompleta)