Como melhorar ao testar seu próprio código

45

Sou um desenvolvedor de software relativamente novo, e uma das coisas que acho que devo melhorar é minha capacidade de testar meu próprio código. Sempre que desenvolvo uma nova funcionalidade, acho realmente difícil seguir todos os caminhos possíveis para encontrar bugs. Eu costumo seguir o caminho onde tudo funciona. Eu sei que esta é uma questão bem conhecida que os programadores têm, mas não temos testadores no meu atual empregador e meus colegas parecem ser muito bons nisso.

Na minha organização, não fazemos desenvolvimento orientado a testes nem testes de unidade. Isso me ajudaria muito, mas não é provável que isso mude.

O que vocês acham que eu poderia fazer para superar isso? Qual abordagem você usa ao testar seu próprio código?

    
por Fran Sevillano 29.08.2011 / 14:53
fonte

14 respostas

20

O trabalho de um codificador é construir coisas.

O trabalho de um testador é quebrar as coisas.

O mais difícil é quebrar as coisas que você acabou de construir. Você só conseguirá ultrapassar essa barreira psicológica.

    
por 29.08.2011 / 15:00
fonte
14

Franciso, vou fazer algumas suposições aqui, com base no que você disse:

"Não fazemos nem TDD nem testes unitários. Isso me ajudaria muito, mas não é provável que isso mude."

A partir disso, eu suspeito que sua equipe não valoriza muito o teste ou o gerenciamento não irá liberar tempo para a equipe tentar arrumar o código existente e manter dívida técnica ao mínimo.

Primeiramente, você precisa convencer sua equipe / gerência sobre o valor do teste. Seja diplomático. Se a gerência está mantendo sua equipe avançando, você precisa mostrar alguns fatos, como a taxa de defeitos para cada lançamento. O tempo gasto corrigindo defeitos pode ser melhor gasto em outras coisas, como melhorar o aplicativo e torná-lo mais adaptável a requisitos futuros.

Se a equipe e a gerência em geral são apáticos sobre consertar o código e você se sente infeliz com isso, talvez seja necessário procurar outro lugar para trabalhar, a menos que você possa convencê-los, como eu disse. Eu encontrei este problema em diferentes graus em todos os lugares que trabalhei. Pode ser qualquer coisa, desde a falta de um modelo de domínio adequado até a má comunicação na equipe.

Cuidar do seu código e da qualidade do produto que você desenvolve é um bom atributo e um que você sempre quer incentivar em outras pessoas.

    
por 29.08.2011 / 15:17
fonte
11

Se você codificar em C, Objective-C ou C ++, você pode usar o CLang Static Analyzer para criticar sua fonte sem realmente executando-o.

Existem algumas ferramentas de depuração de memória disponíveis: ValGrind, Guard Malloc no Mac OS X, Electric Fence no * NIX.

Alguns ambientes de desenvolvimento fornecem a opção de usar um alocador de memória de depuração, que faz coisas como preencher páginas recém-alocadas e páginas recém-liberadas com lixo, detectar a liberação de ponteiros não alocados e gravar alguns dados antes e depois de cada bloco de heap. o depurador sendo chamado se o padrão conhecido desses dados for alterado.

Um cara no Slashdot disse que obteve muito valor de uma única nova linha de fonte em um depurador. "É isso", ele disse. Nem sempre sigo o seu conselho, mas quando o tenho, tem sido muito útil para mim. Mesmo se você não tiver um caso de teste que estimule um caminho de código incomum, você pode girar uma variável em seu depurador para usar esses caminhos, digamos, alocando alguma memória e, em seguida, usando o depurador para definir o novo ponteiro como NULL. endereço de memória, em seguida, percorrendo o manipulador de falha de alocação.

Use assertions - a macro assert () em C, C ++ e Objective-C. Se o seu idioma não fornecer uma função de afirmação, escreva um para você mesmo.

Use as afirmações generosas e, em seguida, deixe-as em seu código. Eu chamo assert () "O teste que continua em teste". Eu os uso mais comumente para verificar condições prévias no ponto de entrada da maioria das minhas funções. Essa é uma parte de "Programação por contrato", que é incorporada à linguagem de programação de Eiffel. A outra parte é pós-condições, isto é, usando assert () nos pontos de retorno da função, mas eu acho que não consigo tirar o máximo de milhagem como pré-condições.

Você também pode usar assert para verificar invariantes de classe. Embora nenhuma classe seja estritamente necessária para ter qualquer invariante, a maioria das classes projetadas com sensatez as possui. Uma invariante de classe é uma condição que é sempre verdadeira, exceto dentro de funções de membro, que pode temporariamente colocar seu objeto em um estado inconsistente. Tais funções sempre devem restaurar a consistência antes de retornarem.

Assim, cada função de membro poderia verificar a invariante na entrada e na saída, e a classe poderia definir uma função chamada CheckInvariant que qualquer outro código poderia chamar a qualquer momento.

Use uma ferramenta de cobertura de código para verificar quais linhas de sua fonte estão realmente sendo testadas e, em seguida, projete testes que estimulem as linhas não testadas. Por exemplo, você pode verificar os manipuladores de pouca memória executando seu aplicativo dentro de uma VM que esteja configurada com pouca memória física e sem um arquivo de troca ou um muito pequeno.

(Por alguma razão eu nunca tive conhecimento, enquanto o BeOS poderia rodar sem um arquivo de swap, era muito instável.) Dominic Giampaolo, que escreveu o sistema de arquivos BFS, me pediu para nunca rodar o BeOS sem swap. não vejo por que isso deve importar, mas deve ter sido algum tipo de artefato de implementação.)

Você também deve testar a resposta do seu código a erros de E / S. Tente armazenar todos os seus arquivos em um compartilhamento de rede e, em seguida, desconecte o cabo de rede enquanto o aplicativo tiver uma carga de trabalho alta. Da mesma forma, desconecte o cabo - ou desligue o sistema sem fio - se estiver se comunicando por uma rede.

Uma coisa que eu acho particularmente irritante são sites que não têm código Javascript robusto. As páginas do Facebook carregam dezenas de pequenos arquivos JavaScript, mas se qualquer um deles não conseguir fazer o download, a página inteira será quebrada. Só tem que haver alguma maneira de fornecer alguma tolerância a falhas, digamos, tentar novamente um download ou fornecer algum tipo de retorno razoável quando alguns de seus scripts não foram baixados.

Tente matar seu aplicativo com o depurador ou com "kill -9" no * NIX enquanto estiver no meio de um arquivo grande e importante. Se seu aplicativo for bem arquitetado, o arquivo inteiro será gravado ou não será gravado, ou talvez se for apenas parcialmente escrito, o que será gravado não será corrompido, com os dados salvos sendo completamente utilizáveis por o aplicativo ao reler o arquivo.

Os bancos de dados

sempre têm E / S de disco tolerante a falhas, mas quase nenhum outro tipo de aplicativo faz isso. Enquanto os sistemas de arquivos com journaling previnem a corrupção do sistema de arquivos em caso de falta de energia ou travamentos, eles não fazem nada para evitar a corrupção ou a perda de dados do usuário final. Essa é a responsabilidade dos aplicativos do usuário, mas quase nenhum outro além dos bancos de dados implementam a tolerância a falhas.

    
por 29.08.2011 / 15:23
fonte
10

Quando olho para testar meu código, geralmente faço uma série de processos de pensamento:

  1. Como faço para quebrar esse "coisinha" em pedaços de tamanho testável? Como posso isolar apenas o que quero testar? Quais stubs / mocks devo criar?
  2. Para cada trecho: Como eu testo esse trecho para ter certeza de que ele responde corretamente a um conjunto razoável de entradas corretas?
  3. Para cada trecho: Como testar se o trecho responde corretamente a entradas incorretas (ponteiros NULOS, valores inválidos)?
  4. Como faço para testar limites (por exemplo, onde os valores vão de assinados a não assinados, de 8 bits a 16 bits, etc.)?
  5. Quão bem meus testes cobrem o código? Há alguma condição que perdi? [Este é um ótimo lugar para ferramentas de cobertura de código.] Se há código que foi perdido e nunca pode ser executado, ele realmente precisa estar lá? [Essa é uma outra pergunta!]

A maneira mais fácil que encontrei para fazer isso é desenvolver meus testes junto com o meu código. Assim que eu escrevo mesmo um fragmento de código, gosto de escrever um teste para ele. Tentar fazer todos os testes depois de ter codificado vários milhares de linhas de código com complexidade de código ciclomático não-trivial é um pesadelo. Adicionar um ou mais dois testes depois de adicionar algumas linhas de código é muito fácil.

BTW, só porque a empresa em que você trabalha e / ou seus colegas não fazem Testes Unitários ou TDD, não significa que você não possa testá-los, a menos que sejam especificamente proibidos. Talvez usá-los para criar um código robusto seja um bom exemplo para os outros.

    
por 29.08.2011 / 15:21
fonte
5

Além do conselho dado nas outras respostas, sugiro usar as ferramentas análise estática (a Wikipedia tem uma lista de um número de ferramentas de análise estática para vários idiomas ) para encontrar possíveis defeitos antes do início do teste, bem como monitorar algumas métricas relacionadas à testabilidade do código, como complexidade ciclomática , o Medidas de complexidade do Halstead , e coesão e acoplamento (você pode medir isso com fan-in e fan-out).

Encontrar dificuldades para testar o código e facilitar o teste facilitará a gravação de casos de teste. Além disso, detectar defeitos antecipadamente adicionará valor a todas as suas práticas de garantia de qualidade (que incluem testes). A partir daqui, familiarizar-se com as ferramentas de teste de unidade e as ferramentas de zombaria tornará mais fácil a implementação do seu teste.

    
por 29.08.2011 / 15:27
fonte
3

Você pode analisar o possível uso de Tabelas da Verdade para ajudar a definir todos os caminhos possíveis em seu código. É impossível considerar todas as possibilidades em funções complexas, mas uma vez que você tenha seu tratamento estabelecido para todos os caminhos conhecidos, você pode estabelecer um tratamento para o caso else.

A maior parte dessa habilidade em particular é aprendida pela experiência. Depois de usar uma determinada estrutura por um período de tempo significativo, você começa a ver os padrões e marcas de comportamento que permitem que você examine uma parte do código e veja onde uma pequena alteração pode causar um grande erro. A única maneira que posso pensar em aumentar sua aptidão nisso é a prática.

    
por 29.08.2011 / 15:01
fonte
3

Se você disse que não precisa de testes unitários, não vejo uma abordagem melhor do que tentar quebrar seu próprio código manualmente.

Tente empurrar seu código para os limites . Por exemplo, tente passar variáveis para uma função que exceda os limites de limite. Você tem uma função que deve filtrar a entrada do usuário? Tente inserir diferentes combinações de caracteres.

Considere o ponto de vista do usuário . Tente ser um dos usuários que usarão seu aplicativo ou biblioteca de funções.

    
por 29.08.2011 / 15:08
fonte
3

but we don't have testers at my current employer and my colleagues seem to be pretty good at this

Seus colegas devem ser realmente excepcionais para não seguir TDD ou teste de unidade e nunca gerar bugs. Em algum nível, duvido que eles não estejam realizando testes de unidade.

Estou supondo que seus colegas estão fazendo mais testes do que está sendo permitido, mas como esse fato não é conhecido pela gerência, a organização sofre como resultado, porque a gerência tem a impressão de que o teste verdadeiro não está sendo realizado e o número de erros são baixos, portanto, o teste não é importante e o tempo não será programado para isso.

Converse com seus colegas e tente descobrir que tipo de teste de unidade eles estão fazendo e emule isso. Posteriormente, você pode prototipar maneiras melhores de testar unidades e atributos de TDD e, lentamente, introduzir esses conceitos à equipe para facilitar a adoção.

    
por 29.08.2011 / 15:53
fonte
2
  • Escreva seus testes antes de escrever seu código.
  • Sempre que você corrigir um bug que não foi detectado por um teste, escreva um teste para detectar esse bug.

Você deve conseguir cobertura sobre o que você escreve, mesmo que sua organização não tenha cobertura total. Como muitas coisas na programação, a experiência de fazer isso de novo e de novo é uma das melhores maneiras de ser eficiente nisso.

    
por 29.08.2011 / 17:09
fonte
2

Além de todos os outros comentários, já que você diz que seus colegas são bons em escrever testes que não sejam de caminho feliz, por que não pedir que eles emparelhem com você para escrever alguns testes.

A melhor maneira de aprender é ver como se faz e aprender o que você aprende com isso.

    
por 29.08.2011 / 18:22
fonte
2

Teste de caixa preta! Você deve criar suas classes / métodos com os testes em mente. Seus testes devem ser baseados na especificação do software e devem estar claramente definidos no seu diagrama de Sequência (via casos de uso).

Agora, você pode não querer fazer um desenvolvimento orientado a testes ...

Coloque a validação de entrada em todos os dados recebidos; não confie em ninguém. A estrutura .net lançou muitas exceções com base em argumentos inválidos, referências nulas e estados inválidos. Você já deve estar pensando em usar a validação de entrada na camada de interface do usuário, então é o mesmo truque no meio-ambiente.

Mas você deveria estar fazendo algum tipo de teste automatizado; essas coisas salvam vidas.

    
por 29.08.2011 / 19:53
fonte
2

Na minha experiência

A unidade de teste, se não for completamente automática, é inútil. É mais como um chefe de cabelo pontudo poderia comprar. Por quê ?, porque a Unidade de Teste lhe prometeu economizar tempo (e dinheiro) automatizando algum processo de teste. Mas, algumas ferramentas unitárias de teste fazem o oposto, forçando os programadores a trabalhar de alguma forma estranha e forçando outros a criar testes de extensão excessiva. Na maioria das vezes, não economiza hora de trabalho, mas aumenta o tempo de deslocamento do controle de qualidade para o desenvolvedor.

A UML é outra perda de tempo. um único quadro branco + caneta poderia fazer o mesmo, mais barato e rápido.

BTW, Como ser bom em codificar (e evitar bugs)?

  • a) atomicidade. Uma função que faz uma simples (ou uma única tarefa). Por ser fácil de entender, é fácil rastrear e é fácil resolvê-lo.

  • b) Homologia. Se, por exemplo, você chamar um banco de dados usando um procedimento de armazenamento, faça o resto do código.

  • c) Identifique, reduza e isole o "código do criativo". A maior parte do código é basicamente copy & colar. O código do criativo é o oposto, um código que é novo e age como um protótipo, pode falhar. Este código é propenso a erros lógicos, por isso é importante reduzir, isolar e identificá-lo.

  • d) Código "gelo fino", é o código que você sabe que é "incorreto" (ou potencialmente perigoso), mas ainda está precisando, por exemplo, código não seguro para um processo de múltiplas tarefas. Evite se você puder.

  • e) Evite o código da caixa preta, isso inclui código que não é feito por você (por exemplo, framework) e expressão regular. É fácil perder um bug com esse tipo de código. Por exemplo, eu trabalhei em um projeto usando o Jboss e achei não um, mas 10 erros no Jboss (usando a última versão estável), era um PITA encontrá-los. Evite especialmente o Hibernate, ele esconde a implementação, daí os bugs.

  • f) adicione comentários em seu código.

  • g) entrada do usuário como fonte de erros. identificá-lo. Por exemplo, o SQL Injection é causado por uma entrada do usuário.

  • h) Identifique o elemento ruim da equipe e separe a tarefa atribuída. Alguns programadores estão propensos a estragar o código.

  • i) Evite código desnecessário. Se, por exemplo, a classe precisar da Interface , use-a, caso contrário, evite adicionar código irrelevante.

a) eb) são fundamentais. Por exemplo, eu tive um problema com um sistema, quando clico em um botão (salvar) ele não salva o formulário. Então eu fiz uma lista de verificação:

  • o botão funciona? ... sim.
  • o banco de dados armazena algo ?. não, então o erro estava no meio da etapa.
  • então, a classe que armazena no banco de dados funciona ?. não < - ok, encontrei o erro. (estava relacionado com a permissão de banco de dados). Então eu verifiquei não só este procedimento, mas todos os procedimentos que fazem o mesmo (porque a homologia do código). Levei 5 minutos para rastrear o bug e 1 minuto para resolvê-lo (e muitos outros bugs).

E um sidenote

Most of the time QA suck (as a separate section of Dev), it is useless if they are not worked in the project. They do some generic test and nothing else much. They are unable to identify most logic bugs. In my case, i was working in a prestigious bank, a programmer finished a code then send it to QA. QA approved the code and was put in production... then the code failed (an epic fail), do you know who was blamed?. yes, the programmer.

    
por 30.08.2011 / 14:57
fonte
2

Um testador e um programador enfrentam o problema de diferentes ângulos, mas ambos os papéis devem testar a funcionalidade e encontrar bugs. Onde os papéis diferem está em foco. Um testador clássico vê o aplicativo somente do lado de fora (ou seja, caixa preta). Eles são especialistas nos requisitos funcionais do aplicativo. Espera-se que um programador seja especialista nos requisitos funcionais e no código (mas tende a se concentrar mais no código).

(Depende da organização se os programadores são explicitamente esperados para ser um especialista em requisitos. Independentemente disso, a expectativa implícita está lá - se você projetar algo errado, você - não a pessoa de requisitos - receberá o culpa.)

Esse papel de especialista duplo está sobrecarregando a mente do programador e, com exceção dos mais experientes, pode diminuir a proficiência nos requisitos. Acho que preciso mudar de assunto mentalmente para considerar os usuários do aplicativo. Veja o que me ajuda:

  1. Depuração ; defina pontos de interrupção no código e execute o aplicativo. Depois de atingir um ponto de interrupção, percorra as linhas ao interagir com o aplicativo.
  2. Teste automatizado ; escreva código que testa seu código. Isso ajuda apenas em camadas abaixo da interface do usuário.
  3. Conheça seus testadores ; eles podem conhecer o aplicativo melhor do que você, então aprenda com eles. Pergunte quais são os pontos fracos de sua aplicação e quais táticas eles usam para encontrar bugs.
  4. Conheça seus usuários ; aprenda a andar no lugar dos seus usuários. Os requisitos funcionais são a impressão digital de seus usuários. Muitas vezes há muitas coisas que seus usuários sabem sobre o aplicativo que podem não aparecer claramente nos requisitos funcionais. À medida que você entende melhor seus usuários - a natureza de seu trabalho no mundo real e como seu aplicativo deve ajudá-los - você entenderá melhor o que o aplicativo deve ser.
por 31.08.2011 / 14:27
fonte
2

Eu acho que você quer trabalhar em duas frentes. Uma é política, fazendo com que sua organização adote os testes em algum nível (com a esperança de que, com o tempo, eles adotem mais). Fale com engenheiros de QA fora do seu local de trabalho. Encontre listas de livros de controle de qualidade . Dê uma olhada em artigos relevantes da Wikipédia . Familiarize-se com os princípios e práticas de QA. Aprender essas coisas irá prepará-lo para fazer o caso mais convincente possível em sua organização. Existem bons departamentos de QA e agregam um valor considerável às suas organizações.

Como desenvolvedor individual, adote estratégias para usar em seu próprio trabalho. Use o TDD por meio do desenvolvimento de códigos e testes. Mantenha os testes claros e bem conservados. Se perguntado por que você está fazendo isso, você pode dizer que está evitando regressões e mantém seu processo de pensamento mais organizado (ambos os quais serão verdadeiros). Existe uma arte para escrever código testável , aprendê-lo. Seja um bom exemplo para seus colegas desenvolvedores.

Em parte, estou pregando para mim mesmo aqui, porque faço menos coisas do que sei que deveria.

    
por 01.09.2011 / 05:05
fonte

Tags