Trilha de aprendizado · Nível 8 · Tutorial 4

Isolar dependências com substitutos de teste

Delimitar testes unitários e substituir colaboradores externos por objetos controlados, tornando os cenários reproduzíveis sem acessar recursos reais.

  • Nível: Intermediário
  • Duração: 20 min
  • 7 passos
Isolar dependências com substitutos de teste

O que você vai percorrer

  1. Definir a fronteira do teste Delimite as regras sob teste e reconheça quais colaboradores devem ficar fora do cenário isolado. 2 min
  2. Fornecer respostas com um stub manual Construa um substituto simples para controlar as respostas do repositório e testar os cenários de inscrição nova e duplicada sem acessar recursos externos. 3 min
  3. Simular falhas sem provocar problemas reais Configure uma falha determinística no repositório e verifique a reação do serviço por sua operação pública. 3 min
  4. Representar estado com um fake em memória Crie um repositório simplificado que mantém inscrições em memória e permite observar como uma gravação afeta consultas posteriores. 3 min
  5. Registrar interações que fazem parte do contrato Registre chamadas essenciais em um substituto manual para verificar qual participante foi enviado ao repositório e quando nenhuma gravação deve ocorrer. 3 min
  6. Reconhecer o que o isolamento garante Avalie o determinismo, o acoplamento e os limites de confiança de testes que usam substitutos manuais. 3 min
  7. Consolidar uma pequena suíte isolada Reúna o serviço e um substituto manual configurável em três testes reproduzíveis: sucesso, duplicidade e falha externa. 4 min

O que você vai aprender

  • Distinguir testes unitários de testes de integração pela fronteira e pelas dependências exercitadas.
  • Criar substitutos manuais com os métodos esperados pelo código consumidor.
  • Fornecer respostas e falhas controladas sem depender de arquivos ou serviços reais.
  • Verificar resultados e interações relevantes sem reproduzir os detalhes internos do código testado.

Antes de começar

  • Testar mudanças de estado em objetos
  • Compor objetos com responsabilidades distintas
  • Aplicar polimorfismo pelo comportamento dos objetos

Passo 1 de 7

Definir a fronteira do teste

Delimite as regras sob teste e reconheça quais colaboradores devem ficar fora do cenário isolado.

O que fica dentro da fronteira?

Separe regra e colaboração

Nosso caso contínuo será um serviço de inscrição em uma oficina. Ele consulta um repositório, rejeita uma inscrição duplicada e, quando o participante ainda não está inscrito, solicita o registro antes de confirmar o sucesso.

A fronteira do teste indica o comportamento que queremos verificar. Neste caso, as decisões de rejeitar ou confirmar pertencem ao serviço. Consultar e armazenar inscrições são responsabilidades do repositório, um objeto colaborador.

Em um teste unitário isolado do serviço, mantemos suas regras dentro da fronteira e colocamos o repositório real fora dela, fornecendo um substituto controlado.

A mesma unidade, duas conexões

Compare as duas configurações. A fronteira do serviço permanece igual; o que muda é o colaborador exercitado pelo teste.

Comparação entre um serviço de inscrição conectado a um repositório real e o mesmo serviço conectado a um substituto controlado.

À esquerda, o teste atravessa a fronteira e usa infraestrutura real. À direita, exercita as regras do serviço com respostas controladas.

Isolamento ou integração?

Classifique pelos componentes exercitados

Um teste é unitário isolado quando verifica a unidade escolhida sem acionar colaboradores externos reais. Um teste é de integração quando também exercita a comunicação com componentes reais, como um repositório que acessa arquivos, banco de dados ou serviços.

A classificação não depende da quantidade de classes, do tamanho da função de teste nem do uso de pytest. Ela depende da fronteira escolhida e dos componentes que realmente são executados.

Exemplo

Dois testes com a mesma chamada pública

Cenário A: o serviço recebe um objeto controlado que informa que o participante ainda não existe. O teste verifica que o serviço confirma a inscrição. O repositório real ficou fora da fronteira: é uma verificação isolada.

Cenário B: o serviço recebe o repositório real, que abre um arquivo ou consulta um serviço externo. A verificação atravessa a fronteira entre os componentes: é um teste de integração.

Recursos externos podem variar conforme dados, permissões, rede ou disponibilidade. Também podem tornar o teste lento e causar efeitos colaterais. Isso não os torna dispensáveis, mas justifica excluí-los quando o objetivo é testar somente as regras do serviço.

Escolha a fronteira adequada

Classifique o cenário

Um teste fornece ao serviço de inscrição um objeto controlado que responde “participante inexistente”. Em seguida, chama a operação pública do serviço e verifica a confirmação da inscrição. Qual classificação e fronteira descrevem melhor esse cenário?

Passo 2 de 7

Fornecer respostas com um stub manual

Construa um substituto simples para controlar as respostas do repositório e testar os cenários de inscrição nova e duplicada sem acessar recursos externos.

Um colaborador controlado pelo teste

O contrato mínimo basta

O serviço de inscrição usa somente duas operações do repositório:

  • existe(email) informa com True ou False se o participante já está inscrito;
  • registrar(email) armazena uma inscrição nova e não precisa devolver um valor.

Um stub manual pode atender a esse contrato com uma classe simples, sem herdar da implementação real. Sua resposta é escolhida durante a preparação do teste e permanece previsível.

Resposta controlada fornecida ao serviço

O serviço recebe o stub pelo construtor e usa seus métodos como usaria os do repositório real.

Diagrama de um serviço de inscrição ligado a um stub configurável, com dois caminhos de resposta: falso para inscrição nova e verdadeiro para duplicidade.

O teste troca apenas o colaborador. A decisão de aceitar ou rejeitar a inscrição continua no serviço.

O serviço mantém a regra

Crie o módulo da aplicação

Em uma pasta nova, crie o arquivo inscricoes.py. O serviço recebe qualquer objeto que ofereça os métodos esperados. Ele não importa nem cria um repositório específico.

inscricoes.py

A regra de duplicidade pertence ao serviço, não ao substituto.

python
class ServicoInscricao:
    def __init__(self, repositorio):
        self.repositorio = repositorio

    def inscrever(self, email):
        if self.repositorio.existe(email):
            return False

        self.repositorio.registrar(email)
        return True

Configure e execute os dois cenários

Crie os testes com o stub

Na mesma pasta, crie test_inscricoes.py. O stub devolve o valor recebido em seu construtor. Como o efeito de armazenamento ainda não é o foco, registrar pode não fazer nada.

test_inscricoes.py

Cada teste cria seu próprio stub e o fornece explicitamente ao serviço.

python
from inscricoes import ServicoInscricao


class StubRepositorio:
    def __init__(self, resposta_existe):
        self.resposta_existe = resposta_existe

    def existe(self, email):
        return self.resposta_existe

    def registrar(self, email):
        pass


def test_aceita_inscricao_nova():
    repositorio = StubRepositorio(resposta_existe=False)
    servico = ServicoInscricao(repositorio)

    resultado = servico.inscrever("ana@example.com")

    assert resultado is True


def test_rejeita_inscricao_duplicada():
    repositorio = StubRepositorio(resposta_existe=True)
    servico = ServicoInscricao(repositorio)

    resultado = servico.inscrever("ana@example.com")

    assert resultado is False

Execute no terminal

Com o ambiente que contém o pytest ativado, execute o comando a partir da pasta dos dois arquivos.

shell
python -m pytest -q

Dica

O stub não decide a regra

O stub apenas responde se a inscrição existe. Quem transforma essa informação em aceitação ou rejeição é ServicoInscricao. Colocar a regra de duplicidade dentro do stub faria o teste reproduzir indevidamente a lógica que deveria verificar.

Confira a configuração e o resultado

Configure a duplicidade

Para representar uma inscrição já existente, complete a configuração: StubRepositorio(resposta_existe=____)

Relate sua execução local

Execute a suíte e registre o que apareceu no terminal. Explique também o que resposta_existe=False e resposta_existe=True representaram nos dois testes.

Escreva pelo menos 40 caracteres (0/40).

Passo 3 de 7

Simular falhas sem provocar problemas reais

Configure uma falha determinística no repositório e verifique a reação do serviço por sua operação pública.

Uma falha controlada na fronteira

Regra rejeitada ou dependência indisponível?

Uma inscrição duplicada é rejeitada por uma regra do próprio serviço. Já uma ConnectionError informa que o repositório não conseguiu atender à consulta.

Para testar essa segunda situação, o stub pode lançar sempre a exceção esperada. Assim, não é necessário desligar serviços, alterar permissões ou manipular arquivos. Neste contrato, o serviço deve propagar a ConnectionError; portanto, a chamada não chega a confirmar sucesso.

O caminho da exceção

A falha nasce no colaborador controlado e atravessa a operação pública do serviço. O caminho de sucesso é interrompido.

Diagrama mostrando o serviço consultando um repositório substituto, que devolve uma exceção ao serviço e interrompe o caminho de sucesso.

O teste aciona o serviço; o stub apenas determina onde a falha externa surgirá.

Exercitar a operação pública

Stub que sempre falha na consulta

O pytest.raises envolve servico.inscrever(...), e não uma chamada direta ao stub. Desse modo, o teste verifica como o serviço reage à falha do colaborador.

python
import pytest


class ServicoInscricao:
    def __init__(self, repositorio):
        self.repositorio = repositorio

    def inscrever(self, email):
        if self.repositorio.existe(email):
            return False

        self.repositorio.registrar(email)
        return True


class RepositorioIndisponivelStub:
    def existe(self, email):
        raise ConnectionError("repositório indisponível")

    def registrar(self, email):
        pass


def test_propaga_falha_ao_consultar_repositorio():
    repositorio = RepositorioIndisponivelStub()
    servico = ServicoInscricao(repositorio)

    with pytest.raises(ConnectionError):
        servico.inscrever("ana@example.com")

Verifique o raciocínio

Qual chamada deve ser verificada?

Para verificar a reação do serviço, basta colocar repositorio.existe(...) diretamente dentro de pytest.raises.

Por que controlar a falha?

Por que um stub que lança ConnectionError é mais adequado a este teste unitário do que provocar a indisponibilidade de um recurso real?

Escreva pelo menos 40 caracteres (0/40).

Passo 4 de 7

Representar estado com um fake em memória

Crie um repositório simplificado que mantém inscrições em memória e permite observar como uma gravação afeta consultas posteriores.

Quando uma resposta fixa não basta

Do stub ao fake

Um stub devolve respostas escolhidas antecipadamente. Isso funciona quando cada consulta pode receber uma resposta fixa.

Porém, alguns cenários exigem estado: depois que o serviço registra uma inscrição, uma consulta posterior deve encontrá-la. Nesse caso, podemos usar um fake — uma implementação funcional simplificada do repositório, sem banco de dados, arquivos ou serviços externos.

O fake mantém apenas o comportamento necessário ao teste. Ele armazena e consulta inscrições; a regra de rejeitar duplicidades continua pertencendo ao serviço.

Resposta fixa e estado em memória

Comparação visual entre um substituto que sempre entrega a mesma resposta e outro que guarda uma inscrição em uma coleção para encontrá-la depois.

À esquerda, o stub entrega uma resposta predeterminada. À direita, o fake calcula a resposta consultando seu estado em memória.

Implementar o repositório em memória

O mesmo contrato, uma implementação mais simples

O serviço continua chamando existe(email) e registrar(email). O fake oferece esses mesmos métodos, mas usa um conjunto de instância. Assim, cada objeto começa com sua própria coleção e não compartilha inscrições com outros testes.

Serviço, fake e testes completos

Salve este conteúdo como test_inscricoes.py.

python
class ServicoInscricao:
    def __init__(self, repositorio):
        self.repositorio = repositorio

    def inscrever(self, email):
        if self.repositorio.existe(email):
            return False

        self.repositorio.registrar(email)
        return True


class RepositorioInscricoesEmMemoria:
    def __init__(self):
        self._inscricoes = set()

    def existe(self, email):
        return email in self._inscricoes

    def registrar(self, email):
        self._inscricoes.add(email)


def test_inscricao_registrada_passa_a_ser_encontrada():
    repositorio = RepositorioInscricoesEmMemoria()
    servico = ServicoInscricao(repositorio)

    resultado = servico.inscrever("ana@example.com")

    assert resultado is True
    assert repositorio.existe("ana@example.com") is True


def test_segunda_inscricao_do_mesmo_email_e_rejeitada():
    repositorio = RepositorioInscricoesEmMemoria()
    servico = ServicoInscricao(repositorio)

    primeiro_resultado = servico.inscrever("ana@example.com")
    segundo_resultado = servico.inscrever("ana@example.com")

    assert primeiro_resultado is True
    assert segundo_resultado is False


def test_instancias_nao_compartilham_inscricoes():
    primeiro_repositorio = RepositorioInscricoesEmMemoria()
    segundo_repositorio = RepositorioInscricoesEmMemoria()

    primeiro_repositorio.registrar("ana@example.com")

    assert primeiro_repositorio.existe("ana@example.com") is True
    assert segundo_repositorio.existe("ana@example.com") is False

Dica

Observe pela interface pública

Os testes consultam repositorio.existe(...) em vez de acessar _inscricoes. Isso verifica o efeito observável e permite mudar a coleção interna sem reescrever a expectativa do teste.

Escolher a responsabilidade correta

Relacione cenário e responsabilidade

Associe cada necessidade ao componente mais adequado.

Toque em um item e depois no par correspondente.

Executar e interpretar o teste

Prática local

No seu computador, execute o arquivo com:

python -m pytest test_inscricoes.py -v

Os três testes devem passar. Depois, observe por que _inscricoes precisa ser criada dentro de __init__: cada instância recebe um conjunto novo, evitando que dados de um teste apareçam em outro.

Registre sua conclusão

Execute os testes e explique como eles demonstram que uma gravação influencia consultas posteriores sem vazar estado entre instâncias.

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 7

Registrar interações que fazem parte do contrato

Registre chamadas essenciais em um substituto manual para verificar qual participante foi enviado ao repositório e quando nenhuma gravação deve ocorrer.

O efeito que o retorno não revela

Observe a interação essencial

O retorno de inscrever() informa se a inscrição foi aceita, mas não comprova que o participante correto foi enviado ao repositório. Quando essa gravação faz parte do contrato, o substituto pode guardar cada argumento recebido em uma lista de instância.

O teste observa essa lista para confirmar dois comportamentos: no sucesso, o serviço solicita o registro correto; na duplicidade ou na falha da consulta, não solicita registro algum.

Resultado e interação são evidências diferentes

O teste verifica tanto o resultado público do serviço quanto o registro mantido pelo substituto.

Diagrama em que um serviço de inscrição devolve um resultado ao teste e, separadamente, envia um participante a um repositório substituto que guarda a chamada em uma lista.

A lista de chamadas torna observável o argumento enviado ao colaborador, sem acessar um repositório real.

Criar um substituto que também registra

Resposta controlada e registro no mesmo objeto

Crie inscricoes.py com o serviço e o substituto abaixo. RepositorioRegistrador.existe() fornece uma resposta ou falha predeterminada, enquanto registrar() preserva os participantes recebidos em registros.

Um substituto pode cumprir mais de um papel útil ao cenário; não é necessário encaixá-lo em uma categoria rígida.

inscricoes.py

O serviço mantém a regra de duplicidade. O substituto apenas responde à consulta e registra chamadas.

python
class ServicoInscricao:
    def __init__(self, repositorio):
        self.repositorio = repositorio

    def inscrever(self, participante):
        if self.repositorio.existe(participante):
            return False

        self.repositorio.registrar(participante)
        return True


class RepositorioRegistrador:
    def __init__(self, ja_existe=False, falha_consulta=False):
        self.ja_existe = ja_existe
        self.falha_consulta = falha_consulta
        self.registros = []

    def existe(self, participante):
        if self.falha_consulta:
            raise ConnectionError("repositório indisponível")
        return self.ja_existe

    def registrar(self, participante):
        self.registros.append(participante)

Dica

Uma lista nova por teste

registros é criada em __init__, portanto cada instância começa com sua própria lista. Isso evita que chamadas de um teste apareçam em outro.

Verificar sucesso, duplicidade e falha

Teste pela operação pública

Crie tests/test_interacoes.py. Os três testes chamam inscrever(), e não os métodos do substituto diretamente. Depois, execute python -m pytest na pasta do projeto.

tests/test_interacoes.py

As verificações cobrem o argumento enviado no sucesso e a ausência de gravação nos dois caminhos interrompidos.

python
import pytest

from inscricoes import RepositorioRegistrador, ServicoInscricao


def test_inscricao_nova_registra_o_participante_correto():
    repositorio = RepositorioRegistrador(ja_existe=False)
    servico = ServicoInscricao(repositorio)

    resultado = servico.inscrever("Ana")

    assert resultado is True
    assert repositorio.registros == ["Ana"]


def test_inscricao_duplicada_nao_solicita_novo_registro():
    repositorio = RepositorioRegistrador(ja_existe=True)
    servico = ServicoInscricao(repositorio)

    resultado = servico.inscrever("Ana")

    assert resultado is False
    assert repositorio.registros == []


def test_falha_na_consulta_nao_solicita_registro():
    repositorio = RepositorioRegistrador(falha_consulta=True)
    servico = ServicoInscricao(repositorio)

    with pytest.raises(ConnectionError):
        servico.inscrever("Ana")

    assert repositorio.registros == []

Escolher interações relevantes

Contrato ou detalhe interno?

Qual conjunto de verificações confirma as interações essenciais sem acoplar o teste a detalhes internos irrelevantes?

Passo 6 de 7

Reconhecer o que o isolamento garante

Avalie o determinismo, o acoplamento e os limites de confiança de testes que usam substitutos manuais.

Um cenário realmente controlado

Determinismo exige controle explícito

Um teste isolado deve produzir o mesmo resultado sempre que receber a mesma preparação. Para isso, respostas, falhas e estado inicial do substituto precisam estar explícitos. Também não pode haver acesso externo oculto nem uma coleção compartilhada entre testes.

Criar um substituto novo em cada teste ajuda a impedir que uma execução influencie a seguinte.

O que fica dentro da fronteira

O serviço pode receber o substituto mais simples que torne o comportamento relevante observável.

Serviço de inscrição ligado a três tipos de substituto controlado, enquanto banco de dados e rede reais permanecem fora da fronteira do teste.

Resposta fixa, estado em memória e registro de chamadas fornecem evidências diferentes, mas nenhum deles exercita a infraestrutura real.

Escolha simples, verificação estável

Exemplo

Escolha pelo comportamento observado

  • Para decidir se uma inscrição já existe: um stub com resposta fixa pode bastar.
  • Para fazer uma gravação influenciar consultas posteriores: use um fake com estado em memória.
  • Para comprovar que o participante correto foi enviado ao repositório: registre a chamada e seus argumentos.

Adicionar estado ou registros que o cenário não usa aumenta a complexidade sem ampliar a evidência.

Atenção

Não transforme a implementação no resultado esperado

Um teste fica frágil quando exige, por exemplo, exatamente uma consulta ao repositório, embora o contrato só determine que duplicidades sejam rejeitadas. Uma otimização poderia alterar a quantidade de chamadas sem mudar o comportamento público.

Verifique interações apenas quando elas forem efeitos exigidos pelo contrato, como não registrar uma inscrição duplicada.

O substituto também tem limites

Mesmo nome de método não basta

A compatibilidade com o repositório real inclui os argumentos aceitos, os resultados devolvidos, as exceções produzidas e os efeitos esperados. Um substituto pode permitir que todos os testes passem mesmo representando esse contrato de forma incorreta.

Além disso, testes isolados não comprovam que dados serão realmente persistidos, que a comunicação externa funcionará ou que o colaborador real se comportará como o substituto. Testes de integração complementam essa confiança.

Dica

Leia corretamente a evidência

Um teste isolado bem-sucedido sustenta uma conclusão limitada: dadas as respostas configuradas para o colaborador, a regra do serviço apresentou o comportamento esperado.

Analise a confiança do teste

Encontre o risco

Considere este fake usado em um teste do serviço:

class RepositorioEmMemoria:
inscricoes = set()

def existe(self, email):
return email in self.inscricoes

def registrar(self, email):
self.inscricoes.add(email)

O teste passa ao inscrever uma pessoa nova. Aponte uma fonte de variação ou fragilidade nesse fake e explique qual risco de compatibilidade com o repositório real ainda permanece mesmo que o teste passe.

Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7

Consolidar uma pequena suíte isolada

Reúna o serviço e um substituto manual configurável em três testes reproduzíveis: sucesso, duplicidade e falha externa.

Prepare o código sob teste

Uma fronteira, três cenários

Crie uma pasta para a prática e, dentro dela, o arquivo inscricoes.py. O serviço contém as regras que queremos verificar: rejeitar duplicidade, solicitar o registro de uma inscrição nova e propagar uma falha de conexão. O repositório chega pelo construtor, por isso cada teste poderá fornecer um objeto controlado.

inscricoes.py

Salve este conteúdo no arquivo inscricoes.py:

python
class InscricaoDuplicada(Exception):
    pass


class ServicoInscricao:
    def __init__(self, repositorio):
        self.repositorio = repositorio

    def inscrever(self, email):
        if self.repositorio.existe(email):
            raise InscricaoDuplicada(email)

        self.repositorio.registrar(email)
        return True

Monte a suíte isolada

Um substituto com apenas o necessário

Crie a pasta tests e nela o arquivo test_inscricoes.py. O RepositorioControlado fornece uma resposta ou falha predeterminada e registra os argumentos enviados a registrar. Ele não reproduz um banco de dados nem a regra de duplicidade do serviço.

tests/test_inscricoes.py

Os três testes criam objetos novos e seguem preparação, execução e verificação:

python
import pytest

from inscricoes import InscricaoDuplicada, ServicoInscricao


class RepositorioControlado:
    def __init__(self, existe=False, erro_consulta=None):
        self.existe_resultado = existe
        self.erro_consulta = erro_consulta
        self.registros = []

    def existe(self, email):
        if self.erro_consulta is not None:
            raise self.erro_consulta
        return self.existe_resultado

    def registrar(self, email):
        self.registros.append(email)


def test_confirma_inscricao_nova_e_solicita_registro():
    repositorio = RepositorioControlado(existe=False)
    servico = ServicoInscricao(repositorio)

    resultado = servico.inscrever("ana@example.com")

    assert resultado is True
    assert repositorio.registros == ["ana@example.com"]


def test_rejeita_duplicidade_sem_solicitar_novo_registro():
    repositorio = RepositorioControlado(existe=True)
    servico = ServicoInscricao(repositorio)

    with pytest.raises(InscricaoDuplicada):
        servico.inscrever("ana@example.com")

    assert repositorio.registros == []


def test_propaga_falha_de_consulta_sem_solicitar_registro():
    repositorio = RepositorioControlado(
        erro_consulta=ConnectionError("repositório indisponível")
    )
    servico = ServicoInscricao(repositorio)

    with pytest.raises(ConnectionError):
        servico.inscrever("ana@example.com")

    assert repositorio.registros == []

Dica

Escolha orientada pelo cenário

A resposta fixa permite representar inscrição nova ou duplicada; a exceção configurada representa indisponibilidade; e a lista permite observar o pedido de registro. Esses papéis podem coexistir em uma classe simples quando isso deixa o teste claro.

Execute e justifique as verificações

Rode no seu computador

Abra o terminal na pasta que contém inscricoes.py e a pasta tests. Execute a suíte com o mesmo ambiente em que o pytest está instalado. Se algum teste falhar, leia a diferença apresentada antes de alterar o código.

Comando de execução

Execute a suíte a partir da raiz da prática:

bash
python -m pytest -q

Relatório da prática

Depois de executar, relate: quais cenários passaram; por que a resposta, a falha e o registro de chamadas foram suficientes; e uma garantia que essa suíte isolada não oferece.

Escreva pelo menos 100 caracteres (0/100).

Revisão final

Resumo

O que esta suíte demonstra

A confiança obtida está delimitada pela fronteira escolhida.

  • Cada teste cria um serviço e um substituto novos, evitando estado compartilhado entre cenários.
  • O cenário de sucesso verifica o retorno e o pedido de registro do participante correto.
  • Duplicidade e falha de consulta não produzem tentativa de registro.
  • Respostas e falhas explícitas tornam a execução independente do estado de recursos externos.
  • Os testes verificam regras do serviço, mas não comprovam persistência, comunicação ou compatibilidade completa com o repositório real; essa confiança exige integração complementar.

Tutorial concluído

Parabéns! Você concluiu: Isolar dependências com substitutos de teste

Você concluiu o tutorial e já pode escolher substitutos simples para construir testes unitários reproduzíveis, reconhecendo quando a integração ainda precisa ser verificada.

50 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso