Este guia aborda a criação de um conjunto de scripts para gerenciar de forma automatizada uma base de testes existente. O cenário envolve uma estrutura de diretórios com múltiplos módulos e submódulos, onde os arquivos de teste podem apresentar inconsistências de nomenclatura, status e metadados.
Estrutura de Diretórios e Cenário Atual
Os casos de teste estão organizados em um diretório raiz testcases, subdividido em módulos e submódulos. Cada script .py contém uma única classe de teste. Problemas comuns incluem nomes de arquivos com símbolos (✅/❌), discrepâncias entre nome do arquivo e nome da classe, e metadados (como prioridade) inconsistentes.
Exemplo de Script de Teste
Cada teste segue um padrão definido por uma classe que herda de TestCase. Os metadados são definidos como atributos de classe.
# Arquivo: sample_test.py
from testbase import TestCase
class SampleTest(TestCase):
"""Exemplo de caso de teste automatizado"""
description = ''
owner = 'developer'
priority = TestCase.EnumPriority.Medium
status = TestCase.EnumStatus.Ready
tags = ("exemplo", "módulo1")
timeout = 2
def run_test(self):
"""Passos:
1. Realizar ação principal.
2. Verificar resultado esperado.
"""
self.start_step('Ação principal')
# Implementação do passo 1
if __name__ == '__main__':
SampleTest().debug_run()
Scripts de Automação para Gestão
1. Estatísticas de Casos de Teste
Este script percorre a estrutura de diretórios e coleta informações das classes de teste, gerando contagens por módulo e por responsável.
import os
import importlib
import inspect
from collections import defaultdict
def collect_test_cases(root_dir):
module_stats = defaultdict(int)
owner_stats = defaultdict(int)
for dirpath, _, filenames in os.walk(root_dir):
for file in filenames:
if file.endswith('.py') and file != '__init__.py':
rel_path = os.path.relpath(os.path.join(dirpath, file), root_dir)
module_path = rel_path.replace(os.sep, '.').rstrip('.py')
try:
mod = importlib.import_module(f'testcases.{module_path}')
for name, obj in inspect.getmembers(mod, inspect.isclass):
if issubclass(obj, 'TestCase') and obj is not 'TestCase':
module = os.path.dirname(rel_path)
owner = getattr(obj, 'owner', 'Não definido')
module_stats[module] += 1
owner_stats[owner] += 1
except Exception as e:
print(f"Erro ao importar {module_path}: {e}")
return module_stats, owner_stats
2. Limpeza de Nomes de Arquivos
Remove marcadores de status (✅,❌) dos nomes dos arquivos de teste.
import os
import re
def clean_filenames(root_dir):
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if filename.endswith('.py'):
cleaned_name = re.sub(r'[✅❌]', '', filename)
if cleaned_name != filename:
old_path = os.path.join(dirpath, filename)
new_path = os.path.join(dirpath, cleaned_name)
os.rename(old_path, new_path)
print(f"Renomeado: {filename} -> {cleaned_name}")
3. Sincronização de Nomes de Classes
Altera o nome da classe dentro do script para corresponder ao nome do arquivo (convertido de snake_case para PascalCase).
import os
import re
def snake_to_pascal(name):
return ''.join(word.capitalize() for word in name.split('_'))
def fix_class_names(root_dir):
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if filename.endswith('.py') and filename != '__init__.py':
file_class_name = snake_to_pascal(os.path.splitext(filename)[0])
filepath = os.path.join(dirpath, filename)
with open(filepath, 'r') as f:
content = f.read()
# Encontra e substitui o nome da classe no código
pattern = r'^class\s+\w+\(TestCase\):'
match = re.search(pattern, content, re.MULTILINE)
if match:
old_class = match.group()
new_class = f'class {file_class_name}(TestCase):'
new_content = content.replace(old_class, new_class)
if new_content != content:
with open(filepath, 'w') as f:
f.write(new_content)
print(f"Corrigido: {filename}")
4. Marcação de Casos em Imlpementação
Renomeia scripts com status Implement para incluir sufixo _implement.
import os
import importlib
def mark_implement_status(root_dir):
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if filename.endswith('.py') and not filename.endswith('_implement.py'):
rel_path = os.path.relpath(os.path.join(dirpath, file), root_dir)
module_path = rel_path.replace(os.sep, '.').rstrip('.py')
try:
mod = importlib.import_module(f'testcases.{module_path}')
for name, obj in inspect.getmembers(mod, inspect.isclass):
if hasattr(obj, 'status') and obj.status == 'TestCase.EnumStatus.Implement':
base_name = os.path.splitext(filename)[0]
new_filename = f"{base_name}_implement.py"
os.rename(
os.path.join(dirpath, filename),
os.path.join(dirpath, new_filename)
)
print(f"Marcado: {filename} -> {new_filename}")
break
except Exception:
pass
5. Geração de Casos a partir de Planilha
Cria scripts de teste no formato padrão com base em uma lista de casos de teste em formato CSV.
import os
import csv
TEMPLATE = '''from testbase import TestCase
class {classname}(TestCase):
"""{title}"""
owner = "{owner}"
priority = TestCase.EnumPriority.{priority}
status = TestCase.EnumStatus.Implement
def run_test(self):
"""Passos:
{steps}
"""
pass
'''
def generate_test_cases(csv_path, target_dir):
os.makedirs(target_dir, exist_ok=True)
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
classname = row['nome_classe']
filename = f"{classname.lower()}.py"
filepath = os.path.join(target_dir, filename)
steps = '\n'.join([f' {i+1}. {step}' for i, step in enumerate(row['passos'].split('\\n'))])
with open(filepath, 'w') as f:
f.write(TEMPLATE.format(
classname=classname,
title=row['titulo'],
owner=row['responsavel'],
priority=row['prioridade'],
steps=steps
))
print(f"Gerado: {filename}")
6. Exportação de Metadados para CSV
Coleta metadados de todos os casos de teste e exporta para um arquivo CSV.
import os
import csv
import importlib
import inspect
def export_test_metadata(root_dir, output_file):
with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Classe', 'Título', 'Módulo', 'Prioridade', 'Status', 'Responsável', 'Caminho'])
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if filename.endswith('.py') and filename != '__init__.py':
rel_path = os.path.relpath(os.path.join(dirpath, filename), root_dir)
module = os.path.dirname(rel_path)
module_path = rel_path.replace(os.sep, '.').rstrip('.py')
try:
mod = importlib.import_module(f'testcases.{module_path}')
for name, obj in inspect.getmembers(mod, inspect.isclass):
if hasattr(obj, 'run_test'):
writer.writerow([
name,
getattr(obj, '__doc__', ''),
module,
getattr(obj, 'priority', ''),
getattr(obj, 'status', ''),
getattr(obj, 'owner', ''),
rel_path
])
except Exception as e:
print(f"Erro ao processar {module_path}: {e}")