Share
[Trem on Rails] Como não odiar seus testes
Sabemos que escrever testes automatizados é extremamente importante, pois, entre outras coisas, eles garantem a qualidade do nosso código e o funcionamento da nossa aplicação. Apesar disso, muitas vezes acabamos com uma suíte de testes que, além de ser difícil de entender e manter, não é confiável. Nessa talk, vamos ver como reconhecer e evitar alguns dos problemas mais comuns que nos fazem odiar nossos testes, além de dar algumas dicas de como melhorar nossos testes.
Links legais:
Talk: Como não odiar seus testes (v1): https://www.youtube.com/watch?v=Dkqbck_DMoE&ab_channel=AtlasTechnologies
Livro: X-Unit Patterns: http://xunitpatterns.com/
Catálogo de test smells: https://test-smell-catalog.readthedocs.io/
Talk do Joe Yoder: Architecting Agility: https://www.youtube.com/live/0bGVSQw1948?si=7Hn3F9KDa_vhpxJs&t=2634
Camila Campos
Transcript
99 slides
-
@camposmilaa Camila Campos Meu propósito de vida é promover diversidade
e inclusão através da educação @camposmilaa Software Engineer @ Coinbase -
@camposmilaa test('should setup middleware', async () => { const nuxt
= createNuxt() const server = new Server(nuxt) server.useMiddleware = jest.fn() server.serverContext = { id: 'test-server-context' } await server.setupMiddleware() expect(server.nuxt.callHook).toBeCalledTimes(2) expect(server.nuxt.callHook).nthCalledWith(1, 'render:setupMiddleware', server.app) expect(server.nuxt.callHook).nthCalledWith(2, 'render:errorMiddleware', server.app) expect(server.useMiddleware).toBeCalledTimes(4) expect(serveStatic).toBeCalledTimes(2) expect(serveStatic).nthCalledWith(1, 'resolve(/var/nuxt/src, var/nuxt/static)', server.options.render.static) expect(server.useMiddleware).nthCalledWith(1, { dir: 'resolve(/var/nuxt/src, var/nuxt/static)', id: 'test-serve-static', prefix: 'test-render-static-prefix' }) expect(serveStatic).nthCalledWith(2, 'resolve(/var/nuxt/build, dist, client)', server.options.render.dist) expect(server.useMiddleware).nthCalledWith(2, { handler: { dir: 'resolve(/var/nuxt/build, dist, client)', id: 'test-serve-static' }, path: '__nuxt_test' }) const nuxtMiddlewareOpts = { options: server.options, nuxt: server.nuxt, renderRoute: expect.any(Function), resources: server.resources } expect(nuxtMiddleware).toBeCalledTimes(1) expect(nuxtMiddleware).toBeCalledWith(nuxtMiddlewareOpts) -
@camposmilaa @camposmilaa Tempo Sentido Demora muito pra rodar Gasta tempo
para fazer Não dá pra entender Não testa nada Falha aleatoriamente É redundante -
@camposmilaa Todo código é testado. Se não for por você,
será por quem o utiliza. -
@camposmilaa Code without tests is bad code. It doesn’t matter
how well written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is. (...) Michael Feathers -
@camposmilaa Código sem testes é código ruim. Não importa o
quão bem escrito está; não importa quão bonito ou orientado a objetos ou bem encapsulado ele está. (...) Michael Feathers -
@camposmilaa @camposmilaa Testes não são só sobre achar bugs na
aplicação! Achar bugs Poder mudar comportamento Evitar bugs Ter qualidade de código Poder refatorar Ter uma boa arquitetura Garantir funcionamento Mover com agilidade -
@camposmilaa Legal, teste é top! Mas ainda temos os problemas
de: Tempo Sentido -
@camposmilaa "Escrever testes gasta um tempo precioso do time de
desenvolvimento, que poderia estar sendo usado para fazer funcionalidades!" -
@camposmilaa @camposmilaa if you think good architecture is expensive, try
bad architecture. Joe Yoder -
@camposmilaa @camposmilaa se você acha que uma boa arquitetura custa
caro, tenta ter uma arquitetura ruim. Joe Yoder -
@camposmilaa Hora de refatorar! Setup complexo ou grande Múltiplas verificações
Muitas dependências -
@camposmilaa Hora de refatorar! Setup complexo ou grande Múltiplas verificações
Muitas dependências Muitas responsabilidades -
@camposmilaa Hora de refatorar! Setup complexo ou grande Múltiplas verificações
Fluxo de teste muito longo Muitas dependências Muitas responsabilidades -
@camposmilaa Hora de refatorar! Setup complexo ou grande Múltiplas verificações
Fluxo de teste muito longo Muitas dependências Muitas responsabilidades API complexa -
@camposmilaa Hora de refatorar! Setup complexo ou grande Múltiplas verificações
Código complexo no teste Fluxo de teste muito longo Muitas dependências Muitas responsabilidades API complexa -
@camposmilaa Hora de refatorar! Muitos passos de "exercise" Setup complexo
ou grande Múltiplas verificações Código complexo no teste Fluxo de teste muito longo Muitas dependências Muitas responsabilidades Código complexo API complexa -
@camposmilaa Se fazer teste não vale a pena, escrever código
bom também não! -
@camposmilaa @camposmilaa "Tem um tanto de testes na minha aplicação,
mas eles demoram horas para rodar. Haja paciencia! ):" -
@camposmilaa @camposmilaa testes de unidade Não é teste de unidade
se: 1. Não é rápido 2. Fala com o banco de dados 3. Faz chamadas externas 4. Escreve/Lê algum arquivo 5. Precisa de configurações especiais de ambiente -
@camposmilaa "Que que tá rolando nesse teste?" "Essa desgrama não
tá testando é nada!" "Roda de novo que vai…" -
@camposmilaa DRY Alguns padrões de código não se aplicam aos
testes Don’t Repeat Yourself -
@camposmilaa "Every piece of knowledge must have a single, unambiguous,
authoritative representation within a system" Andy Hunt e Dave Thomas - The Pragmatic Programmer -
@camposmilaa "Cada pedaço de conhecimento deve ter uma representação única,
clara e confiável dentro do sistema." Andy Hunt e Dave Thomas - The Pragmatic Programmer -
@camposmilaa it 'greets a person' do # ... end it
'gracefully greets a person' do # ... end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do person = Person.new(name: 'any-name') response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do person = Person.new(name: 'any-name') response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do person = Person.new(name: 'any-name') response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do person = Person.new(name: 'any-name') response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do person = Person.new(name: 'any-name') response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } it 'greets a person'
do response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } it 'greets a person'
do response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'greets a person' do expect(messenger.greet).to eq 'Hello, any-name' end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'greets a person' do expect(messenger.greet).to eq 'Hello, any-name' end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'greets a person' do expect(messenger.greet).to eq 'Hello, any-name' end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'does an entirely different thing' do # .... end it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'does nothing sometimes' do # .... end it 'does an entirely different thing' do # .... end it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'does any other thing' do # .... end it 'does nothing sometimes' do # .... end it 'does an entirely different thing' do # .... end it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'does something else' do # .... end it 'does any other thing' do # .... end it 'does nothing sometimes' do # .... end it 'does an entirely different thing' do # .... end it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'does something' do # .... end it 'does something else' do # .... end it 'does any other thing' do # .... end it 'does nothing sometimes' do # .... end it 'does an entirely different thing' do # .... end it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa let(:person) { Person.new(name: 'any-name') } let(:messenger) { Messenger.new(person) }
it 'does something' do # .... end it 'does something else' do # .... end it 'does any other thing' do # .... end it 'does nothing sometimes' do # .... end it 'does an entirely different thing' do # .... end it 'greets a person' do expect(messenger.greet).to eq "Hello, #{person.name}" end it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good afternoon, ma'am #{person.name}!" end -
@camposmilaa it 'gracefully greets a person' do expect(messenger.fancy_greet).to eq "Good
afternoon, ma'am #{person.name}!" end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
response = Messenger.new(person).greet expect(response).to eq 'Hello, any-name' end it 'gracefully greets a person' do person = Person.new(name: 'any-name') response = Messenger.new(person).fancy_greet expect(response).to eq "Good afternoon, ma'am any-name!" end -
@camposmilaa @camposmilaa Precisamos escrever testes com padrões Arrange Act Assert
Setup Exercise Verify Teardown -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
# Setup response = Messenger.new(person).greet # Exercise expect(response).to eq 'Hello, any-name' # Verify end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
# Setup response = Messenger.new(person).greet # Exercise expect(response).to eq 'Hello, any-name' # Verify end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
# Setup response = Messenger.new(person).greet # Exercise expect(response).to eq 'Hello, any-name' # Verify end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
# Setup response = Messenger.new(person).greet # Exercise expect(response).to eq 'Hello, any-name' # Verify end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
# Setup response = Messenger.new(person).greet # Exercise expect(response).to eq 'Hello, any-name' # Verify end -
@camposmilaa it 'greets a person' do person = Person.new(name: 'any-name')
# Setup response = Messenger.new(person).greet # Exercise expect(response).to eq 'Hello, any-name' # Verify end -
@camposmilaa it 'sends email to person' do mailer = double('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup expect(mailer).to receive(:notify).with(person.email) # Verify Messenger.new(person, mailer: mailer).send_email # Exercise end -
@camposmilaa it 'sends email to person' do mailer = double('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup expect(mailer).to receive(:notify).with(person.email) # Verify Messenger.new(person, mailer: mailer).send_email # Exercise end -
@camposmilaa it 'sends email to person' do mailer = double('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup expect(mailer).to receive(:notify).with(person.email) # Verify Messenger.new(person, mailer: mailer).send_email # Exercise end -
@camposmilaa it 'sends email to person' do mailer = double('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup expect(mailer).to receive(:notify).with(person.email) # Verify Messenger.new(person, mailer: mailer).send_email # Exercise end -
@camposmilaa it 'sends email to person' do mailer = double('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup expect(mailer).to receive(:notify).with(person.email) # Verify Messenger.new(person, mailer: mailer).send_email # Exercise end -
@camposmilaa it 'sends email to person' do mailer = double('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup expect(mailer).to receive(:notify).with(person.email) # Verify Messenger.new(person, mailer: mailer).send_email # Exercise end -
@camposmilaa it 'sends email to person' do mailer = spy('my
mailer') # Setup person = Person.new(name: 'any-name', email: '[email protected]') # Setup Messenger.new(person, mailer: mailer).send_email # Exercise expect(mailer).to have_received(:notify).with(person.email) # Verify end -
@camposmilaa Diminua testes redundantes controller service model repository A B
A B A B classe 1 classe 2 classe 4 classe 3 A B A B -
@camposmilaa Diminua testes redundantes controller service model repository A B
A B A B classe 1 classe 2 classe 4 classe 3 A B A B O teste é de unidade mesmo? Que tal remover alguns desses testes? -
@camposmilaa @camposmilaa Testes intermitentes: arrume agora ou delete! Dependentes de
ordem Precisam de configurações externas Quebram em determinados horários Funcionam em só um tipo de máquina -
@camposmilaa @camposmilaa como não odiar seus testes 1. Ter bons
testes é ter qualidade de código 2. Testes difíceis indicam código ruim 3. Um teste bom é simples e fácil de entender -
@camposmilaa para mais detalhes sobre test smells 1. Talk: Como
não odiar seus testes (v1) 2. Livro: X-Unit Patterns 3. Catálogo de test smells 4. Talk do Joe Yoder: Architecting Agility -
@camposmilaa @camposmilaa (...) undesigned applications carry the seeds of their
own destruction; they are easy to write but gradually become impossible to change. Sandi Metz -
@camposmilaa @camposmilaa (...) aplicações sem design carregam os frutos da
sua própria destruição; elas são fáceis de escrever mas gradualmente se tornam impossíveis de mudar. Sandi Metz -
@camposmilaa Testes ruins são melhor que nada Não ter testes
é melhor do que ter testes ruins