Saltar para o conteúdo
Telmo Silva/hub
Curso de Python para QA · Módulo 1 de 4

Fundamentos de Automação de Testes com Python

As primeiras quatro semanas, do zero até ao teu primeiro bug report.

Por Telmo Silva

Ir para o teu progresso ↓

Porque escrevi este curso

A minha primeira exposição relevante a programação foi a construir ferramentas de automação em Python, já como Technical Trainer, para reduzir erro manual na administração da formação. Não tinha percurso técnico. O que tinha era quase uma década em qualidade e apoio ao cliente, e foi essa disciplina, não um curso de informática, que me ensinou a pensar como quem testa: o que é que isto devia fazer, o que é que fez de facto, e como é que verifico a diferença sem confiar de olhómetro.

Este curso ensina Python a partir daí: não programação em geral, programação para quem vai usá-la para testar e verificar coisas. Cada módulo tem teoria, código real explicado linha a linha, e um exercício que produz algo que existe fora do curso, um script que corres, um projeto no GitHub, um bug report a sério.

Uma honestidade sobre o que aqui está

É o primeiro de quatro cursos que escrevi. Os outros três ainda não estão publicados aqui. Este resulta de ensinar automação de testes a sério, não de um curso genérico de Python adaptado: cada exemplo, desde o primeiro módulo, já é um exemplo de QA (validar um email, comparar dois ficheiros, encontrar um erro num log), porque é isso que vais fazer com isto no dia a dia.

Serve para quem nunca escreveu uma linha de código e quer entrar por testes, não por uma app ou um site. Precisas só de Python instalado (3.10 ou mais recente) e de um editor de código, como o VS Code, que é gratuito.

Não precisas de saber nada disto hoje. Precisas de escrever a primeira linha.

9 capítulos, com código a sério em cada um. Não é para ler de seguida: para cada módulo, o que importa é fazeres o exercício, não só leres a solução escondida.

01Variáveis, Tipos, Condicionais, Loops e Funções

Variáveis

Uma variável é uma caixa com um nome, onde guardas um valor para usares mais tarde. Em Python, crias uma variável só ao dares-lhe um nome e um valor:

python

nome = "Ana Ferreira"
idade = 28
aprovado = True
print(nome, idade, aprovado)
  • Não precisas de dizer antecipadamente que tipo de valor a variável vai guardar: o Python percebe sozinho.
  • Usa nomes que digam o que a variável guarda (nome, não x). Vais ler este código muitas vezes mais do que o escreves.
  • O sinal de igual aqui não significa "igual a" como em matemática; significa "guarda isto nesta caixa".

Tipos de dados

Cada valor tem um tipo, e o tipo determina o que podes fazer com ele. Os quatro que vais usar constantemente:

python

nome = "Ana Ferreira"   # str: texto, sempre entre aspas
idade = 28               # int: número inteiro
altura = 1.68             # float: número com casas decimais
aprovado = True           # bool: só pode ser True ou False
print(nome, idade, altura, aprovado)

Podes sempre confirmar o tipo de uma variável com type():

python

idade = 28
print(type(idade))   # <class 'int'>

Um erro muito comum de principiante: "28" (com aspas) é texto, não é o número 28. "28" + 1 dá erro: não podes somar texto com número. Se precisares de converter, usa int("28") ou str(28).

Condicionais (if / else)

Um condicional é uma instrução que só corre se uma condição for verdadeira, como "se chover, levo o guarda-chuva". Em Python:

python

idade = 16

if idade >= 18:
    print("Pode votar")
else:
    print("Ainda não pode votar")

O bloco a seguir ao if só corre se a condição for verdadeira. O bloco a seguir ao else só corre se for falsa. Repara na indentação (os espaços antes de print). Em Python, a indentação não é estética, é o que define o que pertence a cada bloco. Se a indentação estiver errada, o código não funciona como esperas, mesmo sem dar erro.

Para mais do que duas hipóteses, usa elif ("else if"):

python

nota = 14

if nota >= 18:
    print("Excelente")
elif nota >= 10:
    print("Aprovado")
else:
    print("Reprovado")

Loops

Um loop é uma instrução que repete uma ação várias vezes, sem teres de a escrever repetidamente. Os dois tipos que vais usar:

python

# for: quando sabes por quantos elementos vais passar
nomes = ["Ana", "Bruno", "Carla"]
for nome in nomes:
    print(nome)

# while: quando repetes até uma condição deixar de ser verdadeira
tentativas = 0
while tentativas < 3:
    print("A tentar...")
    tentativas = tentativas + 1

for é o que vais usar mais: em QA automation, quase sempre estás a percorrer uma lista de coisas para verificar (linhas de um ficheiro, resultados de um teste, registos de uma base de dados).

Funções

Uma função é um bloco de código com nome, que podes reutilizar sempre que precisares, como uma receita. Defines uma função com def:

python

def saudacao(nome):
    return f"Olá, {nome}!"

mensagem = saudacao("Ana")
print(mensagem)   # Olá, Ana!

nome aqui é um parâmetro: um valor que entra na função de fora. return é o que a função devolve a quem a chamou. Sem return, a função ainda corre, mas não devolve nada útil para usares depois.

Repara em f"Olá, {nome}!", chama-se f-string: o f antes das aspas permite meter variáveis diretamente dentro do texto, entre chavetas. Vais usar isto constantemente para construir mensagens.

Escrever código como funções, em vez de uma sequência solta de instruções, é o que te vai permitir, já no exercício deste módulo, reutilizar a mesma lógica de validação para emails, datas e IDs sem copiar e colar três vezes.

02O Terminal, e os Tipos de Teste

O terminal

O terminal é a janela onde escreves comandos de texto para o computador executar, em vez de clicares com o rato. É estranho nas primeiras vezes e depois torna-se o sítio onde passas mais tempo como QA automation.

bash

cd nome-da-pasta      # entrar numa pasta (cd = "change directory")
cd ..                 # subir uma pasta
dir                   # listar ficheiros da pasta atual (Windows)
ls                    # listar ficheiros da pasta atual (Mac/Linux)
python nome_do_ficheiro.py    # correr um script Python

Para o exercício deste módulo, o fluxo típico é: abrir o terminal, entrar (cd) até à pasta onde guardaste o script e os ficheiros de dados, e depois correr python validador_de_dados.py. Se o terminal disser que não encontra o ficheiro, o problema quase sempre é estares na pasta errada. Confirma com dir/ls o que está mesmo à tua volta antes de assumir que o código tem um erro.

Tipos de teste: funcional, regressão, exploratório

Três formas diferentes de testar, cada uma respondendo a uma pergunta diferente.

  • Teste funcional: verificar se uma funcionalidade faz o que devia fazer. Pergunta: "isto faz o que prometeu?" Exemplo: confirmar que um formulário de login realmente deixa entrar com credenciais certas.
  • Teste de regressão: verificar se uma alteração nova não estragou algo que já funcionava. Pergunta: "o que já funcionava, continua a funcionar?" Exemplo: depois de corrigires um bug no checkout, confirmas que o login continua a funcionar.
  • Teste exploratório: usar a aplicação sem um guião fixo, à procura de problemas inesperados. Pergunta: "o que é que ainda não pensei em testar?" Não segues uma lista: navegas, experimentas casos estranhos (um email sem "@", um campo vazio, colar um texto gigante) só para ver o que acontece.

O validador que vais construir hoje é, no fundo, um teste funcional automatizado: em vez de olhares linha a linha para um ficheiro de dados a confirmar se cada valor tem o formato certo, escreves um script que faz essa verificação por ti, e que podes correr outra vez amanhã, com dados diferentes, sem repetir o trabalho manual.

03Níveis de Teste, e o Ciclo de Vida de um Defeito

Níveis de teste: unitário, integração, sistema, aceitação

Estes quatro níveis diferem na escala do que estás a testar, de uma peça isolada até ao produto inteiro.

  • Teste unitário: testar a mais pequena parte possível do código, isolada do resto. Exemplo: testar só a função saudacao() do capítulo anterior, sem depender de mais nada.
  • Teste de integração: testar se duas partes do sistema funcionam bem quando ligadas entre si. Exemplo: a função que valida um email funciona bem quando ligada à função que lê o ficheiro CSV?
  • Teste de sistema: testar a aplicação completa, do início ao fim. Exemplo: correr o validador_de_dados.py inteiro, do ficheiro de entrada ao resultado final impresso.
  • Teste de aceitação: confirmar que o produto cumpre o que o cliente pediu. Exemplo: se o pedido foi "um script que me diga quais das minhas 100 linhas de dados têm erros", o teste de aceitação é mesmo isso: dás-lhe 100 linhas reais e confirmas que o resultado é útil e correto.

Não são níveis que se "escolhem". Um projeto real normalmente tem os quatro, cada um a apanhar problemas que os outros não apanhariam sozinhos.

Ciclo de vida de um defeito

Quando encontras um problema (um "defeito" ou "bug"), ele não desaparece só por o teres reportado; passa por estados até estar mesmo resolvido.

  1. Reportado: o problema foi identificado e escrito (o que vais praticar no capítulo 8, com o bug report).
  2. Em análise: alguém confirma que é mesmo um problema (e não, por exemplo, um mal-entendido sobre como a funcionalidade deveria funcionar).
  3. Corrigido: a alteração no código foi feita.
  4. Verificado: confirma-se que a correção realmente resolveu o problema, e que não introduziu um novo.
  5. Fechado: o ciclo termina.

Vais viver este ciclo já no exercício deste módulo, em miniatura: quando o teu validador disser que um valor é inválido, isso é "reportado". Confirmares porque é inválido é "em análise". Se fores tu a corrigir o dado de origem, isso é "corrigido", e correr o validador outra vez para confirmar é "verificado".

04Prática: o Teu Primeiro Validador

Antes de começares o Exercício 1, cria um ficheiro chamado dados_teste.csv, na mesma pasta onde vais escrever o script, com este conteúdo (copia exatamente, inclui casos válidos e inválidos de propósito):

csv

tipo,valor
email,ana.ferreira@exemplo.com
email,bruno@@exemplo.com
email,carla.exemplo.com
email,diogo@exemplo
data,2026-09-30
data,30-09-2026
data,2026-9-3
data,2026/09/30
id,QA1234
id,qa_1234
id,QA
id,QA123456789012

Exercício 1: Validador de dados de teste

Objetivo: Escrever validador_de_dados.py, um script que lê um ficheiro CSV com dados de teste e diz, para cada linha, se o valor é válido ou não, para emails, datas e IDs.

  • Lê o CSV linha a linha (podes usar o módulo csv da biblioteca padrão, ou simplesmente .split(",") em cada linha).
  • Um email é válido se: tiver exatamente um @, tiver pelo menos um ponto depois do @, e não começar nem terminar em @.
  • Uma data é válida se seguir o formato AAAA-MM-DD: exatamente 10 caracteres, com hífen na posição 5 e na posição 8, e todos os outros caracteres a serem dígitos.
  • Um ID é válido se tiver entre 5 e 12 caracteres, e for composto só por letras e números.
  • Escreve uma função separada para cada tipo de validação (validar_email, validar_data, validar_id): cada uma recebe o valor e devolve True ou False.
  • No final, imprime cada linha com o resultado, e um resumo: quantas válidas, quantas inválidas.

Dica: Para verificar os caracteres de uma posição específica de uma string, usa valor[posicao]: por exemplo, valor[4] dá-te o 5º caráter (a contagem começa em 0).

Ver solução
import csv


def validar_email(valor):
    if valor.count("@") != 1:
        return False
    if valor.startswith("@") or valor.endswith("@"):
        return False
    posicao_arroba = valor.index("@")
    parte_depois = valor[posicao_arroba + 1:]
    return "." in parte_depois


def validar_data(valor):
    if len(valor) != 10:
        return False
    if valor[4] != "-" or valor[7] != "-":
        return False
    partes = valor.split("-")
    if len(partes) != 3:
        return False
    ano, mes, dia = partes
    return ano.isdigit() and mes.isdigit() and dia.isdigit()


def validar_id(valor):
    if len(valor) < 5 or len(valor) > 12:
        return False
    return valor.isalnum()


def validar(tipo, valor):
    if tipo == "email":
        return validar_email(valor)
    if tipo == "data":
        return validar_data(valor)
    if tipo == "id":
        return validar_id(valor)
    return False


def main():
    validos = 0
    invalidos = 0

    with open("dados_teste.csv", encoding="utf-8") as ficheiro:
        leitor = csv.DictReader(ficheiro)
        for linha in leitor:
            tipo = linha["tipo"]
            valor = linha["valor"]
            resultado = validar(tipo, valor)

            if resultado:
                validos += 1
                print(f"OK    [{tipo}] {valor}")
            else:
                invalidos += 1
                print(f"FALHA [{tipo}] {valor}")

    print(f"\n{validos} válidos, {invalidos} inválidos")


if __name__ == "__main__":
    main()

Antes deste exercício, cria também um ficheiro log.txt na mesma pasta, com este conteúdo:

2026-08-01 09:12:03 INFO  Servidor iniciado
2026-08-01 09:12:05 INFO  Ligação à base de dados estabelecida
2026-08-01 09:14:22 INFO  Pedido recebido: GET /utilizadores
2026-08-01 09:14:22 ERROR Timeout ao ligar à base de dados
2026-08-01 09:15:01 INFO  Pedido recebido: POST /login
2026-08-01 09:15:02 ERROR Credenciais inválidas para o utilizador 'ana'
2026-08-01 09:16:40 INFO  Pedido recebido: GET /produtos
2026-08-01 09:16:41 INFO  Resposta enviada: 200 OK
2026-08-01 09:18:12 ERROR Ficheiro de configuração não encontrado
2026-08-01 09:19:00 INFO  Servidor encerrado

Exercício 2: Percorrer ficheiros de log à procura de erros

Objetivo: Escrever um script que lê um ficheiro de log linha a linha e identifica as linhas que representam um erro.

  • Lê o ficheiro log.txt linha a linha.
  • Uma linha é um erro se contiver o texto ERROR.
  • Para cada linha de erro, imprime o número da linha (a primeira linha é a linha 1, não 0) e o conteúdo da linha.
  • No final, imprime quantas linhas de erro foram encontradas, no total de quantas linhas o ficheiro tem.

Dica: enumerate() dá-te o número da posição e o valor ao mesmo tempo num for. Evita teres de gerir um contador à mão.

Ver solução
def encontrar_erros(caminho_ficheiro):
    total_linhas = 0
    erros = []

    with open(caminho_ficheiro, encoding="utf-8") as ficheiro:
        for numero, linha in enumerate(ficheiro, start=1):
            total_linhas += 1
            if "ERROR" in linha:
                erros.append((numero, linha.strip()))

    return erros, total_linhas


def main():
    erros, total_linhas = encontrar_erros("log.txt")

    for numero, linha in erros:
        print(f"Linha {numero}: {linha}")

    print(f"\n{len(erros)} erro(s) em {total_linhas} linhas")


if __name__ == "__main__":
    main()

05Listas, Dicionários e Ficheiros CSV

Listas

Uma lista é uma sequência ordenada de valores, como uma lista de compras: os itens têm uma ordem, e podes ter valores repetidos. Em Python, cria-se entre parênteses retos:

python

produtos = ["teclado", "rato", "monitor"]

print(produtos[0])       # teclado: o primeiro item está na posição 0
print(len(produtos))     # 3: quantos itens a lista tem

produtos.append("webcam")
print(produtos)           # ['teclado', 'rato', 'monitor', 'webcam']

produtos[0] acede ao item na posição 0: a contagem começa em 0, tal como já viste ao indexar caracteres de uma string no capítulo 1. append() acrescenta um item ao fim da lista. E percorrer uma lista item a item é simples:

python

produtos = ["teclado", "rato", "monitor"]
for produto in produtos:
    print(f"Produto: {produto}")

Dicionários

Um dicionário é uma coleção de pares nome-valor, como uma agenda telefónica: procuras por um nome (a chave) e encontras a informação associada (o valor). Em Python, cria-se entre chavetas, com dois pontos a separar cada chave do seu valor:

python

produto = {"id": "1", "nome": "Teclado mecânico", "preco": "29.90"}

print(produto["nome"])       # Teclado mecânico
produto["preco"] = "24.90"   # atualizar um valor existente
print(produto)

Ao contrário de uma lista, não acedes a um dicionário por posição; acedes pelo nome da chave. Uma combinação muito comum em QA automation é uma lista de dicionários: vários registos, cada um com os mesmos campos.

python

produtos = [
    {"id": "1", "nome": "Teclado mecânico", "preco": "29.90"},
    {"id": "2", "nome": "Rato sem fios", "preco": "9.90"},
]

for produto in produtos:
    print(produto["nome"], "->", produto["preco"])

É exatamente esta forma (uma lista, com um dicionário por linha) que vais obter ao ler um ficheiro CSV em Python.

Ler e escrever ficheiros CSV

CSV é a sigla de comma-separated values, valores separados por vírgula. É um ficheiro de texto simples onde cada linha representa um registo, e a primeira linha costuma ser o cabeçalho. Python já vem com um módulo pronto para ler isto, chamado csv; não precisas de instalar nada. A ferramenta mais prática é csv.DictReader, que lê cada linha diretamente como um dicionário, usando o cabeçalho como chaves:

python

import csv

with open("produtos.csv", encoding="utf-8") as ficheiro:
    leitor = csv.DictReader(ficheiro)
    for linha in leitor:
        print(linha["id"], linha["produto"], linha["preco"])

Repara no encoding="utf-8" ao abrir o ficheiro: garante que acentos, como em "mecânico", são lidos corretamente. Para escrever é parecido, com csv.DictWriter:

python

import csv

produtos = [
    {"id": "1", "produto": "Teclado mecânico", "preco": "29.90"},
    {"id": "2", "produto": "Rato sem fios", "preco": "9.90"},
]

with open("saida.csv", "w", encoding="utf-8", newline="") as ficheiro:
    colunas = ["id", "produto", "preco"]
    escritor = csv.DictWriter(ficheiro, fieldnames=colunas)
    escritor.writeheader()
    escritor.writerows(produtos)
  • "w" (de write, escrever) em vez de nada: sem isto, open() assume que queres ler o ficheiro, e o teu código falha.
  • newline="" evita linhas em branco extra entre cada registo no Windows. Basta copiares esta linha sempre que escreveres um CSV.
  • escritor.writeheader() escreve a linha de cabeçalho primeiro. Sem isto, o ficheiro não diz o que cada coluna significa.

06Comparar o Esperado com o Obtido

No fundo, isto é o que QA automation faz a maior parte do tempo: comparar o que esperavas obter com o que realmente obtiveste, e assinalar as diferenças. É a mesma lógica de conferires o talão de compras com o que está mesmo dentro do saco: se o talão diz 3 artigos e só encontras 2 no saco, descobriste um problema sem teres de confiar de olhómetro.

Em Python, a comparação mais simples é entre dois valores, com dois sinais de igual:

python

esperado = 29.90
obtido = 34.90

if esperado == obtido:
    print("OK: valores iguais")
else:
    print(f"DIFERENÇA: esperado {esperado}, obtido {obtido}")

Na prática, raramente comparas só um valor solto; comparas registos inteiros, como uma linha de um CSV. Nesse caso, comparas campo a campo, e juntas os nomes dos campos que forem diferentes:

python

def comparar_linhas(linha_esperada, linha_obtida):
    diferencas = []
    for campo in linha_esperada:
        if linha_esperada[campo] != linha_obtida[campo]:
            diferencas.append(campo)
    return diferencas


linha_esperada = {"id": "1", "produto": "Teclado mecânico", "preco": "29.90"}
linha_obtida = {"id": "1", "produto": "Teclado mecânico", "preco": "34.90"}

diferencas = comparar_linhas(linha_esperada, linha_obtida)
print(diferencas)   # ['preco']: só o preço é diferente

Baseline e atual

Vais ver muito este par de palavras em QA. Baseline é a versão de referência: o resultado já confirmado como correto, o teu talão de compras. Atual é o resultado mais recente, o que queres validar contra essa referência. Comparar baseline contra atual, campo a campo, é praticamente um teste de regressão feito à mão, só que agora vais deixar o Python fazê-lo por ti, mais depressa e sem erros de distração.

07Git, Sem Mistério

Até agora, sempre que alteraste um script, a versão anterior desapareceu: escreveste por cima dela. O Git é uma ferramenta que guarda o histórico de alterações do teu código: cada versão fica registada, e podes sempre voltar atrás se algo correr mal, como um botão de "undo" que continua a funcionar mesmo depois de fechares o computador e voltares no dia seguinte.

O ciclo básico tem cinco comandos, escritos no terminal, dentro da pasta do teu projeto:

bash

git init
git add .
git commit -m "Primeira versão do comparador"
git remote add origin https://github.com/o-teu-utilizador/comparador-resultados.git
git push -u origin main
  • git init: transforma a pasta atual num repositório Git. Só corres isto uma vez, no início do projeto.
  • git add .: prepara ficheiros para serem guardados. O ponto significa "tudo o que está nesta pasta".
  • git commit -m "...": guarda mesmo uma fotografia do projeto no histórico, com uma mensagem a explicar o que mudou.
  • git remote add origin ...: liga o teu repositório local a um repositório vazio criado antes no GitHub.
  • git push -u origin main: envia os commits guardados localmente para o GitHub. O -u só é necessário na primeira vez.

Antes de correres o git remote add, precisas de ter criado esse repositório vazio no site do GitHub, normalmente com o botão verde "New repository".

Um comando extra que vale a pena conheceres já: git status. A qualquer momento, diz-te o que mudou desde o último commit, e o que ainda não foi preparado com git add.

08Como Escrever um Bug Report

Encontraste uma diferença entre o esperado e o obtido. E agora? Se guardares isso só na tua cabeça, essa informação perde-se, e quem tem de corrigir o problema não sabe por onde começar. Um bug report é um documento que descreve um problema de forma clara o suficiente para outra pessoa, ou tu próprio duas semanas depois, perceber exatamente o que aconteceu, sem teres de estar ao lado a explicar.

Um bug report útil tem sempre estes campos:

  • Título: um resumo numa linha, específico o suficiente para se distinguir de outros problemas. "O preço está errado" é fraco; "Preço do produto id 1 aparece como 34.90€ em vez de 29.90€" já diz logo do que se trata.
  • Passos para reproduzir: a sequência exata de ações para chegar ao problema, numerada.
  • Esperado: o que devia ter acontecido.
  • Obtido: o que realmente aconteceu.
  • Severidade: o quão grave é o problema.
SeveridadeExemplo
CríticaA aplicação bloqueia, perde dados, ou impede uma tarefa essencial.
AltaUma funcionalidade importante falha, mas existe forma de contornar.
MédiaUm problema real e visível, mas que não impede o uso geral da aplicação.
BaixaUm problema cosmético, que não afeta o funcionamento.

Repara como isto encaixa diretamente no ciclo de vida de um defeito do capítulo 3: escrever o bug report é o passo "reportado". É o ponto de partida de tudo o resto.

Resposta fraca, e porquê

Corri o script e deu erro.

Não chega. Diz que ficheiros usaste, que comando correste exatamente, e o que apareceu no ecrã. Escreve os passos como se quem vai ler nunca tivesse visto o teu projeto.

09Prática: o Teu Primeiro Comparador

Antes de escreveres o comparador, cria dois ficheiros na mesma pasta: baseline.csv e atual.csv. São deliberadamente parecidos mas não iguais, para teres o que comparar. Conteúdo de baseline.csv:

csv

id,produto,preco
1,Teclado mecânico,29.90
2,Rato sem fios,9.90
3,Monitor 24 polegadas,139.00
4,Auscultadores Bluetooth,19.50
5,Webcam HD,24.90
6,Suporte para portátil,15.00

Conteúdo de atual.csv:

csv

id,produto,preco
1,Teclado mecânico,34.90
2,Rato sem fios,9.90
3,Monitor 24 polegadas,139.00
5,Webcam HD,24.90
6,Suporte para portátil,15.00
7,Rato gaming RGB,49.90

Exercício 1: Comparar dois ficheiros, baseline vs. atual

Objetivo: Escrever comparador.py, um script que lê dois ficheiros CSV, baseline.csv (a versão de referência) e atual.csv (o resultado mais recente), e lista, linha a linha, as diferenças entre eles.

  • Lê os dois ficheiros com csv.DictReader.
  • Usa a coluna id para encontrar a "mesma" linha nos dois ficheiros; não assumas que estão pela mesma ordem.
  • Se uma linha existe nos dois mas algum campo tem valores diferentes, reporta essa diferença campo a campo.
  • Se uma linha existe no baseline mas não no atual, reporta-a como "em falta".
  • Se uma linha existe no atual mas não existia no baseline, reporta-a como "nova".
  • No final, imprime um resumo: quantas linhas iguais, diferentes, em falta, novas.

Dica: Transforma cada CSV lido numa estrutura fácil de consultar por id: um dicionário onde a chave é o id e o valor é a linha inteira.

Ver solução
import csv


def ler_csv_por_id(caminho):
    linhas_por_id = {}
    with open(caminho, encoding="utf-8") as ficheiro:
        leitor = csv.DictReader(ficheiro)
        for linha in leitor:
            linhas_por_id[linha["id"]] = linha
    return linhas_por_id


def comparar(baseline, atual):
    iguais = 0
    diferentes = []
    em_falta = []
    novas = []

    for id_linha, linha_baseline in baseline.items():
        if id_linha not in atual:
            em_falta.append(id_linha)
            continue

        linha_atual = atual[id_linha]
        diferencas_campos = []
        for campo in linha_baseline:
            if campo == "id":
                continue
            if linha_baseline[campo] != linha_atual[campo]:
                diferencas_campos.append(campo)

        if diferencas_campos:
            diferentes.append((id_linha, diferencas_campos))
        else:
            iguais += 1

    for id_linha in atual:
        if id_linha not in baseline:
            novas.append(id_linha)

    return iguais, diferentes, em_falta, novas


def main():
    baseline = ler_csv_por_id("baseline.csv")
    atual = ler_csv_por_id("atual.csv")

    iguais, diferentes, em_falta, novas = comparar(baseline, atual)

    for id_linha, campos in diferentes:
        linha_base = baseline[id_linha]
        linha_atual = atual[id_linha]
        print(f"DIFERENÇA id={id_linha}:")
        for campo in campos:
            print(f"   {campo}: esperado {linha_base[campo]!r}, obtido {linha_atual[campo]!r}")

    for id_linha in em_falta:
        print(f"EM FALTA id={id_linha}: existe no baseline, não existe no atual ({baseline[id_linha]['produto']})")

    for id_linha in novas:
        print(f"NOVA id={id_linha}: não existe no baseline, existe no atual ({atual[id_linha]['produto']})")

    print(f"\n{iguais} iguais, {len(diferentes)} diferente(s), {len(em_falta)} em falta, {len(novas)} nova(s)")


if __name__ == "__main__":
    main()

Exercício 2: Publicar o projeto no GitHub

Objetivo: Publicar o projeto do exercício anterior (comparador.py, baseline.csv, atual.csv) num repositório novo no GitHub.

  • Ter o Git instalado e uma conta no GitHub.
  • Ter uma pasta local com os três ficheiros do exercício anterior.
  • Criar um repositório vazio no GitHub antes de o ligar ao projeto local.
  • Fazer o primeiro commit e enviar (push) o código.
  • Confirmar, a abrir o repositório num browser, que os ficheiros aparecem lá.

Dica: Se o git push pedir password e a tua não for aceite, o GitHub já não aceita passwords normais por linha de comandos: cria um "personal access token" nas definições da conta.

Ver solução
git init
git add .
git commit -m "Primeira versão do comparador de resultados"
git remote add origin https://github.com/o-teu-utilizador/comparador-resultados.git
git push -u origin main

Exercício 3: Escrever um bug report de um problema real

Objetivo: Escrever um bug report completo sobre um problema real que encontraste, por exemplo, uma das diferenças que o teu comparador.py identificou.

  • Usa os cinco campos: título, passos para reproduzir, esperado, obtido, severidade.
  • Os passos devem ser precisos o suficiente para outra pessoa chegar ao mesmo resultado sem mais explicação.
  • Atribui uma severidade justificada, pensando no impacto real do problema.
  • Guarda o bug report num ficheiro de texto (por exemplo, bug_report_001.md).

Dica: Escreve os passos como se quem vai ler nunca tivesse visto o teu projeto: "corri o script e deu erro" não chega.

Ver solução
# Bug Report: Exemplo real

**Título:** Preço do produto id 1 ("Teclado mecânico") está errado em atual.csv

**Passos para reproduzir:**
1. Abrir baseline.csv e confirmar o preço do produto com id 1: 29.90€.
2. Abrir atual.csv e confirmar o preço do mesmo produto (id 1): 34.90€.
3. Correr "python comparador.py" no terminal, dentro da pasta do projeto.
4. Observar a linha "DIFERENÇA id=1" no resultado impresso, com o campo preco assinalado.

**Esperado:** o preço do id 1 deveria ser 29.90€ em atual.csv, igual ao valor em baseline.csv.

**Obtido:** o preço aparece como 34.90€ em atual.csv, sem justificação conhecida.

**Severidade:** Média. O valor mostrado está incorreto e afeta diretamente o que o cliente vê, mas não impede a aplicação de funcionar.

O teu progresso

Marca os exercícios à medida que os fores fazendo a sério, não só a ler a solução. Fica guardado só neste browser.

Conclusão

Em quatro semanas: variáveis, condicionais, loops e funções; os tipos e níveis de teste; o ciclo de vida de um defeito; listas, dicionários e ficheiros CSV; comparar o esperado com o obtido; Git básico; e como escrever um bug report que outra pessoa consegue mesmo seguir. Não é tudo o que há para saber de Python. É o suficiente para começares a automatizar verificações a sério, hoje.

Se fizeste os cinco exercícios a sério, e não só leste as soluções, tens agora dois scripts que funcionam, um projeto publicado no GitHub, e um bug report escrito sobre um problema real que tu próprio encontraste. Isso é mais do que a maior parte de quem começa a aprender a programar consegue mostrar ao fim de quatro semanas.

Se ficares só com uma decisão deste curso, fica com esta: da próxima vez que fizeres algo à mão, duas vezes seguidas, pergunta-te se um script de quinze linhas o fazia por ti a terceira vez em diante.

Se fizeste os exercícios e queres o que vem a seguir, o Curso 2 já está aqui: Automação de Testes de API e Dados.

Isto ajudou-te?

Se isto te foi útil, o mais provável é ser útil a alguém que conheces. Partilha.

LinkedInWhatsApp

Fontes, e o que é só observação minha

O resto

  • É o primeiro de quatro cursos que escrevi, a partir da minha própria entrada em programação: automação em Python para reduzir erro manual, já como Technical Trainer, depois de quase uma década em qualidade e apoio ao cliente. Não é investigação, é o programa com que ensino automação de testes a sério, calibrado módulo a módulo.