DEV Community

chris
chris

Posted on

meu primeiro teste: LuizaLabs

Há um tempo, no início da minha trajetória como desenvolvedor, fiz o teste técnico da LuizaLabs — foi meu primeiro contato real com um processo seletivo na área. O desafio era ler um arquivo de log de partidas do Quake 3 Arena, extrair estatísticas de cada partida (jogadores, kills, mortes causadas pelo próprio jogo) e expor isso por uma API — com testes automatizados e, se possível, algo além do pedido.

Resolvi voltar a esse projeto agora e documentar aqui a lógica que usei e as decisões que tomei na época, tanto como registro pessoal dessa fase quanto para quem estiver enfrentando um desafio parecido hoje.

🔗 Repositório no GitHub

🧩 O que o desafio pedia

O arquivo games.log contém eventos de várias partidas em sequência, cada uma delimitada por uma linha InitGame:. Dentro de cada partida, os eventos de morte (Kill:) precisavam virar duas informações: quem matou quem, e um total de kills por jogador. A regra que exigia mais atenção: quando o world (o próprio cenário do jogo — quedas, lava, etc.) mata um jogador, isso não conta como kill de outro jogador, e ainda deve penalizar quem morreu em -1 no placar.

🏗️ O que tentei / arquitetura

Na época, separei o problema em três camadas desde o início, em vez de escrever tudo dentro da rota da API:

  • Parser puro (parser.py): só lê texto e devolve estruturas de dados Python. Sem Flask, sem banco, testável isoladamente.
  • API (app.py, Flask): expõe o resultado do parser via rotas REST.
  • Persistência (models.py, PyMongo): grava e consulta o resultado no MongoDB Atlas, como um extra além do que foi pedido.

Essa separação foi a decisão mais importante do projeto — ela que permitiu testar a lógica de parsing sem precisar subir servidor nem banco.

💻 Implementação

A parte central é identificar o início de cada partida e agrupar os eventos até a próxima InitGame::

def parse_log(log_text: str) -> dict:
    games = {}
    game_index = 0
    current_game = None

    for line in log_text.splitlines():
        if "InitGame:" in line:
            game_index += 1
            current_game = f"game_{game_index}"
            games[current_game] = {"total_kills": 0, "players": set(), "kills": {}}

        elif "Kill:" in line and current_game:
            killer, victim = extract_kill_participants(line)
            games[current_game]["total_kills"] += 1

            if killer != "<world>":
                games[current_game]["players"].add(killer)
                games[current_game]["kills"][killer] = games[current_game]["kills"].get(killer, 0) + 1

            games[current_game]["players"].add(victim)
            if killer == "<world>":
                games[current_game]["kills"][victim] = games[current_game]["kills"].get(victim, 0) - 1

    for game in games.values():
        game["players"] = list(game["players"])

    return games
Enter fullscreen mode Exit fullscreen mode

A regra do <world> ficou concentrada num único lugar: se quem matou é <world>, não soma kill para ninguém (o jogo não é um jogador), e ainda desconta um ponto de quem morreu. Isso evita que a mesma regra precise ser reimplementada em outro lugar do código.

🐛 Erros e dificuldades

O erro mais bobo e mais custoso: no começo, eu extraía jogadores da linha de kill sem considerar que <world> também aparece como "killer" — e estava adicionando <world> na lista de jogadores da partida. Isso inflava a contagem de "players" e quebrava um teste que verificava o número exato de jogadores únicos por partida.

A correção foi simples depois de identificada: qualquer lugar que adiciona alguém à lista de players, primeiro verifica se o nome não é <world>.

Outro ponto que exigiu atenção: eu inicialmente rodava pytest sem ajustar o PYTHONPATH, e os imports entre backend/ e backend/tests/ quebravam dependendo de onde eu executava o comando. Resolvido fixando a execução sempre a partir da raiz do projeto, com PYTHONPATH="." explícito.

✅ Solução

Com o parser isolado e testado separadamente da API, ficou fácil garantir que a lógica de negócio (contagem de kills, regra do world) estava correta antes mesmo de qualquer rota HTTP existir. Os testes (test_parser.py) cobrem esses cenários isoladamente, e test_api.py cobre os endpoints por cima disso.

Como extra, adicionei persistência em MongoDB Atlas e uma rota separada (/games/db) para consultar os dados salvos — o desafio pedia só a API sobre o arquivo de log, mas achei que demonstrar uma camada de persistência agregava ao teste.

📌 O que aprendi

O maior aprendizado não foi técnico no sentido de sintaxe — foi sobre onde colocar a complexidade. Separar o parser puro da API me forçou a pensar em contratos de dados antes de pensar em rotas, e isso tornou os testes muito mais simples de escrever. Também aprendi, na prática, como um detalhe de regra de negócio mal tratado num único ponto (o caso do <world>) se propaga silenciosamente para métricas que nada tem a ver com ele à primeira vista (contagem de players).

🏁 Conclusão

Foi meu primeiro teste técnico como desenvolvedor e também minha primeira vez estruturando um projeto pensando desde o início em separação de responsabilidades e cobertura de testes, não só em "fazer funcionar". Olhando para trás, fico contente, claro que hoje em dia faria de uma forma completamente diferente.


Python #Flask #MongoDB #API #TesteTecnico #DevJunior #PrimeiroEmprego #BackendDevelopment #100DaysOfCode

Top comments (0)