Catálogo Python¶
Os códigos estruturais C*, como implementados pelo scanner falsegreen: uma
passagem de AST sem dependências sobre pytest e unittest. Cada código nomeia o sinal que dispara a regra e,
onde ajuda, o parecido que ele deliberadamente deixa em paz.
Confiança: ALTO bloqueia, BAIXO avisa, OFF é só diagnóstico. Julgamentos são J1-J6; famílias são F1-F8.
Cada código emitido tem sua própria entrada abaixo, agrupada por família. O índice leva direto a cada uma.
Índice¶
| Código | Conf | J | Resumo |
|---|---|---|---|
| C1 | BAIXO | J1 | assert dentro de if/for que pode nunca rodar |
| C2 | ALTO | J1 | nenhuma verificação (corpo vazio) |
| C2b | BAIXO | J1 | chama coisas mas não verifica nada |
| C2c | BAIXO | J1 | bloco self.subTest(...) vazio |
| C3 | ALTO | J1 | assert dentro de um try cujo except engole o erro |
| C4 | ALTO | J1 | não coletado pelo pytest (nunca roda) |
| C4b | BAIXO | J1 | classe de teste tem __init__ (não coletada) |
| C5 | ALTO | J2 | verificação sempre verdadeira (assert True / tupla / or True) |
| C6 | BAIXO | J4 | verificação fraca (só que algo voltou) |
| C6b | BAIXO | J5 | acoplada ao layout posicional de argumentos |
| C6c | BAIXO | J4 | call_count como oráculo de truthiness |
| C7 | ALTO | J2 | compara algo consigo mesmo |
| C8 | BAIXO | J4 | igualdade exata em float |
| C8b | BAIXO | J4 | igualdade aproximada sem tolerância explícita |
| C9 | BAIXO | J4 | pytest.raises amplo demais |
| C11a | BAIXO | J2 | literal autoconfirmante atribuído pelo teste |
| C13 | ALTO | J3 | asserção de mock escrita errada / não chamada |
| C13b | BAIXO | J3 | patch() sem autospec |
| C14 | BAIXO | J2 | golden/snapshot gerado a partir da própria saída |
| C16 | BAIXO | J1 | depende de tempo, aleatoriedade ou um sleep fixo |
| C17 | ALTO | J1 | skip dentro de um except amplo esconde a falha |
| C18 | BAIXO | J2 | compara str()/repr() com um literal |
| C19 | BAIXO | J1 | pytest.raises envolve mais de uma chamada |
| C20 | ALTO | J1 | asserção em código morto após return/raise/fail |
| C21 | BAIXO | J1 | toda asserção é condicional, nenhuma roda |
| C22 | OFF | J1 | teste async afirma mas nunca dá await na unidade |
| C23 | BAIXO | J6 | abre um arquivo real num caminho literal |
| C24 | BAIXO | J6 | estado mutável global compartilhado entre testes |
| C25 | BAIXO | J4 | xfail sem strict=True (XPASS tratado como pass) |
| C27 | ALTO | J1 | try/except/pass sem nenhuma asserção |
| C28 | BAIXO | J4 | binding de pytest.raises nunca inspecionado |
| C29 | BAIXO | J6 | os.environ atribuído direto (vaza entre testes) |
| C30 | BAIXO | J3 | interceptor HTTP registrado mas não ativado |
| C31 | BAIXO | J4 | saída de capsys/capfd capturada mas não afirmada |
| C32 | BAIXO | J1 | @pytest.mark.skip sem reason= |
| C33 | BAIXO | J4 | métrica sklearn computada mas não afirmada |
| C34 | BAIXO | J4 | forma de assert subótima |
| C35 | BAIXO | J1 | decorator de retry/flaky mascara flakiness |
| C36 | BAIXO | J4 | pytest.fail() sem motivo |
| C37 | BAIXO | J4 | caso de parametrize duplicado |
| C38 | ALTO | J1 | dois testes com o mesmo nome (o primeiro nunca roda) |
| C39 | ALTO | J1 | retorna uma comparação em vez de afirmá-la |
| C41 | BAIXO | J4 | asserção sobre um mutador in-place que retorna None |
| C42 | ALTO | J2 | asserção sobre uma generator expression ou lambda |
| C43 | BAIXO | J1 | pytest.skip() depois da lógica do teste |
| C44 | ALTO | J2 | tautologia numérica (len()/abs() sempre verdadeiro) |
| C45 | ALTO | J1 | lista de parametrize vazia (zero casos) |
| C48 | BAIXO | J1 | dark patch: liga uma flag de modo-teste e afirma |
| C49 | BAIXO | J1 | pytest.warns/assertWarns envolve mais de uma chamada |
| C50 | BAIXO | J4 | caplog/assertLogs capturado mas não afirmado |
| C51 | ALTO | J1 | contexto pytest.raises/warns de corpo vazio |
| C52 | BAIXO | J2 | autoconfirmação de pertinência (x in {x}) |
| C55 | BAIXO | J3 | compara dois valores enraizados em mock |
| C56 | BAIXO | J1 | assert síncrono de uma corrotina nunca aguardada |
| C57 | BAIXO | J3 | compara contra um atributo de Mock não configurado |
| C59 | ALTO | J1 | comparação solta no nível do teste (irmã loose-statement do C39) |
| CC | BAIXO | J1 | assert comentado |
| D1 | OFF | J4 | roleta de asserções |
| D3 | OFF | J4 | assert duplicado |
| D4 | OFF | J4 | parametrize sem nomes |
| D5 | OFF | J5 | setup inline excessivo |
| D6 | OFF | J4 | print() de debug no corpo |
| M2 | OFF | J5 | método de teste longo |
| PL1 | BAIXO | J1 | -O/PYTHONOPTIMIZE remove todo assert |
| PL2 | BAIXO | J1 | filterwarnings não promove avisos a erro |
| PL7 | BAIXO | J5 | sem gate de cobertura |
| PL8 | BAIXO | J5 | addopts interrompe a execução cedo |
Um código mantém seu id entre linguagens onde o smell é o mesmo. Dois ids carregam um sentido
diferente por linguagem, documentado em cada página: C31 é a captura de capsys/capfd aqui,
mas o valor capturado e nunca usado (${x}= Get Text) no Robot; C44 é a
tautologia numérica aqui, ampliada no Robot para qualquer asserção de biblioteca vazia (mesmo id,
balde mais largo).
Família A - o teste nunca verifica nada¶
Modos de falha F1 (sem oráculo) e F2 (a verificação nunca roda).
C1 - asserção dentro de um condicional ou loop que pode nunca rodar¶
J1 · BAIXO · F2
O assert (ou self.assert*) vive dentro de um if, for ou while cuja condição poderia
ser falsa ou cujo iterável poderia estar vazio. O teste passa vacuamente quando o ramo nunca é
acessado.
Sinal
A asserção não é alcançável a partir do nível superior da função sem entrar em um condicional.
Não sinalizado quando o loop itera um literal não-vazio (for x in (1, 2, 3):).
C2 - o corpo do teste não contém nenhuma asserção¶
J1 · ALTO · F1
Nenhum assert, nenhum self.assert*, nenhum pytest.raises(), nenhum .should. fluente, nenhuma asserção de mock.
O corpo é só pass, uma docstring, ... ou setup. Sempre verde, não importa o código.
Sinal
Nenhuma verificação de qualquer tipo no corpo. Exceções: @pytest.mark.skip,
@pytest.mark.xfail e decoradores @hypothesis / @given / @fuzz.
C2b - o teste chama código de produção mas não verifica nada¶
J1 · BAIXO · F1
Como C2, mas com chamadas reais à unidade sob teste. A verificação simplesmente está faltando. Mantido separado porque é fácil confundir com um padrão de delegação.
Sinal
Uma chamada real ao SUT sem asserção depois. Exceção: se o teste chama um helper que contém a asserção, a verificação executa pelo helper, então não é sinalizado.
C2c - bloco self.subTest vazio¶
J1 · BAIXO · F1
Um bloco with self.subTest(...): do unittest que envolve trabalho mas não tem nenhuma asserção
dentro - o análogo do teste vazio para subTest, já que cada sub-caso gerado roda e não verifica
nada. Mais específico que a C2b, que ele substitui nessa forma. Um subTest que asserta, levanta
exceção, ou delega para um helper check_*/verify_* não é sinalizado; o receptor tem que ser self/cls.
C3 - assert dentro de um try cujo except engole o erro¶
J1 · ALTO · F2
Um try contém um assert, e o except captura AssertionError, Exception ou except:
nu com um corpo que é só pass / continue. A falha é comida; o teste fica
verde.
Sinal
Asserção dentro de try, o handler a engole. Um handler que relança ou faz trabalho
significativo não é C3.
C4 - função de teste não coletada pelo pytest¶
J1 · ALTO · F5
Um def test_* definido aninhado dentro de outra função ou método de classe, com uma asserção real,
nunca chamado e nunca decorado como rota ou callback. O pytest só coleta testes de nível superior ou de
método de classe; este fica invisível para o runner.
Sinal
test_* aninhado com uma asserção, sem chamador. Exceção: callbacks de framework (@app.get,
@click.command, corrotinas aguardadas, handlers de rota) não são C4.
C4b - a classe de teste tem __init__¶
J1 · BAIXO · F5
Uma classe chamada Test* (ou uma subclasse de unittest.TestCase) define __init__. O pytest pula tais
classes por inteiro, então nenhum dos seus testes roda.
C20 - asserção depois de um return / raise / fail incondicional¶
J1 · ALTO · F2
Um assert aparece depois de um return, raise, break, continue ou pytest.fail() no
mesmo bloco. Código morto; nunca alcançado. A detecção usa alcançabilidade estruturada intra-teste
(no nível de bloco), então pega uma asserção depois de um return / raise / fail em qualquer bloco,
não só no nível superior.
C21 - toda asserção está dentro de um condicional; nenhuma roda incondicionalmente¶
J1 · BAIXO · F2
A função tem asserções, mas toda verificação está dentro de um ramo if sem um if/else
exaustivo que garanta que ao menos uma rode. O teste pode passar sem verificar nada. O mesmo modelo
de alcançabilidade estruturada (no nível de bloco) decide isso, então o C21 dispara só quando
nenhuma asserção fica na espinha garantida do teste.
C22 - teste async que nunca aguarda a unidade sob teste¶
J1 · OFF · F2
Um async def test_* faz chamadas e tem asserções mas não contém nenhum await, async with,
async for, e não aciona um loop (asyncio.run, run_until_complete, anyio.run). A
corrotina pode retornar antes de qualquer I/O completar. Opcional.
CC - assert comentado¶
J1 · BAIXO · F2
Uma linha no corpo é # assert ...: uma verificação que foi comentada e deixada. A asserção
nunca roda. Um sinal forte de que o teste foi enfraquecido.
Família B - a verificação é fraca ou sempre verdadeira¶
Em sua maioria F3 (a verificação é trivialmente verdadeira).
C5 - asserção sempre verdadeira¶
J2 · ALTO · F3
A asserção é estruturalmente garantida de passar: assert True, assert (x, y) (uma tupla
não-vazia é sempre truthy), assert 1, assert x or True. A verificação não adiciona proteção.
C6 - asserção fraca: só verifica que algo voltou¶
J4 · BAIXO · F4
A asserção verifica só truthiness (assert result), comprimento não-vazio ou conteúdo de string
sem verificar o valor ou a estrutura real.
Sinal
Verificação só de truthiness ou comprimento. Exceção: em testes web/browser, uma resposta truthy ou
um locator É o contrato. assert response.status_code em um teste HTTP não é sinalizado.
C6b - asserção sobre um argumento posicional de mock via índice calculado¶
J3 · BAIXO · F4
O teste lê mock.call_args.args[idx] ou mock.call_args[0][idx] onde idx é calculado
(.index(), aritmética, uma variável) em vez de um literal fixo. A posição é frágil e
pode mudar em silêncio.
C6c - usar a veracidade do call_count do mock como oráculo¶
J4 · BAIXO · F4
assert mock.call_count (puro) passa para qualquer contagem >= 1, então só verifica que o mock
foi chamado, não quantas vezes. O receptor tem que ser um mock conhecido; uma contagem exata ou com
limite inferior (== N, >= 1) é uma verificação real. A forma sempre verdadeira mock.call_count >= 0 é a C44.
C7 - autocomparação: ambos os lados são idênticos¶
J2 · ALTO · F3
assert x == x, assertEqual(x, x), ou qualquer comparação onde ambos os lados são sintaticamente
idênticos e não contêm chamadas de função. Sempre verdadeira por reflexividade.
Sinal
Operandos idênticos, sem chamadas. Exceção: se o teste também verifica x != peer, x in {x}
ou hash(x), ele está testando a semântica de __eq__ / __hash__, não C7.
C8 - igualdade exata de float¶
J4 · BAIXO · F4
== contra um literal float não-sentinela (qualquer coisa além de 0.0 ou 1.0). A aritmética
de ponto flutuante torna a igualdade exata pouco confiável.
C8b - igualdade aproximada sem tolerância explícita¶
J4 · BAIXO · F4
assertAlmostEqual/assertNotAlmostEqual (padrão de 7 casas) ou == pytest.approx(...) (padrão
1e-6 relativo) sem places=/delta=/rel=/abs=. A tolerância padrão pode aprovar um valor
errado de verdade. Dimensionar a tolerância aos valores mantém quieto.
C9 - pytest.raises ampla demais¶
J4 · BAIXO · F4
pytest.raises() sem tipo de exceção, ou com um muito amplo (Exception, BaseException) e
sem match=. Qualquer exceção, inclusive uma de um erro de digitação dentro do teste, satisfaz a verificação.
C11a - literal autoconfirmante: atribui e depois afirma o mesmo valor¶
J2 · BAIXO · F3
obj.attr = VALUE seguido de assert obj.attr == VALUE com o mesmo literal. O teste
confirma que a atribuição de atributo do Python funciona, não o código de produção.
C52 - autoconfirmação de pertinência¶
J2 · BAIXO · F3
assert x in {x} (ou x in [x], x in (x,)): a coleção é construída a partir do próprio
sujeito sob teste, então a pertinência vale por construção. Uma variante de pertinência da C7.
Verificar contra uma coleção montada de forma independente do sujeito é uma verificação real.
C13 - asserção de mock escrita errado ou não chamada¶
J4 · ALTO · F2
Uma asserção de mock acessada como atributo sem (): mock.assert_called_once em vez de
mock.assert_called_once_with(). O acesso ao atributo retorna um método ligado; a verificação nunca
roda. Também sinaliza nomes inventados (assert_called_twice, called_once_with).
C13b - patch() sem autospec¶
J3 · BAIXO · F4
@patch('module.Thing') ou patch.object(obj, 'method') sem autospec=True, spec= ou
spec_set=. O mock aceita qualquer assinatura de chamada em silêncio; erros de digitação em nomes ou contagens de argumentos passam
sem detecção.
C14 - golden file gerado a partir da saída real¶
J2 · BAIXO · F3
if not exists(golden_path): write(golden_path, actual_output). Na primeira execução o teste
escreve a saída atual (possivelmente errada) como o valor esperado, depois compara contra ela
para sempre.
Sinal
Write-if-missing em um golden path. Exceção: em snapshot testing de browser (Playwright, Selenium) isso é intencional e não é sinalizado.
C16 - o resultado depende de tempo, aleatoriedade ou sleep não controlados¶
J6 · BAIXO · F6
time.sleep(N), datetime.now() / time.time() sem freezegun / time_machine,
random.* sem random.seed(), torch.rand* sem torch.manual_seed() ou
train_test_split sem random_state=. Também sinaliza uuid.uuid4() / uuid.uuid1() /
uuid.getnode() e secrets.token_* / secrets.randbits / secrets.choice, todos qualificados
pelo módulo. Uma chamada from uuid import uuid4 nua e o uuid.uuid5() determinístico não são
sinalizados.
C18 - comparação de string / repr¶
J2 · BAIXO · F4
== onde um dos lados é str(x), repr(x), format(x, ...) ou uma f-string, contra um literal
de string. O formato da string é um detalhe de implementação; muda sem uma mudança semântica.
C25 - xfail sem strict=True¶
J1 · BAIXO · F5
@pytest.mark.xfail sem strict=True. Se o teste passa inesperadamente, o pytest reporta
XPASS, não uma falha. Um xfail que passa em silêncio esconde que o bug foi corrigido sem remover
a marca.
C34 - forma de asserção subótima¶
J4 · BAIXO · F8
assert not x in y (use x not in y), assert len(x) == 0 (use assert not x),
assert x == True / == False / == None / != None (use is / truthiness). Estas enfraquecem
a mensagem de erro e obscurecem a intenção.
Família C - o teste verifica o próprio setup, não o programa¶
C19 - pytest.raises envolve mais de uma chamada¶
J1 · BAIXO · F4
Um bloco with pytest.raises(E): contém mais de uma instrução. Se a primeira lança, a segunda
nunca roda, então o teste pode estar verificando uma linha diferente da pretendida.
C49 - pytest.warns / assertWarns envolve mais de uma chamada¶
J1 · BAIXO · F4
Um bloco with pytest.warns(W): / assertWarns / deprecated_call() contém mais de uma
instrução. Uma linha anterior não relacionada pode emitir o warning enquanto o alvo nunca emite,
então o teste passa sem exercitar o warning sob teste. A irmã de warns da C19.
C28 - variável de ligação do pytest.raises nunca lida¶
J4 · BAIXO · F4
with pytest.raises(E) as exc: onde exc nunca é usado depois. O tipo da exceção é
verificado mas não sua mensagem ou atributos.
C51 - contexto pytest.raises / warns de corpo vazio¶
J1 · ALTO · F1
with pytest.raises(E): (ou pytest.warns) cujo corpo é vazio (pass, ..., um comentário).
Nenhuma chamada é feita dentro do bloco, então a chamada que deveria lançar nunca roda e o
gerenciador de contexto não tem nada a capturar. Sempre verde.
C29 - os.environ modificado diretamente em um teste¶
J6 · BAIXO · F6
os.environ["KEY"] = value, os.environ.update(...) ou os.putenv(...) no corpo de um teste. A
mudança persiste entre testes no mesmo processo. Use monkeypatch.setenv().
C55 - comparação entre dois valores enraizados em mock¶
J3 · BAIXO · F4
assert m.foo == m.bar onde ambos os operandos derivam do mesmo dublê de teste (um Mock,
MagicMock ou um objeto injetado por patch). Cada lado é o próprio valor configurado pelo
teste, então a comparação verifica os dublês entre si, não o SUT.
C56 - assert síncrono de uma corrotina nunca aguardada¶
J1 · BAIXO · F2
A expressão afirmada chama um async def local sem dar await, então o operando é um objeto
corrotina, não o seu valor. Uma corrotina é sempre truthy e nunca é igual ao valor esperado que o
autor tinha em mente; a chamada real nunca rodou. Resolvido no arquivo inteiro contra o conjunto de
nomes async def.
C57 - asserção compara contra um atributo de Mock não configurado¶
J3 · BAIXO · F4
Um lado da comparação é m.attr onde m é um Mock() / MagicMock() nu, sem spec=/spec_set=
e sem atribuição a m.attr no corpo. O acesso ao atributo cria automaticamente um Mock filho novo
e truthy, então o lado esperado é o próprio auto-mock do teste, não um valor real. Só a forma de
atributo único (m.attr); ambos os lados em mock é território do C55.
Família D - o teste depende de estado externo ou compartilhado¶
Em sua maioria F6 (passa ou falha por sorte ou por ordem).
C17 - pytest.skip() dentro de um except amplo¶
J1 · ALTO · F5
Um try com uma asserção, onde o except é amplo e chama pytest.skip() ou
skipTest(). Uma falha real dispara o skip em vez de falhar o teste. Verde mesmo quando o
SUT está quebrado.
C23 - caminho de arquivo absoluto ou relativo ao home fixo no código¶
J6 · BAIXO · F6
open("/home/user/data.csv") ou Path("/tmp/fixture.json").read_text(). O caminho não
existe no CI ou em outra máquina. Use tmp_path ou Path(__file__).parent / "data.csv".
C24 - estado mutável de nível de módulo modificado por um teste¶
J6 · BAIXO · F6
O módulo declara um list, dict ou set global; um teste o modifica sem nenhuma fixture
autouse que o reinicie. A ordem dos testes decide o resultado.
C27 - try/except/pass em volta de uma chamada ao SUT sem asserção¶
J1 · ALTO · F1
Um try chama o SUT sem asserção, e o except é só pass. Sucesso e falha
ambos ficam verdes. Diferente de C3, que envolve um assert; C27 não tem assert nenhum.
C30 - mock de HTTP não ativado¶
J3 · BAIXO · F4
responses.add(...) ou httpretty.register_uri(...) chamado, mas o ativador
(@responses.activate, responses.start(), httpretty.enable()) está ausente. O HTTP real passa
adiante; o mock nunca é usado.
C31 - resultado de capsys.readouterr() descartado¶
J4 · BAIXO · F1
capsys.readouterr() chamado como expressão nua, ou atribuído a uma variável nunca lida. A
captura rodou mas nada foi verificado.
C50 - saída de caplog / assertLogs capturada mas nunca afirmada¶
J4 · BAIXO · F1
caplog é lido (caplog.records, caplog.text) ou self.assertLogs(...) é aberto, mas a saída
capturada nunca é afirmada: nenhuma comparação sobre os registros, mensagens ou níveis. A captura
rodou e não teve efeito no pass/fail. A irmã de logging da C31.
C32 - @pytest.mark.skip sem reason¶
J1 · BAIXO · F5
@pytest.mark.skip sem reason=. Nenhuma explicação para o teste desativado; pode ser esquecido
para sempre.
C35 - decorador de retry / flaky¶
J6 · BAIXO · F6
Um decorador chamado flaky, repeat, retry, rerun ou flake em um teste. Mascara
não-determinismo em vez de corrigi-lo.
Família E - passa, mas verifica a coisa errada¶
C33 - métrica de ML calculada mas não afirmada¶
J4 · BAIXO · F1
Uma métrica do sklearn (accuracy_score, f1_score, model.score()) cujo resultado é descartado ou
atribuído a uma variável nunca lida. A métrica foi calculada mas nunca validada contra um
limiar.
C36 - pytest.fail() sem reason¶
J1 · BAIXO · F8
pytest.fail() sem mensagem. A falha fica ininteligível na saída do CI.
C37 - caso duplicado de parametrize¶
J2 · BAIXO · F8
@pytest.mark.parametrize onde o mesmo conjunto de argumentos aparece duas vezes. A duplicata confirma o
mesmo caminho de código de novo e não adiciona cobertura.
Adições à família (sync do catálogo)¶
C38 - dois testes compartilham um nome¶
J1 · ALTO · F5
Dois def test_* no escopo de módulo ou de classe com o mesmo nome. Python liga o posterior sobre o
anterior, então o primeiro nunca roda.
C39 - retorna uma comparação em vez de afirmar¶
J1 · ALTO · F1
return x == y em um teste. O pytest ignora o valor retornado (avisa com
PytestReturnNotNoneWarning); nada é verificado.
C41 - asserção sobre um mutador que retorna None¶
J4 · BAIXO · F3
assert not lst.sort() / assertIsNone(lst.sort()). Se é trivialmente verde depende do
tipo do receptor, então este é um julgamento só da skill, restrito a mutadores conhecidos
(sort, append, extend, reverse, update, add, remove, insert, clear).
C42 - asserção sobre um generator ou lambda¶
J2 · ALTO · F3
assert (x for x in y) / assert lambda: .... O objeto é sempre truthy. Uma list, set ou
dict comprehension não é C42, porque pode ser vazia.
C43 - skip no meio do teste¶
J1 · BAIXO · F5
pytest.skip() depois da lógica do teste, com verificações abaixo dele que então nunca rodam. Um skip no topo é
um guard legítimo.
C44 - tautologia numérica¶
J2 · ALTO · F3
len(x) >= 0, abs(x) >= 0, len(x) > -1, ou o call_count >= 0 / > -1 de um mock. A
comparação é sempre verdadeira.
C45 - parametrize vazio¶
J1 · ALTO · F5
@pytest.mark.parametrize("...", []). Zero casos são gerados, então o teste nunca roda.
C48 - dark patch: vira um flag de modo de teste e depois afirma¶
J1 · BAIXO · F2
O teste força um toggle de modo de teste para o modo de teste (os.environ["TESTING"] = "1",
settings.TESTING = True, um TESTING = True declarado com global) e depois afirma, então
exercita o ramo só-de-teste do produto (if TESTING: ...) em vez do comportamento real.
Sinal
Um flag de modo de teste conhecido virado antes da asserção. Não dispara quando uma asserção genuína já roda antes da virada, a menos que uma asserção pós-virada leia o próprio flag alterado. Valores de config e feature flags de produto não são sinalizados.
C59 - comparação solta no nível do teste¶
J1 · ALTO · F1
result == expected escrito como instrução, não dentro de um assert. O Python computa a
comparação e descarta o resultado, então nada é verificado. A irmã loose-statement do C39 (que
retorna a comparação); o C59 é dono da linha para o C2b não reportar em duplicidade.
Camada de projeto - auditoria de config (PL)¶
Emitidos pela passagem de auditoria de config, não pela varredura por arquivo. A suíte fica verde por configuração, não por um smell dentro de algum arquivo de teste.
PL1 - asserções removidas em tempo de execução¶
J1 · BAIXO · F2
python -O / -OO ou PYTHONOPTIMIZE no ambiente de execução remove todo assert na compilação,
então a suíte inteira passa sem nenhuma verificação. Rode sem -O e desative PYTHONOPTIMIZE.
PL2 - avisos não promovidos a erro¶
J1 · BAIXO · F4
O filterwarnings do pytest não transforma avisos em erros, então deprecations e warnings de
runtime passam em silêncio. Use filterwarnings = error para que falhem a suíte.
PL7 - sem gate de cobertura¶
J5 · BAIXO · F8
Nenhum --cov-fail-under / [tool.coverage.report] fail_under está configurado, então a cobertura
pode cair a zero e a suíte ainda passa. Adicione um limiar de cobertura.
PL8 - a execução para cedo¶
J5 · BAIXO · F5
addopts carrega -x / --maxfail / --exitfirst, então a execução para nas primeiras falhas e a
contagem de testes reportada fica incompleta. Remova-os para a suíte inteira rodar.
Códigos de diagnóstico (opcionais, OFF por padrão)¶
Família F8: não é false-green (o teste ainda protege), mostrado só numa passagem de diagnóstico. Linters dedicados (ruff) também cobrem estes.
| Código | O que sinaliza |
|---|---|
| D1 | roleta de asserções: duas ou mais asserções, nenhuma com mensagem |
| D3 | assert duplicado: o assert idêntico aparece duas vezes |
| D4 | parametrize sem nomes: 3+ casos sem ids= |
| D5 | setup inline excessivo: mais de 5 instruções antes do primeiro assert |
| D6 | print() de debug deixado no corpo do teste |
| M2 | método de teste longo: corpo com mais de 50 linhas |
Parecidos: NÃO sinalizar¶
Estes lembram um smell mas estão corretos. O scanner os deixa em paz.
@pytest.mark.skip/@pytest.mark.xfailem um corpo vazio: explicitamente desativado, não C2.@given/@hypothesis/@fuzzsemassertexplícito: o hypothesis afirma internamente, não C2.- Um helper chamado pelo teste que contém o
assert: não C2b. for x in (1, 2, 3): assert x: não C1, o literal é não-vazio.assert responseem um teste HTTP /assert locatorem um teste Playwright: não C6, a presença é a asserção nessa camada.assert x == xonde o teste também verificax != peerouhash(x): testando__eq__/__hash__, não C7.freezegun/time_machineimportado: umdatetime.now()não congelado não é C16.patch(..., autospec=True): não C13b.with pytest.raises(E) as exc: ...; assert "msg" in str(exc.value): exc é lido, não C28.