Olá Habr!
Por um longo tempo e quase sem sucesso, procuramos uma cabeça brilhante que queira expulsar o Sr. Kent Beck no mercado - ou seja, estamos procurando alguém que esteja pronto para escrever um livro sobre TDD para nós. Com exemplos reais, uma história sobre seus próprios cones e conquistas. Existem muito poucos livros sobre esse assunto e você não contestará os clássicos ... talvez seja por isso que ainda não conhecemos esse assunto.
Portanto, decidimos não apenas nos lembrar novamente que estávamos procurando por uma pessoa assim, mas também oferecer uma tradução de um artigo bastante controverso, cujo autor, Doug Arcuri, compartilha seus próprios pensamentos sobre por que o TDD não se tornou popular. Vamos discutir se ele está certo e, se não, por quê.
Esta não é uma introdução ao desenvolvimento através de testes. Aqui, apresentarei minhas próprias idéias sobre a reinicialização desta disciplina e falarei sobre as dificuldades práticas dos testes de unidade.O lendário programador Kent Beck é o autor da metodologia TDD (development through testing) em seu sentido moderno. Kent também, juntamente com Erich Gamma, contribuiu para a criação do JUnit, uma estrutura de teste amplamente usada.
Em seu livro
XP Explained (segunda edição), Kent descreve como os
princípios são formados na interseção de
valores e
práticas . Se você criar uma lista de conceitos e substituí-los em um tipo de fórmula, terá uma transformação.
[KISS, Quality, YAGNI, ...] + [Testing, Specs, ...] == [TDD, ...]
Eu respeito profundamente esse trabalho, que é para Kent um trabalho ao longo da vida - não apenas por suas obras-primas em programação, mas também pelo fato de ele explorar incansavelmente a essência da
confiança ,
coragem ,
doação ,
simplicidade e
vulnerabilidade . Todos esses atributos foram indispensáveis para a invenção da Extreme Programming (XP).
TDD é um princípio e
disciplina que são respeitados na comunidade XP. Esta disciplina já tem 19 anos.
Neste artigo, compartilharei minha opinião sobre como o TDD conseguiu assimilar. Depois, compartilharei observações pessoais interessantes que apareceram durante minha sessão de TDD. Por fim, tentarei explicar por que o TDD não foi tão duro quanto parecia. Vamos lá
TDD, pesquisa e profissionalismoNos últimos 19 anos, a disciplina de TDD tem sido objeto de controvérsia na comunidade de programação.
A primeira pergunta que um analista profissional faria: "qual é a porcentagem de desenvolvedores que usam o TDD hoje?" Se você perguntasse a um amigo de Robert Martin (tio Bob) e a um amigo de Kent Beck, a resposta seria "100%".
Apenas o tio Bob tem certeza de que
é impossível se considerar um profissional se você não praticar o desenvolvimento por meio de testes .
O tio Bob está intimamente envolvido nessa disciplina há vários anos, por isso é natural prestar atenção nele nesta revisão. O tio Bob defendeu o TDD e expandiu significativamente os limites dessa disciplina. Você pode ter certeza de que tenho o maior respeito pelo tio Bob e seu dogmatismo pragmático.
No entanto, ninguém faz a seguinte pergunta: “afinal, praticar significa“ usar conscientemente ”- mas não permite julgar a porcentagem, certo?” Na minha opinião subjetiva, a maioria dos programadores
não lidou com TDD nem por um período simbólico.
A realidade é que realmente não conhecemos esses números, pois ninguém investigou ativamente esse percentual. Todos os dados específicos são limitados a uma pequena seleção de empresas coletadas no site
WeDoTDD . Aqui você encontrará estatísticas sobre essas empresas, entrevistas com quem pratica TDD o tempo todo, mas essa lista não é grande. Além disso, é incompleto, porque mesmo uma pesquisa simples revela outras grandes organizações envolvidas no TDD - mas, talvez, não em plena capacidade.
Se não sabemos quantas empresas praticam TDD, surge a seguinte pergunta: "Qual a eficácia do TDD, a julgar pelos seus méritos mensuráveis"?
Você provavelmente ficará satisfeito com o fato de, ao longo dos anos, vários estudos terem sido conduzidos que confirmam a eficácia do TDD. Entre eles estão definitivamente relatórios oficiais da
Microsoft ,
IBM , Universidade da Carolina do Norte e
Universidade de Helsinque .
Um diagrama expressivo retirado de um relatório da Universidade de Helsinque.Até certo ponto, esses relatórios provam que a densidade de erros pode ser reduzida em 40-60%, o que requer mais trabalho; o tempo de execução aumenta em 15-35%. Esses números já estão começando a ser rastreados em livros e novas metodologias industriais, em particular na comunidade DevOps.
Respondendo parcialmente a essas perguntas, passamos à última: "Com o que posso contar quando começar a praticar TDD?" Foi pela resposta que formulei minhas observações pessoais do TDD. Vamos passar para eles.
1. TDD requer uma abordagem verbalizadaAo praticar o TDD, começamos a encontrar o fenômeno da "designação de alvo". Simplificando, projetos breves, como a preparação de testes com êxito e com êxito, são um sério desafio intelectual para o desenvolvedor. O desenvolvedor deve articular claramente: "Acredito que este teste será bem-sucedido" e "Acredito que esse teste falhará" ou "Não tenho certeza, deixe-me refletir depois de tentar essa abordagem".
O IDE tornou-se para o desenvolvedor aquele pato de borracha que implora para conversar ativamente com ela. No mínimo, nas empresas TDD, conversas desse tipo devem se mesclar em um burburinho contínuo.
Pense primeiro - e depois dê o próximo passo (ou passos).
Esse reforço desempenha um papel fundamental na comunicação: permite não apenas prever seu próximo passo, mas também estimulá-lo a escrever o código
mais simples para garantir que o teste de unidade seja aprovado. Obviamente, se o desenvolvedor ficar em silêncio, ele quase certamente perderá o rumo, após o qual terá que retornar à pista.
2. TDD bombeia a memória do motorO desenvolvedor, percorrendo seus primeiros ciclos de TDD, rapidamente se cansa - afinal, esse processo é inconveniente e constantemente pára. Essa é uma situação comum a qualquer atividade que uma pessoa está começando, mas ainda não domina. O desenvolvedor recorrerá a atalhos, tentando otimizar esse ciclo para preencher sua mão e melhorar a memória do motor.
A memória motora é indispensável para que o trabalho seja divertido e funcione como um relógio. No TDD, isso é necessário devido à repetição de ações.
Obtenha a folha de dicas com esses atalhos. Tire o máximo proveito dos atalhos do teclado no IDE para tornar os loops eficazes. Então continue procurando.
Em apenas algumas sessões, o desenvolvedor domina perfeitamente a seleção de atalhos, em particular, algumas sessões são suficientes para montar e executar um teste. Quando você pratica a criação de novos artefatos, destacando texto e navegando pelo IDE, tudo isso lhe parecerá completamente natural. Finalmente, você se tornará um verdadeiro profissional e dominará todas as técnicas de refatoração: em particular, extrair, renomear, gerar, elevar, reformatar e descer.
3. TDD requer pelo menos um pouco de reflexão sobre suas ações com antecedênciaSempre que um desenvolvedor pensa em iniciar um TDD, ele precisa ter em mente um breve mapa mental das tarefas que precisam ser resolvidas. Na abordagem tradicional de programação, esse mapa nem sempre está lá, e a tarefa em si pode ser apresentada "no nível macro" ou ter natureza de pesquisa. Talvez o desenvolvedor não saiba como resolver o problema, mas apenas imagine o objetivo. Os testes de unidade são negligenciados em relação a esse objetivo.
Enquanto se senta no trabalho e termina com outro "sente-se" - tente também fazer disso um ritual. Pense e liste primeiro. Brinque com isso. Liste mais. Então prossiga, pense. Comemore. Repita várias vezes. Então pense novamente e pare.
Seja inflexível sobre o trabalho. Acompanhe o que já foi feito - marque as caixas. Nunca dobre até que haja pelo menos um. Pense!
Talvez o texto da lista demore algum tempo que não se encaixe no ciclo de trabalho. No entanto, antes de começar, você deve ter uma lista. Sem ele, você não sabe para onde está se movendo. Em nenhum lugar sem um cartão.
// // "" -> // "a" -> // "aa" -> // "racecar" -> // "Racecar" -> // //
O desenvolvedor deve
listar os testes conforme descrito por Kent Beck. A lista de testes permite que você resolva o problema na forma de ciclos que passam suavemente um para o outro. Acima da lista de testes, você precisa processar e atualizar constantemente, mesmo que apenas alguns segundos antes dos testes. Se a lista de testes for aprovada quase completamente menos o último estágio, o resultado será "vermelho" e todo o teste falhará.
4. TDD depende da comunicação com os colegasApós a conclusão da lista acima, algumas etapas podem ser bloqueadas, porque não descrevem claramente o que fazer. O desenvolvedor não entendeu a lista de testes. O oposto também acontece - a lista é muito grosseira, na qual existem muitas suposições sobre os requisitos que ainda não foram formulados. Se você receber algo assim, pare imediatamente.
Agir sem TDD pode resultar em implementações excessivamente complexas. Trabalhar no estilo de TDD, mas sem pensar, sem uma lista, não é menos perigoso.
Se você perceber que existem lacunas na lista de testes, levante-se e diga em voz alta.
No TDD, o desenvolvedor deve entender qual produto fazer, sendo guiado pela idéia dos requisitos necessários na interpretação do proprietário - e nada mais. Se o requisito nesse contexto não for claro, a lista de testes começará a desmoronar. Essa falha precisa ser discutida. Uma discussão calma ajuda rapidamente a criar confiança e respeito. Além disso, é assim que os loops de feedback rápidos são formados.
5. TDD requer uma arquitetura iterativaDe volta à primeira edição de seu livro sobre XP, Kent sugeriu que os testes deveriam ser a força motriz da arquitetura. No entanto, ao longo de vários anos, surgiram histórias sobre como as equipes de sprint tropeçam em um muro já em alguns sprints.
Obviamente, construir uma arquitetura baseada em testes é irracional. O próprio tio Bob concordou com outros especialistas que não era bom. É necessário um mapa mais extenso, mas não muito longe das listas de teste que você desenvolveu "em campo".
Muitos anos depois, Kent também expressou essa tese em seu livro
TDD By Example .
Competitividade e
segurança são duas áreas principais em que o TDD não pode ser a força motriz, e o desenvolvedor deve lidar com eles separadamente. Podemos dizer que a competitividade é um nível diferente de design do sistema; a competitividade precisa ser desenvolvida por iterações, coordenando esse processo com o TDD. Isso é especialmente verdade hoje, pois algumas arquiteturas estão evoluindo para um
paradigma reativo e extensões reativas (
reatividade é competitividade no seu auge).
Crie um mapa maior de toda a organização. Ajudando a ver as coisas um pouco em perspectiva. Certifique-se de que você e a equipe estejam seguindo o mesmo curso.
No entanto, a idéia mais importante é a
organização de todo o sistema, e uma organização TDD não é fornecida. O fato é que os testes de unidade são uma coisa de baixo nível. A arquitetura iterativa e a orquestração TDD são complexas na prática e requerem confiança entre todos os membros da equipe, programação em pares e revisões sólidas de código. Não está totalmente claro como conseguir isso, mas em breve você poderá ver que sessões breves de design devem ser conduzidas em uníssono com a implementação de listas de testes na área de assunto.
6. TDD revela fragilidade de testes de unidade e implementação degeneradaOs testes de unidade têm um recurso divertido e o TDD o denuncia completamente. Eles não permitem provar a correção. E.V.Dijkstra trabalhou nesse problema e discutiu como é possível a prova matemática em nosso caso, que preencheria essa lacuna.
Por exemplo, no exemplo a seguir, todos os testes relacionados a um palíndromo imperfeito hipotético ditado pela lógica de negócios são resolvidos. Um exemplo é desenvolvido usando a metodologia TDD.
// @Test fun `Given "", then it does not validate`() { "".validate().shouldBeFalse() } @Test fun `Given "a", then it does not validate`() { "a".validate().shouldBeFalse() } @Test fun `Given "aa", then it validates`() { "aa".validate().shouldBeTrue() } @Test fun `Given "abba", then it validates`() { "abba".validate().shouldBeTrue() } @Test fun `Given "racecar", then it validates`() { "racecar".validate().shouldBeTrue() } @Test fun `Given "Racecar", then it validates`() { "Racecar".validate().shouldBeTrue() }
De fato, existem falhas nesses testes. Os testes de unidade são frágeis mesmo nos casos mais triviais. Nunca foi possível provar sua correção, porque se tentássemos exigiria um trabalho mental incrível, e seria impossível imaginar a entrada necessária para isso.
// , fun String.validate() = if (isEmpty() || length == 1) false else toLowerCase() == toLowerCase().reversed() // , fun String.validate() = length > 1 length > 1
length > 1
pode ser chamado de
implementação degenerada . É o suficiente para resolver a tarefa, mas ele próprio não informa nada sobre o problema que estamos tentando resolver.
A questão é: quando um desenvolvedor deve parar de escrever testes? A resposta parece simples: quando é
suficiente do ponto de vista da lógica de negócios , e não de acordo com o autor do código. Isso pode prejudicar nossa
paixão pelo design , e a simplicidade pode
irritar as pessoas . Esses sentimentos são compensados pela satisfação ao ver nosso próprio código limpo e pelo entendimento de que subsequentemente o código pode ser refatorado com confiança. Todo o código será muito limpo.
Observe que, para toda a falta de confiabilidade, são necessários testes de unidade. Entenda seus pontos fortes e fracos. Se a imagem completa não aparecer, talvez essa lacuna ajude a preencher o
teste de mutação .
O TDD tem seus benefícios, mas essa metodologia pode nos distrair da construção de castelos de areia desnecessários. Sim, isso é uma
limitação , mas, graças a isso, você pode se mover mais rápido, mais longe e com mais confiabilidade. Talvez seja isso que o tio Bob tenha em mente ao descrever o que, do seu ponto de vista, significa
ser profissional .
Mas! Por mais frágeis que os testes de unidade nos pareçam, eles são uma necessidade absoluta. São eles que transformam o
medo em
coragem . Os testes fornecem refatoração suave de código; além disso, eles podem servir como
guia e
documentação para qualquer novo desenvolvedor que possa entrar imediatamente na pista e trabalhar em benefício do projeto - se esse projeto for bem coberto por testes de unidade.
7. TDD demonstra o loop reverso das instruções de testeDê um passo adiante. Para entender os dois fenômenos a seguir, estudamos eventos recorrentes estranhos. Para começar, vamos dar uma olhada rápida no FizzBuzz. Aqui está a nossa lista de testes.
// 9 15. [OK] // , 3, Fizz . // ...
Demos alguns passos à frente. Agora nosso teste falha.
@Test fun `Given numbers, replace those divisible by 3 with "Fizz"`() { val machine = FizzBuzz() assertEquals(machine.print(), "?") } class FizzBuzz { fun print(): String { var output = "" for (i in 9..15) { output += if (i % 3 == 0) { "Fizz " } else "${i} " } return output.trim() } } Expected <Fizz 10 11 Fizz 13 14 Fizz>, actual <?>.
Naturalmente, se duplicarmos os dados de asserção esperados em
assertEquals
, o resultado desejado será alcançado e o teste será realizado.
Às vezes, os testes com falha fornecem o resultado correto necessário para passar no teste. Não sei como nomear esses eventos ... talvez
teste de vodu . Quantas vezes você vê isso - depende em parte da sua preguiça e etiqueta durante o teste, mas já notei muitas vezes quando uma pessoa tenta obter uma implementação que funcione normalmente com conjuntos de dados prontos e previsíveis.
8. TDD mostra a sequência de transformaçõesTDD pode prender você. Acontece que o desenvolvedor fica confuso com as transformações feitas por ele, que ele usa para alcançar a implementação desejada. Em algum momento, o código de teste se transforma em um gargalo onde paramos.
Um
beco sem saída se forma. O desenvolvedor precisa recuar e desarmar, removendo alguns dos testes para sair dessa armadilha. O desenvolvedor permanece desprotegido.
O tio Bob provavelmente caía em tais impasses ao longo dos anos de sua carreira, após o que aparentemente percebeu que, para passar no teste, tinha que definir a seqüência correta de ações para minimizar a probabilidade de chegar a um beco sem saída. Além disso, ele teve que perceber outra condição.
Quanto mais testes específicos se tornam, mais generalizado é o código .
A sequência de transformações. Você deve sempre procurar a opção mais simples (no topo da lista).Essa é a
condição de prioridade da
transição . Aparentemente, existe uma certa ordem de risco de refatoração que estamos prontos para alcançar ao passar no teste. Geralmente, é melhor escolher a opção de conversão mostrada no topo da lista (a mais simples). Nesse caso, a probabilidade de chegar a um beco sem saída permanece mínima.
A TPP ou
a análise de teste do tio Bob , por assim dizer, é um dos fenômenos mais intrigantes, tecnológicos e empolgantes que são observados atualmente.
Use-o para manter seu código o mais simples possível.
Imprima a lista de TPP e coloque-a em sua mesa. Verifique com ele para evitar chegar a becos sem saída. Faça uma regra: a ordem deve ser simples.
Isso conclui a história das minhas principais observações. No entanto, na parte final do artigo, gostaria de voltar à pergunta que esquecemos de responder no início: "Qual é a porcentagem de programadores profissionais que usam TDD hoje?" Eu responderia: "Eu acho que existem alguns deles". Gostaria de investigar esta questão abaixo e tentar explicar o porquê.
O TDD está entrincheirado na prática?Infelizmente não. Subjetivamente, parece que a porcentagem de apoiadores é baixa e continuo pesquisando dados. Minha experiência em recrutamento, liderança de equipe e autodesenvolvimento (o que me fascina) me permite fazer as seguintes observações.
Razão 1: falta de contato com a verdadeira cultura de testesPosso razoavelmente supor que a maioria dos desenvolvedores não teve a oportunidade de aprender e trabalhar em uma verdadeira
cultura de teste .
A cultura de teste é um ambiente no qual os desenvolvedores conscientemente praticam e aprimoram a arte de testar. Eles treinam constantemente colegas que ainda não têm experiência suficiente nesse campo. O feedback foi estabelecido em cada par e em cada solicitação de pool que ajuda todos os participantes a desenvolver habilidades de teste. Além disso, há um apoio sério e uma sensação de cotovelo em toda a hierarquia de engenheiros. Todos os gerentes entendem a essência do teste e acreditam nele. Quando os prazos começam a acabar, a disciplina de teste não é descartada, mas continua a ser seguida.
Aqueles que tiveram a sorte de se testar em uma cultura de teste, como por exemplo, tive a chance de fazer essas observações. .
2:TDD, ,
xUnit Patterns Effective Unit Testing . , -, , , . .
. , . . , , , … .
3:: , , . , , ; - , – .
4:, , TDD . , .
, , : « , ». : , « » — .
– .
ConclusãoXP –
,
. – , . TDD.
, , . «» , , – , .
XP Explained. , , ., - .
, – , . , , , .
, , , .
TDD « » , . TDD . TDD .
. TDD , . TDD — , , . , TDD , . , .
@Test fun `Given software, when we build, then we expect tests`() { build(software) shoudHave tests }
, TDD – ,
,
. . , , , .