Alura > Cursos de Programação > Cursos de Kotlin > Conteúdos de Kotlin > Primeiras aulas do curso Kotlin e Spring: testes automatizados e documentação de API

Kotlin e Spring: testes automatizados e documentação de API

Testes manuais - Apresentação

Boas-vindas ao curso de Testes Automatizados e Documentação de API com Kotlin. Sou o João Vitor e serei seu instrutor.

Audiodescrição: João Victor é uma pessoa de pele clara de olhos e cabelos castanhos-escuros. Usa bigode e cavanhaque. Está com uma camiseta azul-marinho e sentado em uma cadeira preta e cinza.

Neste curso, utilizaremos um sistema que representa o fórum da Alura. A ideia desse sistema é que as pessoas usuárias possam cadastrar novos tópicos e listar tópicos a partir do nome de um curso. O objetivo é criarmos algo parecido com o fórum que temos na plataforma real.

Para fazermos isso, precisamos ter a garantia que todos os recursos que estamos utilizando na aplicação estarão corretos. Será que nossas regras de negócio estão fazendo sentido para as pessoas usuárias? Estão fazendo sentido conforme a documentação passada durante o desenvolvimento dessa aplicação? Para garantir isso, realizaremos testes.

Exploraremos ao longo do curso que um excelente teste para garantir que a regra de negócio do sistema está funcional é o teste de unidade.

O teste de unidade testa a menor parte do nosso sistema, seja uma classe ou até mesmo um método, onde podemos validar, por exemplo, uma regra de negócio.

Além disso, o teste de unidade tem como característica usar simulações de recursos. Por exemplo, não usamos o repositório da aplicação, ao invés disso fazemos uma simulação, ou seja, um mock.

Para isso, usaremos o MockK, uma ferramenta do Kotlin que vem ganhando bastante espaço no mercado.

Além das simulações, terão momentos em que precisaremos de fato testar a integração da aplicação com um banco de dados. Portanto, também faremos um teste de integração de containers.

Esse é outro recurso que vem ganhando bastante espaço e que grandes players têm utilizado. Nele, ao invés de fazer um teste de integração da aplicação com uma base de produção, homologação ou dev, sujando ao utilizar os dados do teste, tem uma ferramenta que, em tempo de execução dos nossos testes, sobe um container. Este, aplicará as migrações e nós faremos os testes. No fim desse processo o container deixa de existir.

Também teremos testes de APIs utilizando o MockMVC, um recurso do próprio Spring que nos fornece uma forma de testar as APIs.

Conseguimos testar se a API não está aceitando parâmetros errados, que o recurso da aplicação está fazendo a chamada correta para o método correto, seja através de uma entrada, a saída que esperamos tem que ser a de tópicos. Conseguimos validar também se os recursos protegidos estão realmente aceitando requisição apenas quando possui um token JWT ou uma Base Authentication.

Após garantir a confiabilidade da nossa aplicação e que as integrações estão corretas, conseguiremos utilizar o Swagger, uma ferramenta de documentação de API muito utilizada.

Por fim, teremos uma interface na qual teremos uma forma de trabalhar com a aplicação a partir da interface gráfica do Swagger.

Se esse conteúdo te interessou e você deseja aprender sobre, esperamos você no vídeo seguinte!

Testes manuais - Usando MySQL

Antes de começarmos a desenvolver nossos testes automatizados, resolvemos fazer um vídeo para mostrar qual aplicação utilizaremos para desenvolver nossos testes.

O objetivo é que o projeto seja uma representação do Fórum da Alura, onde as pessoas usuárias podem acessar o fórum de algum curso, fazer perguntas, obter respostas, participar de um bate-papo sobre um assunto específico.

A aplicação já está desenvolvida com recursos como Spring Security e toda a regra de segurança com o JWT. Temos um controller que representa um tópico. Nele podemos fazer requisições do tipo GET para esta API, que retornará tópicos referentes a um curso. Então podemos solicitar tópicos por curso ou fazer requisições sem nenhum filtro, nesse caso a API retornará todos os tópicos existentes na aplicação.

Podemos também criar posts, mas tudo isso depende da role, ou seja, a permissão que a pessoa tem para realizar determinados tipos de operações.

A aplicação tem recursos como curso, usuários, tópicos, entre outros. Trata-se de uma aplicação Kotlin com Spring Boot, que pode ser iniciada e receber requisições para podermos ver seu funcionamento.

Sabendo disso, abrimos o Postman. Nele, podemos fazer uma requisição para o recurso de tópicos passando http://localhost:8080/topicos. Recebemos um erro 403 Forbidden, porque não podemos acessar esse recurso sem estar autenticado, ou seja, sem ter um token que permita essa operação.

Para obter o token, temos um endpoint http://localhost:8080/login, onde podemos passar o username e a password para obter um token. Já temos um usuário em nosso banco de dados, cujo username é o e-mail do usuário ana.email.com e a senha é um valor padrão que criamos 126456.

{
    "username":"ana@email.com",
    "password":"123456"
}

Feito isso, clicamos no botão "Send", na lateral superior direita da tela. Assim, temos um Bearer Token no header da requisição, onde tem a autorização. Podemos copiar esse token, voltar na aba do /topicos, que também tem uma parte de Authorization na parte superior da tela, clicamos nela. Feito isso, no centro da tela aparece um campo nomeado Token, apagamos o conteúdo e colamos. Após clicamos em "Send".

Assim, temos todo o retorno do /topicos. Podemos notar que é uma consulta paginada, com o total de elementos, próxima página, página anterior. Está tudo zerado nesse momento, pois não temos ainda nenhum tópico salvo em nosso banco de dados, mas isso vai mudar à medida que avançarmos no curso e cadastrarmos tópicos.

Importante ressaltar que nossa aplicação está funcional, se tivéssemos um tópico ele seria retornado. Estamos fazendo requisições para o banco de dados, que é um banco de dados em memória. Se observarmos nosso arquivo pom.xml, notamos que temos a dependência do banco de dados h2.

Esse banco de dados funciona normalmente, mas a questão crucial é que toda vez que a aplicação é iniciada, o banco de dados também é iniciado e todas as operações com o banco de dados são feitas, como um insert, update, delete, list e select. Tudo funciona perfeitamente.

No entanto, quando a aplicação é desativada, todas as operações feitas com o banco de dados desaparecem, porque é um banco de dados em memória. Portanto, enquanto a aplicação está ativa, o banco de dados também está ativo. Mas, uma vez que a aplicação é encerrada, o banco de dados é apagado e, na próxima inicialização, temos que fazer tudo de novo.

Isso é muito bom para realizar testes e testes de integração, mas para um ambiente de produção, para um sistema que precisa salvar as respostas e as perguntas dos alunos no fórum, não podemos garantir com esse comportamento. Precisamos de algo que mantenha as informações.

Sendo assim, é interessante usarmos um banco de dados relacional. Existem várias opções no mercado, mas o que utilizaremos neste curso é o MySQL, um dos mais conhecidos no mundo utilizado por grandes empresas.

A primeira coisa que podemos fazer é apagar a dependência de h2 que está da linha 59 até a 63 no arquivo pom.xml, pois não o utilizaremos mais.

Agora, precisaremos pegar a dependência do MySQL. Para isso, podemos abrir o navegador e buscar por "Maven Repository". Clicamos no primeiro resultado de busca. Ao acessar a página, no campo de busca, localizado no centro superior da tela, procuramos por "MySWL". Clicamos no MySQL Connector/J e selecionamos a última versão disponível, que no momento em que o curso foi gravado é a 8.0.28.

Na nova página, no campo "Maven", copiamos o código. Voltamos na aplicação e na linha 82, colamos a dependência abaixo.

//Código omitido

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

//Código omitido

Feito isso, na lateral superior direita da IDE, encontramos o símbolo do Maven, indicado pela letra "M", é onde faremos o carregamento dessas mudanças. Clicamos nesse botão. Outra opção é clicar no botão vertical escrito "Maven" na lateral superior direita, depois no ícone com duas setas formando um círculo. Feito isso, o Maven fará o download da dependência e colocar o class path.

Agora, precisamos alterar o arquivo application-dev.yml no diretório resources. Lembrando que nossa aplicação trabalha com profiles, então temos o application-dev.yml e o application-prod.yml. Como por enquanto estamos fazendo tudo em desenvolvimento, localmente na nossa máquina, vamos alterar apenas o application-dev.yml.

Percebemos que todas as informações do driver H2 estão aqui, vamos alterar e colocar as do MySQL. Para facilitar nosso trabalho, deixamos esse trecho de código já pronto. Então o copiamos e colamos no fim do código para fazermos uma comparação.

spring:
    datasource:
        driverclassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/forum
        username: root
        password: root
    jpa:
        properties:
            hibernate:
                show sql: true
                format sql: true
    h2:
        console:
            enabled: true
jwt:
    secret: secret

Repare que esse novo código é semelhante ao do h2, mas específico para o MySQL. Lembrando que agora, também vamos trabalhar com usuário e senha. Sabendo disso, apagamos o trecho de código do h2, pois não o utilizaremos mais.

Nesse novo codigo, mantemos o jwt secret, as regras do jpa, onde queremos mostrar o SQL quando fazemos uma requisição em nosso console e as configurações do MySQL.

Agora, precisamos iniciar o banco de dados MySQL. Lembrando que, como trabalhamos com migrations, o banco de dados que criaremos, ao aplicar as migrations, deve fazer com que nossa aplicação funcione.

Para facilitar e não precisarmos baixar o MySQL para nossa máquina, usaremos o MySQL no Docker. Atualmente talvez essa seja a opção mais simples de usar um recurso externo como um banco de dados como o MySQL ou o PostgreSQL.

Abrimos o prompt de comando, usaremos a versão 8.0.28 do MySQL, pegando a imagem do Docker. Para isso, usamos o comando docker pull, seguido do nome da imagem que queremos do MySQL e a versão, mysql:8.0.28, e pressionamos "Enter".

docker pull mysql:8.0.28

Feito isso, a ferramenta indica que fez o pulling da biblioteca da imagem do MySQL. Porém, nós já temos a biblioteca na nossa máquina. No seu caso ao passar esse comando o download será feito, ao concluir estará pronto para usarmos o próximo comando, que é criar o container a partir dessa imagem.

O próximo passo então é passar o comando docker run seguido de -dde detected, -p para definir a porta que será 3306:3306. Importante ressaltar que o -p pe para a porta. Quando colocamos 3306, definimos que a porta do container será 3306 e isso será necessário para conseguir nos conectar ao container. Além disso, o banco de dados também será 3306 que é padrão do MySQL.

Dessa forma, quando colocarmos na string de conexão 3306, nos referimos ao banco de dados, que está após os dois pontos :, antes é o container.

Na mesma linha passamos --name mysql-container. Em seguida, configuramos a variável de ambiente -e MYSQL_ROOT_PASSWORD=root seguido de -e mysql_password=root. Por fim, dizemos qual imagem queremos usar, mysql:8.0.28.

docker run -d -p 3306:3306-name mysql-container -e MYSQL_ROOT_PASSWORD=root -e MYSQL_PASSWORD-root mysql:8.0.28

Com esse comando conseguimos iniciar o containet e temos como retorno um hash, que significa que temos um novo container ativo em nossa máquina local.

Precisamos entrar nesse container para criar nosso banco de dados, para a aplicação poder se conectar. Lembre-se de que, em nossa aplicação, ele se conectará a um banco de dados chamado forum.

No prompt de comando, passamos docker exec -it mysql-container bash para poder usar os comandos do MySQL, seguido de "Enter".

docker exec -it mysql-container bash

Após, podemos chamar o mysql seguido de -u root -p e passar a senha. Definimos nossa senha como root, você pode ter definido outra, então neste momento você passa a senha definida.

mysql -u root -p

Agora, precisamos dar um create database forum para criar o banco de dados.

create database forum

Feito isso, criamos o database. Pra conferirmos se está tudo certo, passamos o comando use forum.

use forum

Como retorno temos:

Database changed

Isso significa que deu tudo certo. Agora, derrubaremos a aplicação clicando no ícone identificado por um quadrado vermelho na barra superior direita da IDE. Depois subimos a aplicação novamente, clicando no ícone identificado por um triângulo deitado na cor verde.

Verificaremos se as migrations serão aplicadas no MySQL. Aparentemente não tivemos nenhum problema, tivemos 8 migrations aplicadas. Então, agora no Prompt de comando, passamos select * from topico seguido de "Enter".

select * from topico

A aplicação rodou com sucesso as migrations no banco de dados e já temos todas as tabelas definidas, como curso, resposta, role, tópico, usuário, usuario_role, agora estamos trabalhando com o MySQL, que faz muito sentido para nosso cenário.

A ideia era mostrar um pouco da nossa aplicação e fazer essa alteração do banco de dados.

Até o vídeo seguinte!

Testes manuais - Acrescentando campo na API

Alteramos de um banco de dados em memória para um banco de dados relacional. Chegou o momento de avançamos no desenvolvimento da nossa aplicação.

Precisaremos alterar o esquema do nosso banco de dados devido a uma solicitação do nosso cliente. Foi solicitado que na tabela de tópicos exista um campo data-alteração, onde sempre que um tópico for alterado, a data de alteração seja definida com o horário da alteração.

Para isso, vamos inserir um campo na nossa aplicação, que será um tópico no nosso fórum. Quando inserirmos o tópico, o campo data-alteração estará nulo e, uma vez que esse tópico seja alterado, precisamos definir automaticamente a data da alteração nesse campo.

Como estamos trabalhando com migrações, precisamos criar algo que fará essa alteração a partir da aplicação. Além disso, como estamos trabalhando com entidades utilizando um framework ORM (Objeto Relacional Mapeamento), que reflete tudo o que temos no banco de dados na nossa aplicação com entidades, precisaremos também fazer alterações nesses pontos.

O primeiro passo que daremos é criar, nas migrações, uma nova migração que alterará o esquema do banco de dados e acrescentará esse campo da alteração.

Antes disso, gostaríamos de informar que criamos uma nova migração V9_insert_topico.sql, onde fazemos a inserção de um tópico no banco de dados. Fizemos isso, pois, no último vídeo, fizemos uma requisição para a API /tópicos e o payload veio vazio, pois não tínhamos nenhum tópico cadastrado.

insert into topico(id, titulo, mensagem, data_criacao, status, curso_id, autor_id)
    values(1, 'Duvida sobre kotlin', 'Minha funcao let nao funciona',
        '2021-12-24 12:00:00', 'NAO_RESPONDIDO', 1, 1)

Acrescentando o campo na API

Agora, no Explorer, na pasta "bd.migration", clicamos com o botão direito e depois em "New > File". Nomeamos de V10__alter_table_add_data_alteracao.sql.

V10__alter_table_add_data_alteracao.sql

O comando dele é simples de fazer, porque queremos alterar a tabela tópico. Portanto, podemos passar um ALTER TABLE topico e adicionar uma coluna com ADD COLUM data_alteracao do tipo date.

alter table topico add column data_alteracao date

Uma vez que alteramos o banco de dados e queremos mostrar essa data para o nosso cliente, precisamos fazer algumas alterações nas nossas classes de modelo e nos DTOs, pois são eles os responsáveis por trafegar a informação do banco de dados para o cliente. Começaremos pela controller.

Quando fazemos um GET na nossa aplicação ela retorna um TopicoView. Então, no arquivo TopicoView.kt colocaremos essa nova informação. Na linha 12, passamos val dataAlteracao: LocalDate?, adicionamos o sinal de interrogação, pois o campo pode ser nulo.

//Código omitido

val dataAlteracao: LocalDate?

Precisamos fazer também a alteração no arquivo Topico.kt. Na linha 21, passamos var, ele tem essa diferença, pois faremos a alteração desse campo do objeto somente quando o update for feito no controller. O campo do objeto e é mutável, sendo assim, precisamos usar o var do Kotlin. Em seguida, passamos dataAlteracao : LocalDate?, pois também pode ser nulo.

//Código omitido

var dataAlteracao : LocalDate?

Temos um map que faz esse mapeamento do Topico para o TopicoView. Portanto, acessamos o TopicoViewMapper.kt e na linha 17 criamos o dataAlteracao = t.dataAlteracao.

//Código omitido

dataAlteracao = t.dataAlteracao

Agora falta nossa regra. No arquivo TopicoService.kt precisamos definir que quando o endpoint de atualização for chamado no tópico control através do método put, queremos que o topico.dataAlteracao seja um LocalDate.now. É exatamente isso que estava sendo solicitado, que quando for atualizado também atualize a data e a hora.

//Código omitido 

topico.dataAlteracao = LocalDate.now

Agora, precisamos testar a aplicação. Para isso, precisamos aplicar as novas migrações, acessar o Postman, gerar um token e fazer a listagem com o método listar. Dessa forma, poderemos conferir se ele está devolvendo as informações, depois fazemos um update e listamos novamente para ver se o campo foi inserido com sucesso.

Ao subirmos a aplicação, notamos um erro. A ferramenta pede que seja inserido um TopicoFormMapper, que é o mapper que faz quando inserimos uma informação. No entanto, dissemos que não vamos inserir informação de data de alteração, apenas quando for update.

O erro ocorreu porque esquecemos de inicializar o dataAlteracao no arquivo Topico.kt como nulo.

//Código omitido

var dataAlteracao: LocalDate? = null

Ao fazer isso, rodamos novamente. Ele precisa aplicar as duas migrações, vamos verificar se está certo. Notamos que fez a migração número 9 e a número 10.

Abrimos o Postman, fazemos a requisição para http://localhost:8080/login clicando em "Send" na lateral superior direita. Feito isso, copiamos o Bidder Token.

Em seguida, fazemos uma requisição http://localhost:8080/topicos utilizando o método GET. No campo de Token, colamos o conteúdo e clicamos no botão "Send".

É por isso que criamos a nova migração, para trazer o conteúdo no payload do listar. Agora, o que precisamos é fazer um PUTpara alterar essas informações e verificar se o dataAlteracao vai funcionar.

Em outra aba do postman, agora PUT, clicamos na aba "Body" e passaremos o ID do tópico, que é 1, modificaremos o título aqui para Kotlin alterado e a mensagem para alterou.

{
    "id":1,
    "titulo": "Kotlin alterado",
    "mensagem": "Alterou"
}

Clicamos em "Send". Em seguida, clicamos na aba "Authorization" e no campo Token, colamos o conteúdo. Por fim, clicamos novamente em "Send".

Feito isso, é devolvido qual foi o tópico alterado e a data de alteração.

{
    "id": 1,
    "titulo": "Kotlin alterado".
    "mensagem": "Alterou",
    "status": "NAO_RESPONDIDO",
    "dataCriacao": "2021-12-24T12:00:00",
    "dataAlteracao": "2022-02-22"
}

Se viermos no GET e clicamos em "Send" para fazer a requisição, notamos que muda o conteúdo e também temos a data de alteração. Toda a alteração aqui foi feita com sucesso, mas gostaríamos de chamar a atenção no passo a passo que foi necessário fazer no teste.

Fizemos as alterações no código e para testar tudo isso, tivemos que subir a aplicação, fazer uma requisição no Postman para verificar que a informação estava nula. Depois, foi preciso fazer uma alteração para verificar se o campo foi inserido, se a regra estava correta de inserir a data atual no momento da alteração. Depois, fizemos um novo GET. Foram uma série de passos para fazer um teste relativamente simples.

Isso foi intencional exatamente para ver o quanto é difícil fazer teste manual. A ideia é que consigamos automatizar esses testes. Descobriremos que existe uma série de testes que podemos fazer que validaria esse cenário de uma forma muito mais tranquila.

Nesse vídeo, testamos que nosso objetivo foi concluído. Na próxima aula vamos explorar as automações.

Até lá!

Sobre o curso Kotlin e Spring: testes automatizados e documentação de API

O curso Kotlin e Spring: testes automatizados e documentação de API possui 188 minutos de vídeos, em um total de 38 atividades. Gostou? Conheça nossos outros cursos de Kotlin em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Kotlin acessando integralmente esse e outros cursos, comece hoje!

Plus

De
R$ 1.800
por
12X
R$109
à vista R$1.308
  • Acesso a TODOS os cursos por 1 ano

    Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Certificado

    A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.

  • Mentorias com especialistas

    No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.

  • Comunidade exclusiva

    Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.

  • Acesso ao conteúdo das Imersões

    Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.

  • App Android e iOS para estudar onde quiser

    Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.

Matricule-se

Pro

De
R$ 2.400
por
12X
R$149
à vista R$1.788
  • Acesso a TODOS os cursos por 1 ano

    Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Certificado

    A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.

  • Mentorias com especialistas

    No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.

  • Comunidade exclusiva

    Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.

  • Acesso ao conteúdo das Imersões

    Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.

  • App Android e iOS para estudar onde quiser

    Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.

  • Luri, a inteligência artificial da Alura

    Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.

  • Alura Língua - Inglês e Espanhol

    Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.

Matricule-se

Ultra

12X
R$209
à vista R$2.508
  • Acesso a TODOS os cursos por 1 ano

    Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Certificado

    A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.

  • Mentorias com especialistas

    No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.

  • Comunidade exclusiva

    Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.

  • Acesso ao conteúdo das Imersões

    Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.

  • App Android e iOS para estudar onde quiser

    Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.

  • Luri, com mensagens ILIMITADAS

    Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.

  • Luri Vision, a IA que enxerga suas dúvidas

    Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.

  • Alura Língua - Inglês e Espanhol

    Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.

  • 6 Ebooks da Casa do Código

    Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.

Matricule-se
Conheça os Planos para Empresas

Acesso completo
durante 1 ano

Estude 24h/dia
onde e quando quiser

Novos cursos
todas as semanas