paint-brush
Software estável: aprenda sobre o poder dos testes automatizadospor@igorluchenkov
1,020 leituras
1,020 leituras

Software estável: aprenda sobre o poder dos testes automatizados

por Igor Luchenkov6m2024/03/02
Read on Terminal Reader

Muito longo; Para ler

Os testes automatizados são uma forma de testar seu software sem gastar muito tempo. Existem três tipos de testes: unidade, integração e ponta a ponta. Vamos nos aprofundar em cada tipo e entender por que precisamos de cada um. Os testes unitários são rápidos. O isolamento permite que eles sejam executados a qualquer momento, localmente e no CI, sem ativar os serviços dependentes/fazer chamadas de API e banco de dados. Os testes de integração são mais lentos, mas testam como as partes do seu aplicativo se conectam.
featured image - Software estável: aprenda sobre o poder dos testes automatizados
Igor Luchenkov HackerNoon profile picture

Este artigo merece sua atenção se você

  • São apaixonados por escrever software de boa qualidade e desejam melhorar a estabilidade do seu aplicativo com testes.


  • Estão cansados de ver bugs inesperados surgindo em seus sistemas de produção.


  • Precisa de ajuda para entender o que são testes automatizados e como abordá-los.

Por que precisamos de testes automatizados?

Como engenheiros, queremos construir coisas que funcionem , mas com cada novo recurso que criamos, inevitavelmente aumentamos o tamanho e a complexidade dos nossos aplicativos.


À medida que o produto cresce, torna-se cada vez mais demorado testar manualmente (por exemplo, com as mãos) todas as funcionalidades afetadas pelas alterações.


A ausência de testes automatizados nos leva a gastar muito tempo e diminuir a velocidade de envio ou a gastar muito pouco para economizar velocidade, resultando em novos bugs no backlog junto com as ligações noturnas do PagerDuty.


Pelo contrário, os computadores podem ser programados para fazer o mesmo repetidamente . Então, vamos delegar os testes aos computadores!


Tipos de testes

Pirâmide de testes


A ideia da pirâmide de testes sugere três tipos principais de testes: unidade, integração e ponta a ponta . Vamos nos aprofundar em cada tipo e entender por que precisamos de cada um.

Testes unitários

Uma unidade é um pequeno pedaço de lógica que você testa isoladamente (sem depender de outros componentes).

Os testes unitários são rápidos. Eles terminam em segundos. O isolamento permite que eles sejam executados a qualquer momento, localmente e no CI, sem ativar os serviços dependentes/fazer chamadas de API e de banco de dados.


Exemplo de teste de unidade: uma função que aceita dois números e os soma. Queremos chamá-lo com argumentos diferentes e afirmar que o valor retornado está correto.


 // Function "sum" is the unit const sum = (x, y) => x + y test('sums numbers', () => { // Call the function, record the result const result = sum(1, 2); // Assert the result expect(result).toBe(3) }) test('sums numbers', () => { // Call the function, record the result const result = sum(5, 10); // Assert the result expect(result).toBe(15) })


Um exemplo mais interessante é o componente React que renderiza algum texto após a conclusão da solicitação da API. Precisamos simular o módulo API para retornar os valores necessários para nossos testes, renderizar o componente e afirmar que o HTML renderizado tem o conteúdo que precisamos.


 // "MyComponent" is the unit const MyComponent = () => { const { isLoading } = apiModule.useSomeApiCall(); return isLoading ? <div>Loading...</div> : <div>Hello world</div> } test('renders loading spinner when loading', () => { // Mocking the API module, so that it returns the value we need jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Loading...').toBeInTheDocument() }) test('renders text content when not loading', () => { // Mocking the API module jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Hello world').toBeInTheDocument() })


Testes de Integração

Quando sua unidade interage com outras unidades (dependências) , chamamos isso de integração . Esses testes são mais lentos que os testes de unidade, mas testam como as partes do seu aplicativo se conectam.


Exemplo de teste de integração: um serviço que cria usuários em um banco de dados. Isso requer que uma instância de banco de dados ( dependência ) esteja disponível quando os testes são executados. Testaremos se o serviço pode criar e recuperar um usuário do banco de dados.


 import db from 'db' // We will be testing "createUser" and "getUser" const createUser = name => db.createUser(name) // creates a user const getUser = name => db.getUserOrNull(name) // retrieves a user or null test("creates and retrieves users", () => { // Try to get a user that doesn't exist, assert Null is returned const nonExistingUser = getUser("i don't exist") expect(nonExistingUser).toBe(null); // Create a user const userName = "test-user" createUser(userName); // Get the user that was just created, assert it's not Null const user = getUser(userName); expect(user).to.not.be(null) })


Testes ponta a ponta

É um teste ponta a ponta quando testamos o aplicativo totalmente implantado , onde todas as suas dependências estão disponíveis. Esses testes simulam melhor o comportamento real do usuário e permitem detectar todos os problemas possíveis em seu aplicativo, mas são o tipo de teste mais lento .


Sempre que quiser executar testes ponta a ponta, você deverá provisionar toda a infraestrutura e garantir que provedores terceirizados estejam disponíveis em seu ambiente.


Você deseja tê-los apenas para os recursos de missão crítica do seu aplicativo.


Vamos dar uma olhada em um exemplo de teste completo: Fluxo de login. Queremos ir ao aplicativo, preencher os dados de login, enviá-lo e ver a mensagem de boas-vindas.


 test('user can log in', () => { // Visit the login page page.goto('https://example.com/login'); // Fill in the login form page.fill('#username', 'john'); page.fill('#password', 'some-password'); // Click the login button page.click('#login-button'); // Assert the welcome message is visible page.assertTextVisible('Welcome, John!') })

Como você escolhe que tipo de teste escrever?

Lembre-se de que os testes ponta a ponta são mais lentos que os testes de integração e os testes de integração são mais lentos que os testes unitários .


Se o recurso no qual você está trabalhando for de missão crítica, considere escrever pelo menos um teste ponta a ponta (como verificar como a funcionalidade Login funciona ao desenvolver o fluxo de autenticação).


Além dos fluxos de missão crítica, queremos testar o máximo possível de casos extremos e vários estados do recurso. Os testes de integração nos permitem testar como as partes do aplicativo funcionam juntas.


Ter testes de integração para endpoints e componentes de clientes é uma boa ideia. Os endpoints devem executar as operações, produzir o resultado esperado e não gerar erros inesperados.


Os componentes do cliente devem exibir o conteúdo correto e responder às interações do usuário de acordo com a forma como você espera que eles respondam.


E finalmente, quando devemos escolher testes unitários ? Todas as pequenas funções que podem ser testadas isoladamente, como sum que soma os números, Button que renderiza a tag <button> , são ótimas candidatas para testes unitários. As unidades são perfeitas se você seguir a abordagem de Desenvolvimento Orientado a Testes .


Qual é o próximo?

Escreva alguns testes! (mas comece pequeno)

  • Instale uma estrutura de teste adequada ao seu projeto/linguagem. Cada linguagem possui uma biblioteca popular para testes, como Jest / Vitest para JavaScript, Cypress / Playwright para ponta a ponta (também usa JavaScript), JUnit para Java, etc.


  • Encontre uma pequena função em seu projeto e escreva um teste de unidade para ela.


  • Escreva um teste de integração para alguma interação componente/serviço-banco de dados.


  • Escolha um cenário crítico que possa ser testado rapidamente, como um fluxo de login simples, e escreva um teste completo para isso.


Faça as coisas acima uma vez para entender como funciona. Em seguida, faça isso novamente durante algum trabalho de recurso/bug. Em seguida, compartilhe com seus colegas para que todos façam testes, economizem tempo e durmam melhor à noite!


Recursos úteis: