Melhor maneira de testar consultas SQL [fechado]

109

Eu tive um problema em que continuamos tendo consultas SQL complexas com erros. Essencialmente, isso resulta no envio de email para clientes incorretos e outros 'problemas' como esse.

Qual é a experiência de todos na criação de consultas SQL como essa? Estamos criando novos coortes de dados a cada duas semanas.

Então, aqui estão alguns dos meus pensamentos e as limitações deles:

  • Criando dados de teste Embora isso prove que temos todos os dados corretos, não impõe a exclusão de anomalias na produção. Esses são dados que seriam considerados errados hoje, mas podem estar corretos há 10 anos; não foi documentado e, portanto, só sabemos sobre isso depois que os dados são extraídos.

  • Crie diagramas de Venn e mapas de dados Esta parece ser uma forma sólida de testar o design de uma consulta, mas não garante que a implementação esteja correta. Faz com que os desenvolvedores planejem com antecedência e pensem no que está acontecendo enquanto escrevem.

Obrigado por qualquer contribuição que você possa dar ao meu problema.

Bluephlame
fonte

Respostas:

164

Você não escreveria um aplicativo com funções de 200 linhas. Você decomporia essas funções longas em funções menores, cada uma com uma única responsabilidade claramente definida.

Por que escrever seu SQL assim?

Decomponha suas consultas, assim como você decompõe suas funções. Isso os torna mais curtos, mais simples, mais fáceis de compreender, testar , refatorar. E permite que você adicione "shims" entre eles e "wrappers" ao redor deles, assim como você faz no código procedural.

Como você faz isso? Ao transformar cada coisa significativa que uma consulta faz em uma visualização. Então você compõe consultas mais complexas a partir dessas visualizações mais simples, da mesma forma que compõe funções mais complexas a partir de funções mais primitivas.

E o melhor de tudo é que, para a maioria das composições de visualizações, você obterá exatamente o mesmo desempenho de seu RDBMS. (Para alguns, você não vai; e daí? A otimização prematura é a raiz de todos os males. Codifique corretamente primeiro e, em seguida , otimize se necessário.)

Aqui está um exemplo do uso de várias visualizações para decompor uma consulta complicada.

No exemplo, como cada exibição adiciona apenas uma transformação, cada uma pode ser testada de forma independente para encontrar erros e os testes são simples.

Aqui está a tabela base no exemplo:

create table month_value( 
    eid int not null, month int, year int,  value int );

Esta tabela é falha, porque usa duas colunas, mês e ano, para representar um dado, um mês absoluto. Aqui está nossa especificação para a nova coluna calculada:

Faremos isso como uma transformação linear, de forma que ela classifique da mesma forma que (ano, mês) e que, para qualquer tupla (ano, mês), haja um e somente valor, e todos os valores são consecutivos:

create view cm_absolute_month as 
select *, year * 12 + month as absolute_month from month_value;

Agora, o que temos que testar é inerente à nossa especificação, ou seja, que para qualquer tupla (ano, mês), há um e apenas um (mês_determinado), e que (mês_bsoluto) são consecutivos. Vamos escrever alguns testes.

Nosso teste será uma selectconsulta SQL , com a seguinte estrutura: um nome de teste e uma instrução case catenada. O nome do teste é apenas uma string arbitrária. A declaração do caso é apenas uma case whendeclaração de testethen 'passed' else 'failed' end .

As instruções de teste serão apenas seleções SQL (subconsultas) que devem ser verdadeiras para que o teste passe.

Aqui está nosso primeiro teste:

--a select statement that catenates the test name and the case statement
select concat( 
-- the test name
'For every (year, month) there is one and only one (absolute_month): ', 
-- the case statement
   case when 
-- one or more subqueries
-- in this case, an expected value and an actual value 
-- that must be equal for the test to pass
  ( select count(distinct year, month) from month_value) 
  --expected value,
  = ( select count(distinct absolute_month) from cm_absolute_month)  
  -- actual value
  -- the then and else branches of the case statement
  then 'passed' else 'failed' end
  -- close the concat function and terminate the query 
  ); 
  -- test result.

Executar essa consulta produz este resultado: For every (year, month) there is one and only one (absolute_month): passed

Desde que haja dados de teste suficientes em month_value, este teste funcionará.

Também podemos adicionar um teste para dados de teste suficientes:

select concat( 'Sufficient and sufficiently varied month_value test data: ',
   case when 
      ( select count(distinct year, month) from month_value) > 10
  and ( select count(distinct year) from month_value) > 3
  and ... more tests 
  then 'passed' else 'failed' end );

Agora vamos testar consecutivamente:

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b 
on (     (a.month + 1 = b.month and a.year = b.year) 
      or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )  
where a.absolute_month + 1 <> b.absolute_month ) = 0 
then 'passed' else 'failed' end );

Agora vamos colocar nossos testes, que são apenas consultas, em um arquivo e executar esse script no banco de dados. Na verdade, se armazenarmos nossas definições de visualização em um script (ou scripts, eu recomendo um arquivo por visualizações relacionadas) para ser executado no banco de dados, podemos adicionar nossos testes para cada visualização ao mesmo script, de modo que o ato de (re -) criar nossa visão também executa os testes da visão. Dessa forma, ambos obtemos testes de regressão quando recriamos as visualizações e, quando a criação da visualização é executada na produção, a visualização também será testada na produção.

tpdi
fonte
27
Esta é a primeira vez que vejo código limpo e teste de unidade em sql, estou feliz pelo dia :)
Maxime ARNSTAMM
1
hacks incríveis de sql
CodeFarmer
13
Isso é ótimo, mas por que usar nomes de uma letra para colunas e nomes de visualização quase ilegíveis? Por que o SQL deve ser menos autodocumentável ou legível do que o Python?
snl
1
Explicação incrível para algo útil que eu nunca olhei no mundo SQL / DB. Também adoro a maneira como você testou o banco de dados aqui.
Jackstine
Apenas como um aviso, eu vi visualizações de sql que se unem em visualizações de sql terem um desempenho muito ruim no PostgreSQL. No entanto, usei essa técnica de maneira eficaz com M $ SQL.
Ben Liyanage
6

Crie um banco de dados do sistema de teste que você pode recarregar com a freqüência que desejar. Carregue seus dados ou crie seus dados e salve-os. Produza uma maneira fácil de recarregá-lo. Anexe seu sistema de desenvolvimento a esse banco de dados e valide seu código antes de ir para a produção. Chute-se toda vez que você conseguir deixar um problema entrar em produção. Crie um conjunto de testes para verificar problemas conhecidos e aumente seu conjunto de testes com o tempo.

ojblass
fonte
4

Você pode querer verificar DbUnit , então você pode tentar escrever testes de unidade para seus programas com um conjunto fixo de dados. Dessa forma, você deve ser capaz de escrever consultas com resultados mais ou menos previsíveis.

A outra coisa que você pode querer fazer é criar um perfil de sua pilha de execução do SQL Server e descobrir se todas as consultas são realmente as corretas, por exemplo, se você estiver usando apenas uma consulta que retorna resultados corretos e incorretos, então claramente a consulta está sendo usado está em questão, mas e se o seu aplicativo estiver enviando consultas diferentes em pontos diferentes do código?

Qualquer tentativa de corrigir sua consulta seria fútil ... as consultas fraudulentas ainda podem ser as que geram os resultados errados.

Jon Limjap
fonte
2

Re: tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b  
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )   
where a.am + 1 <> b.am ) = 0  

Observe que isso só verifica se os valores am para meses consecutivos serão consecutivos, não se existem dados consecutivos (que é provavelmente o que você pretendia inicialmente). Isso sempre passará se nenhum de seus dados de origem for consecutivo (por exemplo, você só tem meses pares), mesmo se seu cálculo am estiver totalmente errado.

Também estou faltando alguma coisa, ou a segunda metade dessa cláusula ON aumenta o valor do mês errado? (ou seja, verifica se 12/2011 vem após 1/2010)

O que é pior, se bem me lembro, o SQL Server permite pelo menos menos de 10 níveis de visualizações antes que o otimizador jogue suas mãos virtuais no ar e comece a fazer varreduras completas da tabela em cada solicitação, portanto, não exagere nessa abordagem.

Lembre-se de testar seus casos de teste!

Caso contrário, criar um conjunto muito amplo de dados para abranger a maioria ou todas as formas possíveis de entradas, usando SqlUnit ou DbUnit ou qualquer outra * Unidade para automatizar a verificação de resultados esperados em relação a esses dados, e revisar, manter e atualizar conforme necessário geralmente parece ser o caminho para seguir.

Marte o Infomage
fonte