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.
🧩 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
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.
Top comments (0)