FYI: este artigo é uma versão expandida da minha palestra no SQA Days # 25.Com base na minha experiência com colegas, posso afirmar: o teste de código de banco de dados não é uma prática amplamente difundida. Isso pode ser potencialmente perigoso. A lógica do DB é escrita por seres humanos, assim como todos os outros códigos "usuais". Portanto, pode haver falhas que podem causar consequências negativas para um produto, empresa ou usuário. Se esses procedimentos são armazenados, ajudando o back-end ou modificando os dados de ETL em um armazém - sempre há um risco e o teste ajuda a diminuí-lo. Quero lhe dizer o que é o tSQLt e como isso nos ajuda a testar o código do banco de dados.
O contexto
Há um grande armazém usando o SQL Server e contendo diferentes dados de ensaios clínicos. É preenchido a partir de uma variedade de fontes (principalmente bancos de dados orientados a documentos). Muitos ETLs transformam dados no armazém em várias ocasiões. Esses dados podem ser carregados em bancos de dados menores para uso por aplicativos da Web orientados para pequenas tarefas específicas. Alguns dos clientes do cliente pediram para implementar APIs para suas necessidades. Essas APIs geralmente usam procedimentos armazenados e consultas diferentes.
Em geral, há uma quantidade muito grande de código no lado do DBMS.
Por que precisamos disso?
Como você pode ver na introdução, o código do DB faz parte do código do aplicativo e também pode conter bugs.
Acho que muitos de nós estamos familiarizados com a Curva de Boehm: os bugs são sempre mais caros para serem corrigidos posteriormente no processo. Um erro que foi cometido em um estágio de desenvolvimento anterior e localizado em um posterior pode custar mais. Isso ocorre devido à necessidade de passar por várias etapas intermediárias (codificação, teste de unidade, teste de integração, teste de sistema etc.) duas vezes: para depuração e retorno do código ao estágio em que foi encontrado. Esse efeito também é válido para a caixa do armazém. Se houver um erro em um procedimento ETL e os dados forem modificados várias vezes, devemos:
- passar por todas as etapas de transformação de dados de volta à origem do problema
- conserte o problema
- obter os dados adequados novamente (edições manuais adicionais podem ser necessárias)
- verifique se não há outros dados quebrados causados pelo bug.
Não esqueça que não vendemos brinquedos macios. Um erro em uma área como os ensaios clínicos pode prejudicar não apenas os negócios, mas também a saúde humana.
Como testar?
Como estamos falando de teste de código, queremos dizer teste de unidade e integração. Essas coisas são muito repetitivas e implicam regressão persistente. A rigor, esse teste nunca é feito manualmente (bem, exceto em alguns casos singulares provavelmente).
Bom bônus: os testes podem ser materiais de apoio para a documentação do código. Por exemplo, os requisitos podem ser assim (clicáveis):
Arquivo XLS, 2 colunas com requisitos + informações adicionais fragmentadas em outras colunas + marcação confusa. Pode ser difícil restaurar os desejos iniciais, se necessário. Os testes podem ajudar a registrar nuances de implementação. Obviamente, eles não devem ser considerados como substitutos da documentação.
Infelizmente, a complexidade do teste aumenta com o crescimento da complexidade do código; portanto, esse efeito pode ser suavizado.
Os testes podem ser uma camada de segurança adicional contra fusões espontâneas. Os testes automáticos de CI ajudam nesse problema por causa de seu formalismo.
Portanto, se decidimos usar a automação, precisamos escolher uma ferramenta para isso.
O que usar para testar?
No caso de teste de código de banco de dados, vejo duas abordagens: alimentado por SQL (quando uma ferramenta está funcionando diretamente no DBMS) e não alimentado por SQL. Aqui estão as principais diferenças que encontrei:
No caso do SQL Server, temos várias opções:
A escala "Excelente - Falha" é subjetiva, desculpe, é difícil encontrar uma maneira de contornar.
"Primeira aparição" - a data mais antiga da aparência da estrutura que eu pude encontrar - a primeira versão ou confirmação.
Como você pode ver, as alternativas baseadas em SQL foram abandonadas há muito tempo e o tSQLt é o único produto atualmente suportado. Além disso, o tSQLt vence funcionalmente. A única coisa é que o TST possui um conjunto de afirmações um pouco mais rico que o tSQLt; no entanto, duvido que isso possa superar todos os contras.
A documentação do tSQLt possui algumas nuances, que serão descritas mais adiante.
No mundo que não usa SQL, as coisas não são tão claras. Alternativas estão se desenvolvendo, embora não sejam superativas. DbFit é uma ferramenta bastante interessante baseada na estrutura FitNesse. Isso implica usar a marcação wiki para escrever testes. O Slacker também é interessante: a abordagem BDD é sugerida para teste de código do banco de dados.
Devo dizer sobre asserções em soluções que não usam SQL. À primeira vista, a quantidade de afirmações é menor e podemos pensar que essas ferramentas são piores. Mas devemos ter em mente que eles são fundamentalmente diferentes do tSQLt, portanto, esse olhar superficial é incorreto.
A última linha - "NUnit, etc." - é mais como um lembrete. Muitas estruturas usuais de teste de unidade podem ser aplicadas ao código do banco de dados com a ajuda de bibliotecas adicionais. Existem muitas N / D nesta linha porque, na verdade, essa linha inclui várias ferramentas. Essa é a fonte de "nuances" na coluna "asserções" - ferramentas diferentes podem fornecer conjuntos diferentes e não há garantia de que todas as asserções possam ser aplicadas ao DB.
Como outra métrica interessante, podemos considerar as
tendências do Google .
Nuances:
- Decidi não incluir o Slacker porque esse nome pode significar coisas diferentes (e consultas como "estrutura do Slacker" são pouco vistas no gráfico).
- Por curiosidade (e porque um slot permaneceu vazio), adicionei a tendência do TST. Mas dificilmente nos mostra a imagem real, porque é uma abreviação que pode significar coisas diferentes também.
- Não incluí o NUnit e seus análogos. Essas ferramentas são estruturas para teste de código "usual", portanto, suas tendências não são descritivas para o nosso contexto.
Como você pode ver, o tSQLt é a ferramenta mais pesquisável da lista. Outra ferramenta (menos) popular é o DbFit. Outras ferramentas têm popularidade limitada.
De um modo geral, podemos ver que o tSQLt brilha em segundo plano.
O que é tSQLt?
É fácil adivinhar que o tSQLt é uma estrutura de teste de unidade acionada por SQL. O site oficial é
https://tsqlt.org .
É prometido que o tSQLt suporta o SQL Server a partir do 2005 SP2. Não verifiquei essas revisões antecipadas, mas não vejo problemas com 2012 em nosso servidor dev e 2017 em minha máquina local.
Código aberto, licença Apache 2.0,
disponível no GitHub . Como de costume, podemos dividir, contribuir, usar gratuitamente em projetos comerciais e, o mais importante, não precisamos ter medo de spywares no CLR.
Mecânica
Casos de teste são procedimentos armazenados. Eles podem ser combinados em classes de teste (suíte de testes na terminologia xUnit).
As classes de teste nada mais são do que esquemas de banco de dados. O tSQLt exige registrá-los com o procedimento NewTestClass, que adiciona classes de teste a uma tabela especial.
É possível determinar um procedimento de configuração. Esse procedimento será executado antes de cada caso de teste separado.
O procedimento de desmontagem após a execução do caso de teste não é necessário. Cada caso de teste com seu SetUp é executado em uma transação separada que é revertida após a coleta de resultados. É muito conveniente, mas tem algumas consequências negativas - descreverei-as um pouco mais tarde.
A estrutura permite executar casos de teste um de cada vez, todas as classes de teste de uma só vez ou até todas as classes de teste registradas com um único comando.
Recursos e exemplos
Não querendo repetir o guia oficial, mostrarei os recursos do tSQLt em exemplos.
Isenção de responsabilidade:- exemplos são simplificados
- o código original não é completamente meu - são criações bastante coletivas
- o exemplo 2 é ficcionalizado por mim para demonstrar os recursos mais completamente.
Exemplo # 1: CsvSql
O seguinte foi implementado mediante solicitação de um dos clientes do cliente. Existem consultas SQL armazenadas nos campos Nvarchar (MAX). A interface do usuário mínima é criada para visualizá-los. Os conjuntos de resultados gerados por essas consultas são usados no back-end para composição posterior como arquivos CSV. Os arquivos CSV podem ser solicitados por uma chamada de API.
Os conjuntos de resultados são grandes e contêm um grande número de colunas. Um exemplo hipotético desse conjunto de resultados:
Este conjunto de resultados representa dados de ensaios clínicos. Vamos dar uma olhada no cálculo [ClinicsNum]. Temos 2 tabelas: [Trial] e [Clinic].
Existe um FK: [Clinic]. [TrialID] -> [Trial]. [TrialID]. Obviamente, é suficiente usar COUNT (*) para derivar várias clínicas:
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
Como podemos testar essa consulta? Primeiro, vamos usar o stub FakeTable, que facilitará muito nosso trabalho adicional.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
O FakeTable cria uma coisa simples - renomeia tabelas antigas e cria novas com o mesmo nome. Os mesmos nomes, as mesmas colunas, mas sem restrições e gatilhos.
Precisamos disso porque:
- O banco de dados de teste pode conter alguns dados que podem impedir uma execução adequada do teste. O FakeTable nos permite não depender deles.
- Normalmente, precisamos preencher apenas algumas colunas para fins de teste. A tabela pode conter muitos deles, geralmente contendo restrições e gatilhos. Facilitamos a inserção de dados posteriormente - inseriremos apenas as informações necessárias para o teste, mantendo o teste o mais minimalista possível.
- Não haverá execuções indesejadas, portanto, não precisamos nos preocupar com pós-efeitos.
Em seguida, inserimos os dados de teste necessários:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
Nós derivamos a consulta do banco de dados, criamos a tabela [Actual] e a preenchemos com o resultado
definido a partir da consulta.
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT… CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
Agora, preenchemos [Esperado] - nossos valores esperados:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
Quero chamar sua atenção para o fato de termos apenas uma coluna na tabela [Esperado], embora tenhamos o conjunto completo na coluna [Real].
Isso se deve a um recurso útil do procedimento AssertEqualsTable que usaremos para verificação de valores.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
Ele compara apenas as colunas que são apresentadas nas duas tabelas. É muito conveniente no nosso caso, porque a consulta em teste retorna muitas colunas, cada uma conectada com uma lógica bastante complicada. Não queremos aumentar os casos de teste, portanto esse recurso realmente ajuda. Obviamente, esse recurso é uma faca de dois gumes. Se [Real] for preenchido via SELECT TOP 0 e, a certa altura, aparecer uma coluna inesperada, esse caso de teste não será detectado. Você deve escrever verificações adicionais para cobrir isso.
Procedimentos duplos AssertEqualsTable
Vale ressaltar que o tSQLt contém 2 procedimentos como AssertEqualsTable. Eles são AssertEqualsTableSchema e AssertResultSetsHaveSameMetaData. O primeiro faz o mesmo que AssertEqualsTable, mas nos metadados das tabelas. O segundo faz o mesmo, mas nos metadados dos conjuntos de resultados.
Exemplo # 2: Restrições
O exemplo anterior nos mostrou como podemos remover restrições. Mas e se precisarmos verificar? Tecnicamente, as restrições também fazem parte da lógica e podem ser consideradas como candidatas a cobertura por testes.
Considere a situação do exemplo anterior. 2 tabelas - [Trial] e [Clinic]; [TrialID] FK:
Vamos tentar escrever um caso de teste para verificá-lo. Primeiro, como no caso anterior, falsificamos as tabelas:
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
O objetivo é o mesmo - livrar-se de limites desnecessários. Queremos verificações isoladas sem esforço indevido.
Em seguida, retornamos a restrição que queremos testar usando ApplyConstraint:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
Agora temos uma configuração para a verificação. A verificação em si é que tentar inserir dados causará uma exceção. Para a aprovação do caso de teste, precisamos capturar essa exceção. Manipulador de exceção ExpectException pode ajudar.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
Podemos tentar inserir não inseríveis após a configuração do manipulador.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
A exceção foi capturada. Teste aprovado.
Procedimentos gêmeos ApplyConstraint
A maneira de testar os gatilhos propostos pelos autores do tSQLt é semelhante a testar restrições. Podemos usar o procedimento ApplyTrigger para retornar o gatilho à tabela. Depois disso, tudo corre como no exemplo acima - inicie o gatilho, verifique o resultado.
ExpectNoException - o antônimo de ExpectException
Existe um procedimento ExpectNoException para os casos em que a exceção não deve ocorrer. Funciona da mesma maneira que ExpectException, exceto que o teste falha em caso de exceção.
Exemplo # 3: Semáforo
Existem alguns procedimentos armazenados e serviços do Windows. O início de sua execução pode ser causado por diferentes eventos externos. No entanto, a ordem de sua execução é fixa. Portanto, é necessário implementar o controle de acesso no lado do banco de dados - ou seja, um semáforo. No nosso caso, o semáforo é um grupo de procedimentos armazenados trabalhando juntos.
Vejamos um procedimento dentro do semáforo. Temos 2 tabelas - [Process] e [ProcStatus]:
A tabela [Process] contém uma lista de processos permitidos para execução. [ProcStatus], obviamente, contém a lista de status do processo da tabela anterior.
Então, o que nosso procedimento faz? Primeiro, ele faz as seguintes verificações:
- Passamos um nome de processo como um dos parâmetros de entrada do procedimento. Este nome é pesquisado no campo [Nome] da tabela [Processo].
- Se o nome do processo foi encontrado, ele verifica o sinalizador [IsRunable] da tabela [Process].
- Se o sinalizador estiver LIGADO, consideramos que o processo pode ser executado. A última verificação ocorre na tabela [ProcStatus]. Precisamos garantir que o processo não esteja sendo executado no momento, o que significa a ausência de registros sobre o processo com o status "InProg" na tabela [ProcStatus].
Se tudo estiver OK e todas as verificações forem aprovadas, adicionamos um novo registro sobre nosso processo na tabela [ProcStatus] com o status "InProg". O ID desse novo registro é retornado com o parâmetro de saída ProcStatusId.
Se algo der errado, esperamos o seguinte:
- Um email para um administrador do sistema é enviado.
- ProcStatusId = -1 é retornado.
- Nenhum novo registro [ProcStatus] adicionado.
Vamos criar um caso de teste para verificar o caso de ausência de processo na tabela [Process].
Nós usamos o FakeTable novamente. Não é tão crítico aqui, mas pode ser conveniente porque:
- É garantido que não haverá dados que possam interromper a execução do caso de teste.
- A verificação adicional da ausência de novos registros [ProcStatus] será simplificada.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
Existe um procedimento [SendEmail] cujo nome fala por si. Precisamos atender sua chamada. O tSQLt sugere o uso do SpyProcedure para isso.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
O SpyProcedure faz o seguinte:
- Cria uma tabela com um nome parecido com [dbo]. [ProcedureName_SpyProcedureLog]
- Assim como o FakeTable, substitui o procedimento original por um gerado automaticamente, com o mesmo nome, mas com lógica de log interno. Você também pode adicionar sua própria lógica ao procedimento gerado, se necessário.
Não é difícil adivinhar que os logs são gravados na tabela [dbo]. [SendEmail_SpyProcedureLog]. Esta tabela contém uma coluna [_ID_] que é para os números de sequência das chamadas. As colunas subsequentes são nomeadas após os parâmetros passados para o procedimento e usados para coletá-los, para que os valores dos parâmetros também possam ser verificados.
A última coisa que precisamos fazer antes da chamada do semáforo é criar uma variável para armazenar o valor [ProcStatusId] (para ser mais exato, -1, pois o registro não será adicionado).
DECLARE @ProcStatusId BIGINT;
Chamamos o semáforo:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT; -- here we get -1
Agora, temos todos os dados necessários para as verificações. Vamos começar verificando
que a mensagem foi enviada.
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
Nesse caso, não verificamos os parâmetros passados e testamos apenas o fato de enviar. Quero chamar sua atenção para o procedimento Fail. Ele nos permite "oficialmente" falhar em um caso de teste. Se você precisar criar uma construção sofisticada, o Fail pode ajudar.
Agora, verificamos a ausência de registros na tabela [ProcStatus] com o procedimento AssertEmptyTable.
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
Foi aqui que o FakeTable que usamos no começo nos ajudou. Com isso, podemos esperar uma tabela vazia e testar usando uma única linha de código. A maneira correta de verificar isso sem falsificação de tabela seria comparar o número de linhas antes e depois da execução do procedimento, e isso exigiria mais ações.
Podemos facilmente verificar a igualdade ProcStatusId = -1 com AssertEquals.
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals é minimalista. Apenas compara 2 valores, nada de extraordinário.
Procedimentos gêmeos AssertEquals
Temos os seguintes procedimentos para comparação de valores:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- Assertike
Os nomes são auto-explicativos, eu acho. O único procedimento que quero enfatizar é AssertEqualsString. É o procedimento dedicado à verificação de valores de string. Por que precisamos de mais um procedimento, considerando determinados AssertEquals universais? O problema é que AssertEquals / AssertNotEquals / AssertLike funcionam com o tipo SQL_VARIANT. O NVARCHAR (MAX) não está incluído no SQL_VARIANT, portanto, os desenvolvedores do tSQLt precisaram fazer um procedimento adicional.
Função falsa
Com um simples toque, podemos chamar o FakeFunction de um procedimento semelhante ao SpyProcedure. Esse falso permite substituir qualquer função por uma mais simples. Como as funções do SQL Server funcionam como um tubo de pasta de dente (o resultado é retornado pelo único "orifício"), é tecnicamente impossível implementar uma funcionalidade de log. A substituição da lógica interna é a única maneira disponível.
Armadilhas
Quero falar sobre algumas armadilhas que você pode enfrentar durante o uso do tSQLt. Nesse caso, "armadilhas" significam alguns problemas causados por restrições do SQL Server e / ou impossíveis de serem resolvidos pelos desenvolvedores da estrutura.
Reversão de transações e condenação
O primeiro e principal problema enfrentado por nossa equipe é a reversão e condenação das transações. O SQL Server não pode reverter a transação aninhada separadamente. Ele sempre reverte todas as transações até o limite. Considerando que o tSQLt agrupa cada teste em uma transação separada, pode se tornar um problema, pois a reversão dentro de um procedimento armazenado pode interromper uma execução de teste com um erro de execução não descritivo.
Como solução alternativa, usamos pontos de salvamento. A ideia é simples. No início, verificamos se estamos dentro de uma transação ou não. Se sim, supomos que é uma transação tSQLt e colocamos um ponto de salvamento, portanto, reverteremos para ela, se necessário. Se não, iniciamos uma nova transação. De fato, não permitimos aninhamento.
O problema é complicado por causa da transação - pode acontecer se uma exceção for lançada. Uma transação condenada não pode ser confirmada nem revertida para um ponto de salvamento, portanto, devemos revertê-la para a transação mais externa novamente.
Considerando os pontos descritos acima, devemos usar a seguinte estrutura:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Vamos revisar o código peça por peça. Primeiro, precisamos determinar se estamos dentro de uma transação ou não.
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
Após derivar o sinalizador @isNestedTransaction, podemos iniciar o bloco TRY e definir um ponto de salvamento ou iniciar uma transação, dependendo da situação.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Depois de fazer algo útil, confirmamos os resultados se for um procedimento "real".
Obviamente, se for um caso de teste, não precisamos confirmar nada. O tSQLt reverterá as alterações automaticamente no final.
Se algo der errado e entrarmos no bloco CATCH, precisamos determinar se a transação é comprometível ou não.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
Podemos reverter para o ponto de salvamento apenas se:
- A transação é confirmada
- É uma execução de teste, portanto, existe um ponto de salvamento.
Em todos os outros casos, devemos reverter toda a transação.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
Sim, infelizmente, se atingirmos o estado de transação inconfundível durante uma execução de teste, ainda ocorreremos o erro de execução.
Faketable e a questão da chave estrangeira
Vamos revisar as tabelas familiares [Trial] e [Clinic]
Lembramos sobre o [TrialID] FK. Que problema isso pode causar? Nos exemplos acima, aplicamos o FakeTable nas duas tabelas. Se o usarmos em apenas um deles, alcançaremos a seguinte configuração:
Portanto, uma tentativa de inserir um registro em [Clinic] pode falhar, mesmo se tivermos preparado dados na versão falsa de [Trial].
[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}
Conclusão: fingir tudo ou nada. Em caso de nenhum, obviamente, você deve preparar um banco de dados com todos os dados de teste necessários.
SpyProcedure nos procedimentos do sistema
Infelizmente, não podemos espionar os procedimentos do 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}
No exemplo do semáforo, rastreamos as chamadas do procedimento [SendEmail], criado por nossos desenvolvedores. Nesse caso, não foi exigido apenas pelo teste. Foi necessário criar um procedimento separado, pois é necessário preparar alguns dados antes do envio. Mas você deve estar mentalmente preparado para escrever um procedimento entre camadas para atingir os objetivos do teste.
Prós
Instalação rápida
A instalação do tSQLt consiste em 2 etapas e leva cerca de 2 minutos. Você precisa ativar o CLR se não estiver ativo no momento e executar um único script SQL. Isso é tudo: agora você pode adicionar sua primeira classe de teste e escrever casos de teste.
Aprendizado rápido
O tSQLt é fácil de aprender. Demorou um pouco mais de um dia útil para mim. Perguntei aos colegas e parece que também leva cerca de 1 dia útil para os outros. Duvido que possa demorar muito mais tempo.
Integração rápida de IC
Demorou cerca de 2 horas para configurar a integração do IC em nosso projeto. O tempo pode variar, é claro, mas não é um problema em geral, e pode ser feito rapidamente.
Um amplo conjunto de instrumentos
É subjetivo, mas, na minha opinião, a funcionalidade tSQLt é rica e a maior parte das necessidades pode ser coberta por ela. Se não for suficiente, você sempre pode usar o procedimento Fail para casos raros e sofisticados.
Documentação conveniente
Guias oficiais são convenientes e consistentes. Você pode entender facilmente o uso do tSQLt em um curto período, mesmo que seja sua primeira ferramenta de teste de unidade.
Saída clara
A saída do teste pode ser realizada em um 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. -----------------------------------------------------------------------------
Também pode ser derivado do banco de dados (clicável) ...
... ou até 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>
O último formato permite a integração do IC sem problemas. Especificamente, usamos o tSQLt junto com o Atlassian Bamboo.
Suporte do redgate
Como um dos profissionais, posso citar o suporte de um dos maiores fornecedores de ferramentas de DBA - RedGate. O plug-in do SQL Server Management Studio chamado SQL Test funciona com o tSQLt desde o início. Além disso, o RedGate ajuda o principal desenvolvedor do tSQLt com o ambiente de desenvolvimento, de acordo com suas palavras nos
grupos do Google .
Contras
Nenhuma tabela temporária falsificada
O tSQLt não permite falsificar tabelas temporárias. Pensamentos, em caso de necessidade, você pode usar um complemento não oficial. Infelizmente, este complemento funciona apenas com o SQL Server 2016 ou superior.
Trabalhar com bancos de dados externos
O tSQLt foi projetado para funcionar com o código no mesmo banco de dados no qual a estrutura está instalada. Portanto, pode ser impossível usá-lo com um banco de dados externo. Pelo menos, falsificações não funcionam.
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 as afirmações funcionam, mas sua trabalhabilidade não é garantida, é claro.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
Erros de documentação
Apesar de eu ter mencionado acima que os guias são convenientes e consistentes, a documentação tem alguns problemas. Ele contém peças desatualizadas.
Exemplo 1.
“Guia de início rápido” sugere o download da estrutura do SourceForge.
Eles se mudaram do SourceForge
até 2015 .
Exemplo 2.
O guia ApplyConstraint utiliza design volumoso com o procedimento Fail dentro de um exemplo de captura de exceção. Isso pode ser substituído por um código simples e 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
E isso é esperado, por causa de ...
Abandono parcial
Houve uma pausa prolongada no desenvolvimento do início de 2016 até junho de 2019. Sim, infelizmente, essa ferramenta foi parcialmente abandonada. O desenvolvimento começou lentamente em 2019, de
acordo com o GitHub . Embora os Grupos oficiais do Google
tenham um tópico em que Sebastian, o principal desenvolvedor do tSQLt, foi questionado sobre o futuro do projeto. A última pergunta foi feita em 2 de março de 2019, sem resposta.
Problema no SQL Server 2017
A instalação do tSQLt pode exigir algumas ações adicionais se você estiver usando o SQL Server 2017. A Microsoft implementou a primeira alteração de segurança desde 2012 nesta versão. O sinalizador no nível do servidor "CLR strict security" foi adicionado. Este sinalizador não permite a criação de assemblies não assinados '(mesmo o SAFE). A descrição detalhada merece um artigo separado (e, felizmente,
já temos um artigo bom; consulte também os artigos a seguir na sequência. Apenas esteja mentalmente preparado para isso.
Obviamente, eu poderia atribuir esse problema às "armadilhas", mas esse problema pode ser resolvido pelos desenvolvedores do tSQLt.
O problema do GitHub já foi levantado . Ainda assim, não foi resolvido desde outubro de 2017.
Alternativas (±) para outros SGBD
O tSQLt não é único. Embora você não possa usá-lo em outros DBMS por causa das nuances de CLR e T-SQL, ainda assim poderá encontrar algo semelhante. Vale ressaltar que essas alternativas não são muito próximas ao tSQLt, por isso quero dizer abordagem baseada em SQL.
Por exemplo, usuários do PostgreSQL podem experimentar o
pgTAP . É uma ferramenta bem desenvolvida e em desenvolvimento ativo que usa PL / pgSQL nativo para testes e formato de saída TAP. A ferramenta similar
MyTap pode ajudá-lo com testes no MySQL. Essa estrutura é um pouco menos funcional que o pgTAP, mas ainda pode ser útil. E também está em desenvolvimento ativo. Se você é um usuário feliz do Oracle, tem a oportunidade de usar a ferramenta muito poderosa
utPLSQL . Ele está se desenvolvendo muito ativamente e fornece um grande número de recursos.
Conclusão
Eu queria transmitir 2 idéias:
O primeiro: a utilidade do teste de código do banco de dados. Não é importante se você estiver usando o SQL Server, Oracle, MySQL ou outra coisa. Se seu banco de dados contiver lógica não testada, você estará assumindo riscos. Como todos os outros bugs em todos os outros códigos, os bugs do código do DB podem danificar o produto e a empresa que o fornece.
O segundo: a escolha da ferramenta. Para quem trabalha com o SQL Server, o tSQLt, mesmo que não seja um vencedor de 100%, certamente merece atenção. Apesar do desenvolvimento lento e de alguns problemas, ainda é uma estrutura prática que pode facilitar muito o seu trabalho.
Fontes que me ajudaram (lista não exaustiva)