
Passo 1 de 8
Definir o que a interface decorada deve garantir
Use o contrato público da função decorada para decidir o que testar: o que ela preserva da função original e o que o decorador acrescenta.
Trilha de aprendizado · Nível 9 · Tutorial 8
Ao concluir, você será capaz de verificar tanto o comportamento acrescentado por um decorador quanto as características da função original que devem continuar válidas.
Definir o que a interface decorada deve garantir
Use o contrato público da função decorada para decidir o que testar: o que ela preserva da função original e o que o decorador acrescenta. 2 min
Observar chamadas com funções auxiliares
Crie uma função auxiliar observável para reunir evidências de chamadas sem alterar nem repetir a ação testada. 2 min
Verificar argumentos e quantidade de execuções
Use o registro de chamadas para testar o que realmente atravessa o wrapper: argumentos posicionais, nomeados e o número de execuções da função original. 2 min
Testar retornos e propagação de exceções
Verifique que a função decorada devolve todos os resultados originais e permite que falhas previstas cheguem a quem a chamou. 3 min
Verificar a ordem dos eventos
Teste a sequência completa produzida por wrappers empilhados e pela função original. 3 min
Conferir metadados sem contornar o teste
Verifique os metadados preservados por um decorador e mantenha o teste centrado na interface pública. 2 min
Detectar vazamento de estado e configuração
Use sequências públicas de eventos para confirmar que rótulos permanecem estáveis e que cada função decorada mantém seu próprio contador de tentativas. 3 min
Consolidar uma minissuíte contra regressões
Reúna verificações essenciais em uma suíte curta, execute-a localmente e use um defeito controlado para confirmar que ela protege o contrato público da função decorada. 4 min

Passo 1 de 8
Use o contrato público da função decorada para decidir o que testar: o que ela preserva da função original e o que o decorador acrescenta.
Uma função decorada tem uma interface pública: é por ela que o código cliente faz chamadas. Os testes devem descrever o que essa chamada precisa garantir, sem depender de como o wrapper foi organizado internamente.
No exemplo-guia, uma função decorada recebe argumentos, executa a função original e acrescenta eventos em memória. O contrato reúne tanto o comportamento preservado quanto o comportamento novo.
A chamada pública atravessa o wrapper, que acrescenta eventos, e então alcança a função original. O resultado ou a exceção retorna pela mesma interface pública.

O teste observa entradas e saídas da interface decorada; ele não precisa conhecer variáveis internas do wrapper.
Exemplo
Considere uma função decorada com o rótulo configurável "pagamento".
Para cada chamada pública válida, ela deve:
Além disso, o decorador deve registrar eventos em uma lista compartilhada para observação:
Metadados esperados, como nome e documentação, também pertencem ao contrato, mas serão verificados adiante.
Dica
Um teste que verifica apenas o retorno pode deixar passar uma chamada duplicada: duas execuções podem produzir o mesmo valor. Por isso, o contrato separa retorno, encaminhamento de argumentos e quantidade de execuções.
Associe cada expectativa à categoria correta no contrato da função decorada.
Toque em um item e depois no par correspondente.

Passo 2 de 8
Crie uma função auxiliar observável para reunir evidências de chamadas sem alterar nem repetir a ação testada.
Para testar uma função decorada, defina no próprio teste uma função simples e decore-a normalmente. Essa auxiliar deve registrar o que recebeu e o que produziu em listas mantidas pelo teste.
Use registros com marcadores diferentes: por exemplo, uma tupla iniciada por "chamada" para as entradas e outra iniciada por "resultado" para a saída. Assim, os dados da função original não se confundem com eventos acrescentados pelo decorador.
A chamada pública passa pelo decorador e chega à auxiliar. A auxiliar registra uma vez os argumentos e uma vez o resultado conhecido.

A lista de registros é a evidência do teste; ela evita precisar executar a função de novo.
Dica
O tamanho dos registros de chamada informa quantas vezes a função original foi executada. Fazer uma chamada extra apenas para inspecionar o comportamento alteraria a evidência que você quer verificar.
No seu computador, crie um arquivo chamado test_observacao.py. O exemplo abaixo inclui um decorador simples, uma lista compartilhada intencionalmente para eventos e uma fábrica que cria uma auxiliar nova com seus próprios registros.
Copie o arquivo completo e execute-o com pytest.
from functools import wraps
def registrar_eventos(eventos, rotulo):
def decorar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
eventos.append(("entrada", rotulo))
resultado = funcao(*args, **kwargs)
eventos.append(("saida", rotulo))
return resultado
return wrapper
return decorar
def criar_auxiliar(registros):
def calcular(valor, aumento=1):
resultado = valor + aumento
registros.append(("chamada", valor, aumento))
registros.append(("resultado", resultado))
return resultado
return calcular
def test_registra_chamada_e_resultado_sem_repetir_execucao():
eventos = []
registros = []
calcular = criar_auxiliar(registros)
calcular_observado = registrar_eventos(eventos, "calculo")(calcular)
retorno = calcular_observado(10, aumento=5)
assert retorno == 15
assert registros == [
("chamada", 10, 5),
("resultado", 15),
]
assert len([item for item in registros if item[0] == "chamada"]) == 1
assert eventos == [("entrada", "calculo"), ("saida", "calculo")]Exemplo
Depois de uma única chamada, registros contém duas informações diferentes:
("chamada", 10, 5) mostra os valores recebidos pela auxiliar.("resultado", 15) mostra o valor que ela produziu.O filtro item[0] == "chamada" encontra somente as entradas de chamada. Seu tamanho é 1, portanto a auxiliar executou uma vez — sem uma segunda chamada de inspeção.
No arquivo local, confirme que a auxiliar registra a chamada e o resultado. Execute python -m pytest test_observacao.py. Em seguida, explique qual registro permite concluir a quantidade de execuções da auxiliar sem fazer uma chamada extra.
Escreva pelo menos 80 caracteres (0/80).

Passo 3 de 8
Use o registro de chamadas para testar o que realmente atravessa o wrapper: argumentos posicionais, nomeados e o número de execuções da função original.
Um teste da função decorada deve observar mais do que o valor devolvido. Um wrapper pode encaminhar argumentos errados ou chamar a função original duas vezes e, ainda assim, devolver um resultado aparentemente correto.
Registre na função original os pares (args, kwargs). Assim, o teste verifica exatamente os argumentos recebidos e conta as execuções sem chamar a função novamente.

Compare o registro feito pela função original com os argumentos usados na chamada pública.
Dica
Se a chamada pública for calcular(10), o wrapper correto encaminha args == (10,) e kwargs == {}. O valor padrão de quantidade=1 é aplicado quando a função original recebe a chamada; ele não deve aparecer artificialmente no registro do wrapper.
Crie este arquivo e execute python -m pytest test_encaminhamento.py. Cada teste cria uma lista nova para que seus registros não se misturem.
from functools import wraps
def registrar_chamadas(chamadas):
def decorador(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
return funcao(*args, **kwargs)
return wrapper
return decorador
def criar_calcular(chamadas):
@registrar_chamadas(chamadas)
def calcular(preco, quantidade=1, *, desconto=0):
chamadas.append(("chamada", (preco, quantidade), {"desconto": desconto}))
return preco * quantidade - desconto
return calcular
def test_encaminha_argumento_posicional_sem_inserir_padrao():
chamadas = []
calcular = criar_calcular(chamadas)
resultado = calcular(10)
assert resultado == 10
# A função original registra os valores após aplicar quantidade=1.
assert chamadas == [("chamada", (10, 1), {"desconto": 0})]
def test_encaminha_argumentos_nomeados():
chamadas = []
calcular = criar_calcular(chamadas)
resultado = calcular(preco=8, quantidade=3, desconto=2)
assert resultado == 22
assert chamadas == [("chamada", (8, 3), {"desconto": 2})]
def test_encaminha_chamada_mista():
chamadas = []
calcular = criar_calcular(chamadas)
resultado = calcular(5, quantidade=4, desconto=1)
assert resultado == 19
assert chamadas == [("chamada", (5, 4), {"desconto": 1})]Em uma função auxiliar que registra diretamente (args, kwargs), complete a asserção para a chamada calcular(10):
assert registro == [((10,), ____)]
Para o contrato deste exemplo, cada chamada pública à função decorada deve produzir exatamente um registro de chamada da função original. Faça essa asserção separadamente do teste de retorno.
Se o wrapper executar a função duas vezes, o resultado pode continuar sendo 19, mas a lista terá dois registros — e o contrato foi violado.
Um wrapper com defeito chama a função original duas vezes, mas devolve o resultado da primeira execução. Qual verificação detecta esse defeito para uma única chamada pública?

Passo 4 de 8
Verifique que a função decorada devolve todos os resultados originais e permite que falhas previstas cheguem a quem a chamou.
O teste deve chamar a função decorada, não a função original guardada dentro do decorador. Assim, ele verifica se o wrapper preserva o resultado produzido pela função.
Teste valores que tornem defeitos visíveis: um valor comum, None e um valor falso como 0. Um wrapper que executa a função mas esquece return sempre devolve None — e, por isso, um teste somente com None poderia passar por engano.
Nos dois caminhos, a entrada é registrada. A saída só é registrada quando a função original termina normalmente.

Sucesso: entrada → execução → saída → retorno. Falha: entrada → execução → exceção.
Use listas novas para observar as execuções. Além do resultado público, registre a chamada da função original: isso evita concluir que um retorno None prova, sozinho, que ela foi executada.
Este exemplo pressupõe que registrar_eventos recebe um rótulo e uma lista de eventos, como no exemplo-guia do tutorial.
from app import registrar_eventos
def test_devolve_um_valor_calculado():
eventos = []
chamadas = []
@registrar_eventos("calculo", eventos)
def triplicar(numero):
chamadas.append(numero)
return numero * 3
resultado = triplicar(7)
assert resultado == 21
assert chamadas == [7]
assert eventos == [
("entrada", "calculo", 1),
("saida", "calculo", 1),
]
def test_preserva_none_e_ainda_executa_a_original():
eventos = []
chamadas = []
@registrar_eventos("aviso", eventos)
def avisar(mensagem):
chamadas.append(mensagem)
# O retorno implícito é None.
resultado = avisar("pronto")
assert resultado is None
assert chamadas == ["pronto"]
assert eventos == [
("entrada", "aviso", 1),
("saida", "aviso", 1),
]
def test_preserva_zero_sem_substituir_o_resultado():
eventos = []
@registrar_eventos("estoque", eventos)
def quantidade_disponivel():
return 0
assert quantidade_disponivel() == 0
assert eventos == [
("entrada", "estoque", 1),
("saida", "estoque", 1),
]Quando a função original lança uma exceção prevista, envolva a chamada decorada com pytest.raises. Verifique o tipo e uma mensagem significativa. Depois, confira os eventos: houve entrada, mas não houve saída, pois a execução não terminou com sucesso.
Acrescente este teste ao mesmo arquivo. Ele verifica a interface decorada e não aceita que o wrapper transforme a falha em um retorno comum.
import pytest
from app import registrar_eventos
def test_propaga_erro_e_nao_registra_saida():
eventos = []
@registrar_eventos("divisao", eventos)
def dividir(dividendo, divisor):
if divisor == 0:
raise ValueError("divisor não pode ser zero")
return dividendo / divisor
with pytest.raises(ValueError, match="divisor não pode ser zero"):
dividir(10, 0)
assert eventos == [("entrada", "divisao", 1)]Para garantir que um wrapper não troque um resultado falso por outro valor, complete:
assert quantidade_disponivel() == ____
Por que um teste que verifica somente resultado is None não detecta necessariamente um wrapper que esqueceu de devolver o resultado da função original?
Escreva pelo menos 80 caracteres (0/80).
Resumo
None com evidência de execução e um valor falso como 0.pytest.raises na chamada decorada para confirmar tipo e mensagem da exceção.
Passo 5 de 8
Teste a sequência completa produzida por wrappers empilhados e pela função original.
Uma lista de eventos permite observar a ordem relativa entre o wrapper e a função original. Para dois decoradores empilhados, a chamada pública entra primeiro na camada externa, depois na interna, executa a função original e sai na ordem inversa.
Verificar apenas que todos os eventos estão presentes não é suficiente: uma saída registrada antes da função original ainda é um defeito, mesmo que o retorno final esteja correto.
As cores distinguem as camadas: azul = wrapper externo, laranja = wrapper interno e verde = função original.

A sequência esperada é: entrada externa → entrada interna → execução original → saída interna → saída externa.
Dica
Uma função pode devolver o resultado esperado mesmo com eventos fora de ordem. Faça uma asserção para o retorno e outra para a lista completa de eventos.
Cada rótulo identifica a camada que produziu o evento.
from functools import wraps
def registrar(eventos, rotulo):
def decorar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
eventos.append(f"{rotulo}:entrada")
resultado = funcao(*args, **kwargs)
eventos.append(f"{rotulo}:saida")
return resultado
return wrapper
return decorar
def test_eventos_decoradores_empilhados():
eventos = []
@registrar(eventos, "externo")
@registrar(eventos, "interno")
def dobrar(valor):
eventos.append("original:execucao")
return valor * 2
resultado = dobrar(5)
assert resultado == 10
assert eventos == [
"externo:entrada",
"interno:entrada",
"original:execucao",
"interno:saida",
"externo:saida",
]A igualdade entre listas verifica conteúdo e posição. Portanto, esse teste falha se um evento faltar, aparecer a mais, tiver outro rótulo ou ocorrer em uma posição incorreta.
Crie a lista eventos dentro de cada teste: ela é compartilhada intencionalmente pelas camadas daquele cenário para observação, mas não deve transportar registros de outro teste.
Uma função foi decorada com uma camada externa e uma interna. Coloque os eventos na ordem produzida por uma chamada bem-sucedida.
Complete o evento que deve ocupar o terceiro lugar:
assert eventos == ["externo:entrada", "interno:entrada", ___, "interno:saida", "externo:saida"]

Passo 6 de 8
Verifique os metadados preservados por um decorador e mantenha o teste centrado na interface pública.
Além de testar chamadas, retornos e eventos pela função decorada, confira os metadados que o contrato promete preservar. Com functools.wraps, é esperado que __name__ e __doc__ da interface decorada correspondam aos da função original.
Essas verificações são complementares: metadados corretos não provam que o wrapper encaminha argumentos, devolve o resultado ou registra eventos corretamente.
A interface pública é a função decorada. O atributo __wrapped__ revela a camada imediatamente envolvida, mas chamá-la pula uma camada do comportamento.

Em decoradores empilhados, cada __wrapped__ aponta somente para a próxima camada interna.
Ao aplicar um decorador explicitamente, mantenha a referência da função antes da transformação. Assim, você pode verificar por identidade que __wrapped__ aponta para a função que aquele wrapper envolveu.
Arquivo de teste com pytest
from functools import wraps
def registrar(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def calcular_total(preco, quantidade=1):
"""Calcula o total de itens."""
return preco * quantidade
original = calcular_total
decorada = registrar(original)
def test_preserva_metadados_e_referencia_imediata():
assert decorada.__name__ == "calcular_total"
assert decorada.__doc__ == "Calcula o total de itens."
assert decorada.__wrapped__ is originalEm um empilhamento, o __wrapped__ da camada externa pode ser outro wrapper. Chamar apenas externa.__wrapped__() deixa de executar a camada externa; portanto, não demonstra que ela registra seus eventos, encaminha argumentos ou preserva retornos e exceções.
Use __wrapped__ para testar a ligação entre camadas. Use a função decorada para testar o comportamento público.
Se externa.__wrapped__() retorna o resultado esperado, então o decorador externo está validado.
Complete as duas lacunas:
original = calcular_total
decorada = registrar(original)
assert decorada._____ == "calcular_total"
assert decorada.__wrapped__ ___ originalDica
Verifique __name__, __doc__ e a ligação por __wrapped__. Depois, mantenha testes separados para a chamada pública: argumentos, retorno, exceções e eventos.

Passo 7 de 8
Use sequências públicas de eventos para confirmar que rótulos permanecem estáveis e que cada função decorada mantém seu próprio contador de tentativas.
Uma lista de eventos pode ser compartilhada intencionalmente: ela reúne evidências para o teste. Já o contador de tentativas precisa pertencer a cada função decorada. Assim, chamadas intercaladas de duas funções não podem fazer o número de uma avançar a outra.
Não inspecione células da closure nem variáveis internas. Faça chamadas pela interface decorada e compare a sequência completa de eventos.
O mesmo registro permite observar as duas funções; os contadores independentes preservam o estado de cada uma.

Compartilhar a observação é diferente de compartilhar o estado que define as tentativas.
Dica
Crie uma nova lista de eventos e novas funções decoradas em cada teste. Isso evita que uma execução anterior esconda ou invente um vazamento de estado.
Crie um arquivo local chamado test_estado_decorador.py com este conteúdo e execute python -m pytest -q.
import pytest
from functools import wraps
def registrar(eventos, rotulo):
def decorar(funcao):
tentativa = 0
@wraps(funcao)
def wrapper(*args, **kwargs):
nonlocal tentativa
tentativa += 1
eventos.append((rotulo, "entrada", tentativa))
resultado = funcao(*args, **kwargs)
eventos.append((rotulo, "saida", tentativa))
return resultado
return wrapper
return decorar
def test_falha_tambem_consume_uma_tentativa():
eventos = []
@registrar(eventos, "pagamento")
def cobrar(valor):
if valor < 0:
raise ValueError("valor inválido")
return valor
assert cobrar(10) == 10
with pytest.raises(ValueError, match="valor inválido"):
cobrar(-1)
assert cobrar(5) == 5
assert eventos == [
("pagamento", "entrada", 1),
("pagamento", "saida", 1),
("pagamento", "entrada", 2),
("pagamento", "entrada", 3),
("pagamento", "saida", 3),
]
def test_funcoes_com_rotulos_distintos_nao_misturam_contadores():
eventos = []
@registrar(eventos, "email")
def enviar(destinatario):
return destinatario.upper()
@registrar(eventos, "sms")
def avisar(destinatario):
return destinatario.lower()
assert enviar("Ana") == "ANA"
assert avisar("Bia") == "bia"
assert enviar("Caio") == "CAIO"
assert eventos == [
("email", "entrada", 1),
("email", "saida", 1),
("sms", "entrada", 1),
("sms", "saida", 1),
("email", "entrada", 2),
("email", "saida", 2),
]
def test_mesmo_decorador_configurado_da_contadores_por_funcao():
eventos = []
auditar = registrar(eventos, "auditoria")
@auditar
def criar():
return "criada"
@auditar
def remover():
return "removida"
criar()
remover()
criar()
entradas = [evento for evento in eventos if evento[1] == "entrada"]
assert entradas == [
("auditoria", "entrada", 1),
("auditoria", "entrada", 1),
("auditoria", "entrada", 2),
]
No primeiro teste, a falha produz somente o evento de entrada da tentativa 2, mas a chamada seguinte recebe tentativa 3. Nos outros dois, a ordem intercalada revela que cada função começa em 1 e progride sem afetar a outra — inclusive quando ambas reutilizam o mesmo decorador já configurado.
Depois de enviar("Ana"), a chamada avisar("Bia") deve registrar ("sms", "entrada", ___).
Suponha que, no segundo teste, a entrada de avisar("Bia") seja ("sms", "entrada", 2). Que defeito isso revela? Por que a lista única de eventos não é, por si só, um defeito?
Escreva pelo menos 80 caracteres (0/80).

Passo 8 de 8
Reúna verificações essenciais em uma suíte curta, execute-a localmente e use um defeito controlado para confirmar que ela protege o contrato público da função decorada.
Uma suíte contra regressões não precisa repetir a mesma chamada muitas vezes. Cada teste deve proteger uma expectativa observável do contrato:
__name__, __doc__ e __wrapped__ mantêm a ligação esperada;Essas verificações são complementares: acertar o retorno não prova que a chamada ocorreu uma única vez, por exemplo.
Observe como cada teste obtém evidência pela interface pública ou por efeitos públicos registrados.

A chamada decorada é o centro da verificação; os registros em memória tornam os efeitos observáveis.
Dica
Prefira uma asserção que falhe por um motivo claro. Se duas asserções protegem exatamente a mesma expectativa, mantenha a que comunica melhor o contrato.
Em uma pasta vazia, crie os arquivos decoradores.py e test_decoradores.py com os conteúdos abaixo. Se ainda não tiver pytest no ambiente, instale-o com python -m pip install pytest.
Implementação correta do decorador parametrizado.
from functools import wraps
def registrar(rotulo, eventos):
def decorar(funcao):
tentativa = 0
@wraps(funcao)
def wrapper(*args, **kwargs):
nonlocal tentativa
tentativa += 1
eventos.append(("entrada", rotulo, tentativa))
resultado = funcao(*args, **kwargs)
eventos.append(("saida", rotulo, tentativa))
return resultado
return wrapper
return decorar
A minissuíte abaixo reúne uma evidência para cada dimensão importante do contrato.
import pytest
from decoradores import registrar
def test_encaminha_argumentos_e_executa_uma_vez():
eventos = []
chamadas = []
@registrar("soma", eventos)
def somar(a, b=0, *, ajuste=0):
chamadas.append((a, b, ajuste))
return a + b + ajuste
assert somar(2, b=3, ajuste=4) == 9
assert chamadas == [(2, 3, 4)]
assert eventos == [("entrada", "soma", 1), ("saida", "soma", 1)]
def test_preserva_retorno_falso_e_propagacao_de_excecao():
eventos = []
@registrar("busca", eventos)
def buscar(ativo):
if not ativo:
return 0
raise ValueError("cadastro indisponivel")
assert buscar(False) == 0
with pytest.raises(ValueError, match="cadastro indisponivel"):
buscar(True)
assert eventos == [
("entrada", "busca", 1),
("saida", "busca", 1),
("entrada", "busca", 2),
]
def test_mantem_metadados_e_referencia_a_original():
eventos = []
def formatar(nome):
"""Devolve um nome em letras maiusculas."""
return nome.upper()
original = formatar
decorada = registrar("formato", eventos)(formatar)
assert decorada.__name__ == "formatar"
assert decorada.__doc__ == "Devolve um nome em letras maiusculas."
assert decorada.__wrapped__ is original
assert decorada("ana") == "ANA"
def test_isola_contadores_e_rotulos_entre_funcoes():
eventos = []
auditoria = registrar("auditoria", eventos)
@auditoria
def abrir():
return "abriu"
@registrar("pagamento", eventos)
def cobrar():
return "cobrou"
assert abrir() == "abriu"
assert cobrar() == "cobrou"
assert abrir() == "abriu"
assert eventos == [
("entrada", "auditoria", 1), ("saida", "auditoria", 1),
("entrada", "pagamento", 1), ("saida", "pagamento", 1),
("entrada", "auditoria", 2), ("saida", "auditoria", 2),
]
No terminal aberto nessa pasta, execute:
python -m pytest -q
Com a implementação correta, espere 4 passed. Agora altere somente a última linha do wrapper em decoradores.py, trocando return resultado por return None. Salve e rode o mesmo comando novamente.
Esta é a única alteração necessária para simular uma regressão de retorno.
# Dentro de wrapper, mantenha o restante igual:
resultado = funcao(*args, **kwargs)
eventos.append(("saida", rotulo, tentativa))
return None # defeito controlado: descarta o resultado original
Atenção
A expectativa é que test_encaminha_argumentos_e_executa_uma_vez falhe porque somar(...) passa a devolver None, não 9. A suíte ainda pode mostrar outras falhas dependentes de retorno. Isso não é ruído: cada falha aponta uma promessa pública quebrada.
Depois da observação, restaure return resultado e execute novamente para confirmar que a correção devolveu a suíte ao estado verde.
Resumo
Uma suíte aprovada aumenta a confiança no contrato coberto; ela não prova a ausência de todo defeito possível.
Após executar a variante defeituosa, relate: qual teste detectou o problema, qual parte do contrato foi violada e por que chamar somente __wrapped__ não substituiria esse teste.
Escreva pelo menos 120 caracteres (0/120).
Parabéns! Você concluiu: Testar o comportamento de funções decoradas
Você concluiu este nível!
Agora você vai iniciar: Iteradores e processamento sob demanda
Consumir iteradores com iter e nextDistinguir uma fonte iterável de um iterador e controlar seu consumo, reconhecendo avanço, esgotamento e possibilidade de iniciar uma nova passagem.Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks