Qualidade e confiabilidade do código
App: Python Course · Data: 1 de setembro de 2026
Escopo: o interpretador Python integrado, o conteúdo autoral do curso (mais de 2194 exercícios, 48 lições, 17 módulos, uma nova seção «Lifehacks» — 15 cartões, 6 idiomas), o algoritmo de repetição espaçada e — novo neste relatório — se a própria suíte de testes realmente detecta regressões (testes de mutação) nos 5 módulos de lógica mais críticos
Em resumo: duas perguntas independentes, duas respostas. O código está correto? 9710 candidatos verificados contra o CPython e outros oráculos — 0 discrepâncias. Os testes realmente protegem isso? 3694 defeitos injetados deliberadamente (testes de mutação — a cobertura cresceu junto com o código) — todos os 3694 detectados. A segunda pergunta é nova neste relatório — é ela que torna a primeira confiável, e não apenas afirmada.
Testes de mutação — se os testes realmente detectam bugs
Uma porcentagem de cobertura só responde "esta linha foi executada pelo menos uma vez?", não "um teste perceberia se esta linha quebrasse?". Os testes de mutação respondem diretamente à segunda pergunta: uma ferramenta injeta deliberadamente milhares de pequenos defeitos individuais no código (troca < por <=, && por ||, remove uma verificação de limite, etc.) e verifica se pelo menos um teste falha em seguida. Se nenhum teste detecta o defeito injetado, isso é uma lacuna real — mesmo que a cobertura de linhas por si só mostre 100%.
| Módulo | Mutantes | Detectados | Score |
|---|---|---|---|
Lógica de desbloqueio de lições (gate-engine) |
208 | 208 | 100.00% |
Conquistas, XP, sequência, corações (gamification-engine) |
267 | 267 | 100.00% |
Planejador de repetição espaçada (srs-engine) |
47 | 47 | 100.00% |
| Interpretador Python (tokenizador, parser, avaliador — incluindo o código de import/arquivos do módulo 17) | 3123 | 3123 | 100.00% |
Seção «Lifehacks» (lifehacks-engine + manage-lifehacks) |
49 | 49 | 100.00% |
| Total, 5 módulos mais críticos | 3694 | 3694 | 100.00% |
"Detectado" aqui significa: o mutante fez pelo menos um teste falhar, ou causou um loop infinito/travamento (também contabilizado — o defeito foi igualmente sinalizado com clareza, só que por outra via; a linha do interpretador Python acima contabiliza exatamente um caso assim). Cada caso individual em que um mutante inicialmente sobrevivia foi revisado manualmente: ou foi escrito um novo teste que documenta o comportamento real, ou foi provado matematicamente que aquela mudança específica de código nunca pode afetar o resultado (e nesse caso, a própria linha traz um comentário explicando isso, não um silêncio), ou — quando essa se mostrou a resposta mais certeira — o próprio código foi simplificado de modo que o ramo de lógica não suportado simplesmente deixou de existir. Um desses casos é detalhado logo abaixo.
Metodologia — correção do código (comparação com CPython)
Três frentes de validação independentes, sem código compartilhado e sem dependência entre os resultados. Interpretador: o app executa código por meio de seu próprio interpretador Python, escrito do zero (não é CPython, nem uma biblioteca de terceiros) — cada candidato (uma solução de referência autoral, uma instância gerada a partir de um modelo, ou um programa gerado aleatoriamente) foi executado simultaneamente no interpretador do app e em um CPython 3.12.3 instalado no sistema, com comparação exata do resultado ou da consistência dos erros. Estrutura do curso: uma varredura completa, sem amostragem, de todos os arquivos de conteúdo. Planejador de repetição espaçada: sua saída foi comparada com uma implementação escrita separadamente (não copiada do próprio planejador) da mesma fórmula matemática. A validação foi executada por meio de uma ferramenta temporária e isolada, fora do código próprio do app, em várias rodadas independentes, sem se apoiar nos resultados umas das outras.
Resultados frente a um oráculo
| Frente de validação | Verificados | Corresponderam | Discrepâncias |
|---|---|---|---|
Interpretador — conteúdo do curso e modo de prática estendido (soluções de referência e alternativas, exercícios de completar em modo valor, modelos estáticos e parametrizados do modo de prática estendido, execução real de import/open() do módulo 17) e a seção «Lifehacks» (snapshots de código antes/depois) |
393 | 393 | 0 |
| Interpretador — testes aleatórios (fuzzing) (20 categorias de construções) | 400 | 400 | 0 |
| Planejador de repetição espaçada (1020 cenários, 8917 etapas de revisão) | 8917 | 8917 | 0 |
| Total combinado | 9710 | 9710 | 0 |
Verificações estruturais e de conteúdo
| Verificação | Cobertura | Problemas encontrados |
|---|---|---|
| Estrutura do curso (validade das soluções, integridade do grafo de pré-requisitos, unicidade de id) | 2194 arquivos de exercícios, 48 lições, 17 módulos | 0 |
| Estrutura do pool do modo de prática estendido | 64 entradas (53 estáticas + 11 modelos) | 0 |
| Seção «Lifehacks» (cada cartão referencia um conceito real do curso; o código antes/depois produz saída idêntica no interpretador real) | 15 cartões, 30 snapshots de código | 0 |
| Paridade de localização (chaves de conteúdo, sem valores não traduzidos) | 12 214 chaves × EN, ES, FR, DE, PT (61 070 comparações) | 0 |
Ferramentas
strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes.Como esses números foram realmente alcançados — sem enfeites
Esses números não são o ponto de partida. A primeira execução de testes de mutação no módulo maior e mais difícil (o interpretador Python, ~2500 linhas) resultou em 75,65% — ou seja, quase um quarto dos defeitos injetados deliberadamente passava totalmente despercebido. Fechar essa lacuna até 100% levou sete rodadas consecutivas, cada uma fechando parte do restante e revelando novos casos-limite — incluindo erros de metodologia cometidos durante o próprio processo de verificação.
Bugs reais da aplicação encontrados e corrigidos (não apenas lacunas de testes) — revelados justamente por esse processo rigoroso: a pontuação de uma lição era indevidamente rebaixada após um erro corrigido (um único erro limitava permanentemente a pontuação máxima possível, mesmo que o aluno acertasse imediatamente na repetição); o teste de nivelamento concedia indevidamente as conquistas "Sem Erros" e "Sem Piedade" para lições puladas, não realmente concluídas; e três bugs pontuais do próprio interpretador (divisão inteira, arredondamento no operador de resto, comparação de bool com int).
Um erro de metodologia foi detectado e corrigido no meio do processo: uma verificação intermediária (substituir o texto de uma linha inteira em vez de mutar nas coordenadas exatas do Stryker) superestimou quantos mutantes eram realmente "equivalentes" (ou seja, impossíveis de corrigir) — de 35 suposições desse tipo, apenas 1 se confirmou. Corrigido ao passar para uma verificação precisa, em nível de coordenadas, para cada caso individual.
Os últimos 5 mutantes (comparação de strings usada na ordenação de listas) resistiram tanto a uma prova de equivalência quanto a um teste escrito pela interface pública normal — o próprio algoritmo de ordenação do JavaScript faz comparações redundantes que mascaram um resultado errado em um único par, mesmo em uma lista de 5 elementos ordenada de forma inversa. Resolvido testando diretamente a função de comparação interna, contornando por completo o comportamento imprevisível do algoritmo de ordenação de terceiros.
Resumindo: nenhum número neste relatório está pintado de antemão. Cada um é resultado de trabalho iterativo e verificado — incluindo admitir e corrigir os próprios erros pelo caminho. É exatamente por isso que se pode confiar nesses números: o app não só se comporta corretamente nos exemplos verificados — a rede de segurança de testes ao seu redor realmente detecta regressões futuras, em vez de existir apenas formalmente.
Por que "2797 de 2797, 100%" virou brevemente "3373 de 3374, 99.97%" antes de voltar a 100%: depois de alcançar 100% pela primeira vez (acima), o interpretador ganhou novos tópicos do currículo — iteração direta (for x in iterable: sem range()), __str__/__eq__, @staticmethod/@classmethod, dicionários/conjuntos/compreensões/geradores — centenas de linhas novas que nunca tinham passado por testes de mutação. Em vez de deixar esse crescimento sem verificação (e continuar exibindo silenciosamente um "100%" já defasado), ele foi submetido ao Stryker com o mesmo rigor que todo o resto: 32 novos testes de regressão, cada um escrito contra um mutante sobrevivente específico (primeiro lendo o código ao redor, confirmando logicamente por que os testes existentes o deixavam passar, e só então escrevendo o teste) — não é enchimento de cobertura. Tudo o mais que havia mudado desde a rodada anterior de testes de mutação (17 de agosto) também foi verificado: o parser (nova gramática para esses mesmos tópicos novos) e o planejador de repetição espaçada (um novo limiar antes do intercalamento) — ambos já em 100% sem necessidade de trabalho adicional. Isso deixou exatamente um mutante sem resolução, e foi nesse ponto que o relatório dizia brevemente "3373 de 3374, 99.97%", com uma explicação de por que isso é mais honesto do que um "100%" fabricado: uma função de busca de variável local (Scope.tryGet) tinha uma verificação que, em teoria, poderia importar em dois cenários distintos, mas um deles se mostrou matematicamente impossível dado cada ponto de chamada real do app — e o outro cenário, genuinamente alcançável, já estava coberto por um teste dedicado. Marcar os dois mutantes dessa linha como "equivalentes" com um único comentário teria sido o caminho mais rápido para "100%" — e é exatamente por isso que isso não foi feito naquele momento: a ferramenta só consegue desativar a verificação de mutação para a linha inteira, não para um mutante específico, então esse atalho também teria varrido a cobertura do cenário que realmente importa.
O que mudou depois disso — e por que este é um 100% genuíno, não uma solução paliativa: em vez de se contentar com 99.97% ou suprimir silenciosamente o restante, a próxima pergunta foi se aquela linha já era código morto desde o início. Um rastreamento mais detalhado de cada chamada a tryGet() no app (existe exatamente uma) e de todo lugar de onde o valor que ela protegia poderia se originar mostrou que sim: a linha era genuinamente inalcançável em qualquer estado real do programa, não apenas difícil de testar. Ela foi removida por completo. Isso não é um atalho: apagar código verdadeiramente morto não custa nenhuma cobertura real (ao contrário de suprimir o mutante com um comentário, o que também teria descartado a cobertura real, já verificada, do mutante irmão) — o mutante que essa linha gerava simplesmente deixou de existir junto com ela. A suíte de testes completa (1035 testes na época) confirmou que o comportamento não mudou nem um pouco. A execução seguinte do Stryker resultou em um genuíno 100.00%. A lição desse pequeno desvio no próprio relatório: estar disposto a mostrar um número honesto um pouco abaixo de 100% se revelou o caminho mais rápido para um 100% real, não mais lento do que disfarçar a lacuna desde o início.
De onde veio o crescimento de 3371 para 3694 e de 9655 para 9710 (1 de setembro de 2026): o curso cresceu — um novo módulo 17, "Import, módulos e manipulação de arquivos", adicionou execução real (não apenas reconhecimento de sintaxe) de import/open()/with contra um sistema de arquivos virtual simulado, e uma nova seção «Lifehacks» (15 cartões "antes → depois" explicando por que a forma mais curta funciona da mesma maneira). As duas adições passaram pela mesma disciplina do resto do app: interpreter.ts e parser.ts foram submetidos novamente a testes de mutação (+238 e +36 mutantes, respectivamente, todos detectados), o novo lifehacks-engine e manage-lifehacks foram levados do zero a 100% (2 lacunas reais de teste fechadas, não disfarçadas), os 15 cartões (30 snapshots de código) foram comparados com o CPython, e os 2 novos exercícios do módulo 17 baseados em laços de repetição também. Separadamente, no mesmo dia, foi fechado o último débito de tradução: a seção «Lifehacks» e as 434 novas chaves do módulo 17 foram traduzidas para espanhol, francês, alemão e português de uma vez, então os 6 idiomas do curso agora estão em paridade total de conteúdo (12 214 chaves cada) — por isso o número de comparações de localização cresceu de 58 635 para 61 070.
Notas
Durante a validação iterativa, três bugs pontuais do interpretador foram encontrados e corrigidos — nenhum jamais afetou conteúdo real do curso, e os três agora estão protegidos por testes permanentes que garantem que não voltem a ocorrer:
- a divisão inteira era exibida como número inteiro em vez do resultado decimal real do Python (
11em vez de11.0); - um raro artefato de arredondamento de ponto flutuante no operador de resto (
%) com operandos fracionários; boolnão era comparado como numericamente igual aintem==/!=/in/list.remove().
O limite máximo de intervalo do planejador de repetição espaçada acionou corretamente em várias centenas de etapas projetadas para alcançá-lo, sem nenhuma exceção. Os resultados acima são da rodada de validação final e mais recente, sem problemas conhecidos em aberto.