Diferencia entre revisiones de «Tutorial Campo de entrenamiento»
(Página creada con «== Automatización de Pruebas de Software en una Aplicación Flask == === Parte 1: creamos pruebas para una aplicación sencilla === El objetivo de la primera parte de la...») |
m |
||
Línea 234: | Línea 234: | ||
------- coverage: xxx% ------- | ------- coverage: xxx% ------- | ||
− | Name | Stmts | Miss | Cover | + | Name | Stmts | Miss | Cover |
-----------| ------|------|------ | -----------| ------|------|------ | ||
app.py | 26 | 8 | 69% | app.py | 26 | 8 | 69% |
Revisión del 16:18 14 oct 2024
Contenido
- 1 Automatización de Pruebas de Software en una Aplicación Flask
- 1.1 Parte 1: creamos pruebas para una aplicación sencilla
- 1.1.1 Requisitos previos
- 1.1.2 Desarrollo de la Aplicación Flask
- 1.1.3 Pruebas Unitarias con pytest
- 1.1.4 Pruebas de cobertura con pytest-cov
- 1.1.5 Pruebas de interfaz con Selenium
- 1.1.6 Pruebas de Carga con Locust
- 1.2 Parte 2: Creamos pruebas para nuestra aplicación UVLHUB
- 1.1 Parte 1: creamos pruebas para una aplicación sencilla
Automatización de Pruebas de Software en una Aplicación Flask
Parte 1: creamos pruebas para una aplicación sencilla
El objetivo de la primera parte de la práctica es que nos familiaricemos con diferentes tipos de pruebas usando una aplicación web desarrollada con Flask. Durante la práctica se implementarán los siguientes tipos de pruebas:
- Pruebas unitarias con
pytest
para comprobar la funcionalidad interna de la aplicación. - Pruebas de cobertura para comprobar si nuestras pruebas tienen una buena cobertura de código.
- Pruebas de interfaz con
Selenium
para simular el comportamiento de un usuario interactuando con la interfaz web. - Pruebas de carga con
Locust
para evaluar el rendimiento de la aplicación bajo diferentes niveles de tráfico.
Requisitos previos
Antes de comenzar, asegúrate de tener instalados los siguientes paquetes y herramientas:
- Python 3
- Flask
-
pytest
para pruebas unitarias. -
pytest-cov
para la cobertura de código. -
Selenium
para pruebas de interfaz. -
Locust
para pruebas de carga. - El navegador chromium y chromium-driver (para Selenium).
Para instalar las dependencias necesarias (¡pero recuerda hacerlo en un entorno virtual!):
$ pip install flask pytest pytest-cov selenium locust
Estructura del proyecto
flask_testing_project/
│
├── app.py # Archivo principal de la aplicación Flask
├── templates/ # Directorio que contiene la plantilla HTML
│ └── tasks.html # Plantilla para mostrar y agregar tareas
├── tests/
│ ├── test_app.py # Pruebas unitarias usando pytest
│ └── test_interfaz.py # Pruebas de interfaz con Selenium
└── locustfile.py # Archivo para pruebas de carga con Locust
Desarrollo de la Aplicación Flask
Vamos a crear una aplicación sencilla que gestione tareas. El usuario podrá ver las tareas en una interfaz web y agregar nuevas tareas utilizando un formulario. Además ofrece una API REST para la visualización y creación de tareas.
Código app.py
:
from flask import Flask, jsonify, request, render_template, redirect, url_for
app = Flask(__name__)
# Lista inicial de tareas (guardada en memoria)
tasks = [
{'id': 1, 'title': 'Comprar pan', 'done': False},
{'id': 2, 'title': 'Estudiar Python', 'done': False}
]
# Ruta para obtener la lista de tareas (versión HTML)
@app.route('/')
def task_list():
return render_template('tasks.html', tasks=tasks)
# Ruta para obtener la lista de tareas en JSON (API)
@app.route('/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
# Ruta para crear una nueva tarea desde un formulario HTML
@app.route('/add_task', methods=['POST'])
def add_task_html():
title = request.form.get('title')
if not title:
return "El título es necesario", 400
task = {
'id': tasks[-1]['id'] + 1 if tasks else 1,
'title': title,
'done': False
}
tasks.append(task)
return redirect(url_for('task_list'))
# Ruta para crear una nueva tarea (API JSON)
@app.route('/tasks', methods=['POST'])
def create_task():
if not request.json or 'title' not in request.json:
return jsonify({'error': 'El título es necesario'}), 400
task = {
'id': tasks[-1]['id'] + 1 if tasks else 1,
'title': request.json['title'],
'done': False
}
tasks.append(task)
return jsonify(task), 201
if __name__ == '__main__':
app.run(debug=True)
Como puedes ver en el código, por tanto, se ofrecen dos formas para interactuar con las tareas:
- Una página HTML que muestra la lista de tareas y un formulario para añadir nuevas tareas.
- Una API REST que devuelve la lista de tareas en formato JSON y permite agregar nuevas tareas mediante solicitudes POST.
Plantilla HTML
La plantilla tasks.html
es la encargada de mostrar las tareas y proporcionar un formulario para agregar nuevas tareas.
Archivo templates/tasks.html
:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gestor de Tareas</title>
</head>
<body>
<h1>Gestor de Tareas</h1>
<!-- Formulario para añadir una nueva tarea -->
<form action="{{ url_for('add_task_html') }}" method="POST">
<input type="text" name="title" placeholder="Añadir nueva tarea">
<button type="submit">Añadir tarea</button>
</form>
<!-- Lista de tareas -->
<h2>Lista de Tareas:</h2>
<ul>
{% for task in tasks %}
<li>{{ task.title }} {% if task.done %} (completada) {% endif %}</li>
{% endfor %}
</ul>
</body>
</html>
Lanza la aplicación
Veamos la aplicación en acción:
$ python3 app.py
Interactúa con ella creando y visualizando las tareas usando primero el formulario web y luego también mediante la API:
$ curl -X POST http://127.0.0.1:5000/tasks -H "Content-Type: application/json" \
-d '{"title": "Leer documentación de github actions"}'
$ curl http://127.0.0.1:5000/tasks
Pruebas Unitarias con pytest
Las pruebas unitarias se centrarán en probar los endpoints de la API de manera independiente.
Archivo tests/test_app.py
:
import pytest
from app import app
@pytest.fixture
def client():
with app.test_client() as client:
yield client
def test_get_tasks(client):
response = client.get('/tasks')
assert response.status_code == 200
assert 'Comprar pan' in response.get_data(as_text=True)
def test_create_task(client):
response = client.post('/tasks', json={'title': 'Aprender testing'})
assert response.status_code == 201
assert 'Aprender testing' in response.get_data(as_text=True)
def test_create_task_without_title(client):
response = client.post('/tasks', json={})
assert response.status_code == 400
data = response.get_json()
assert data['error'] == 'El título es necesario'
def test_task_list_updates(client):
response = client.post('/tasks', json={'title': 'Otra nueva tarea'})
assert response.status_code == 201
response = client.get('/tasks')
assert 'Otra nueva tarea' in response.get_data(as_text=True)
Lee el código de las pruebas e intenta deducir qué hace cada línea. En base a las pruebas manuales que has realizado antes, ¿crees que todas estas pruebas van a funcionar bien?
Ejecución de las pruebas:
$ pytest
Comprueba los resultados obtenidos. ¿Coinciden con lo que estabas esperando?
NOTA: Si recibes un error al lanzar pytest porque no se encuentra el módulo app, puedes intentarlo así (lo que añade el directorio actual (.) al PYTHONPATH):
$ PYTHONPATH=. pytest
Pruebas de cobertura con pytest-cov
Para asegurarnos de que nuestras pruebas unitarias tienen una buena cobertura de código, vamos a utilizar pytest-cov
, una herramienta que extiende pytest
para generar un informe sobre qué porcentaje del código ha sido cubierto por las pruebas.
Y, ¿qué es la cobertura de código?
La cobertura de código mide el porcentaje de código ejecutado cuando se lanzan las pruebas. Esto nos ayuda a identificar las áreas de la aplicación que no están siendo probadas adecuadamente.
Medir la cobertura de las pruebas con pytest-cov
$ pytest --cov=app tests/
Este comando ejecutará todas las pruebas en la carpeta tests/ y generará un informe de cobertura que mostrará qué porcentaje del código en el archivo app.py fue ejecutado durante las pruebas.
Tras ejecutar la orden anterior deberías ver una salida del estilo de la siguiente:
------- coverage: xxx% ------- Name | Stmts | Miss | Cover -----------| ------|------|------ app.py | 26 | 8 | 69% TOTAL | 26 | 8 | 69%
Donde 'Stmts' indica el número total de sentencias en el archivo app.py; 'Miss' indica el número de sentencias que no fueron ejecutadas por las pruebas; y 'Cover' el porcentaje de cobertura de las pruebas sobre el archivo app.py.
También se puede obtener un informe más detallado con:
$ pytest --cov=app --cov-report=html tests/
Esta orden generará un informe HTML con detalles de qué líneas de código fueron ejecutadas durante las pruebas y cuáles no. El informe se generará en una carpeta llamada htmlcov/.
Para visualizar el informe, abre el archivo htmlcov/index.html en tu navegador:
$ xdg-open htmlcov/index.html
Con este informe podrás ver función a función cuál es su cobertura, y te permitirá identificar nuevas pruebas que añadir para verificar estos casos no cubiertos.
Pruebas de interfaz con Selenium
Estas pruebas simulan la interacción de un usuario con la interfaz web de la aplicación.
Un pasito previo para ver Selenium
en acción
Antes de realizar las pruebas sobre nuestra aplicación vamos a hacer un pasito previo para comprobar que tenemos chromium y chromium-driver correctamente instalados y entender el tipo de cosas que podemos hacer con Selenium
.
Archivo tests/test_selenium.py
:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Configurar Selenium para usar Chromium
options = webdriver.ChromeOptions()
# Quita '--headless' para ejecutar el navegador de manera visible
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
# Iniciar el driver de Chromium
driver = webdriver.Chrome(options=options)
try:
# 1. Abrir Google
driver.get("https://www.google.com")
# 2. Esperar hasta que aparezca la ventana de cookies y hacer clic en "Rechazar todo"
reject_cookies_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//button[contains(., 'Rechazar todo')]"))
)
reject_cookies_button.click()
# 3. Esperar hasta que el campo de búsqueda sea visible
search_box = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.NAME, "q"))
)
# 4. Escribir "Selenium" en la barra de búsqueda y enviar el formulario
search_box.send_keys("Selenium")
search_box.send_keys(Keys.RETURN)
# 5. Esperar a que el título cambie y contenga "Selenium"
WebDriverWait(driver, 10).until(EC.title_contains("Selenium"))
print("¡Selenium está funcionando correctamente con Chromium!")
except Exception as e:
print(f"Error: {e}")
driver.save_screenshot("error_screenshot.png")
print("Captura de pantalla guardada como 'error_screenshot.png'")
finally:
# Cerrar el navegador
driver.quit()
¿Qué crees que va a ocurrir cuando ejecutemos esta prueba?
Pues vamos a lanzarala y comprobemos qué ocurre:
$ pytest -s tests/test_selenium.py
¿Has visto cómo se ha lanzado el navegador y ha ido realizando los pasos indicados en el archivo tests/test_selenium.py
? Pues ya tenemos todo listo para realizar las pruebas sobre nuestra aplicación.
Archivo tests/test_interfaz.py
:
Ahora sí, vamos a crear las pruebas de la vista para nuestra aplicación.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import pytest
@pytest.fixture
def driver():
print("Iniciando el navegador Chromium...")
driver = webdriver.Chrome()
yield driver
print("Cerrando el navegador Chromium...")
driver.quit()
def test_add_task(driver):
print("Abriendo la aplicación web en localhost:5000...")
driver.get("http://localhost:5000")
print("Verificando que el título de la página es correcto...")
assert "Gestor de Tareas" in driver.title
print("Buscando el campo de entrada de nueva tarea...")
input_field = driver.find_element(By.NAME, "title")
print("Escribiendo 'Tarea de Selenium' en el campo de entrada...")
input_field.send_keys("Tarea de Selenium")
input_field.send_keys(Keys.RETURN)
print("Verificando que 'Tarea de Selenium' aparece en la lista de tareas...")
assert "Tarea de Selenium" in driver.page_source
Lee el código de las pruebas e intenta deducir qué hace cada línea. En base a las pruebas manuales que has realizado antes, ¿crees que todas estas pruebas van a funcionar bien?
Ejecución de las pruebas de interfaz:
$ pytest -s tests/test_interfaz.py
Comprueba los resultados obtenidos. ¿Coinciden con lo que estabas esperando?
Selenium IDE
Y puede que estés pensando "sí, vale, las pruebas han funcionado como esperaba... pero si tuviera que escribir yo la prueba me costaría mucho trabajo".
Y es cierto, pero afortunadamente existe Selenium IDE
, que es una herramienta fácil de usar que nos permite grabar interacciones en el navegador y convertirlas en scripts de prueba que pueden ser ejecutados automáticamente.
Instalar Selenium IDE
Selenium IDE está disponible como una extensión para los navegadores Firefox y Chrome. Una vez instalada la extensión, haz clic en el ícono de Selenium IDE
en la barra de herramientas del navegador para abrirla.
Grabar una prueba con Selenium IDE
Iniciar una nueva grabación:
- Abre
Selenium IDE
.
- Selecciona
Create a new project
y dale un nombre a tu proyecto, por ejemplo, PruebasFlaskInterfaz.
- Introduce la URL de la aplicación Flask en ejecución.
Grabar la interacción:
- Haz clic en el botón de grabación en
Selenium IDE
.
- Acción 1: Abre la página principal de la aplicación Flask.
- Acción 2: En el formulario de tareas, escribe una nueva tarea, por ejemplo, "Tarea de Selenium IDE".
- Acción 3: Haz clic en el botón para añadir la tarea.
- Acción 4: Verifica que la nueva tarea aparece en la lista.
- Detén la grabación una vez que hayas completado estos pasos.
Guardar la prueba en Selenium IDE
.
Ejecutar la prueba grabada
En Selenium IDE
, selecciona la prueba grabada y haz clic en Run current test
.
Observa cómo Selenium IDE
reproduce automáticamente todas las acciones que realizaste durante la grabación (navegar, escribir en el formulario, etc.).
Exportar el test a código Selenium WebDriver
Exportar a Python:
- En
Selenium IDE
, selecciona el menúExport
y eligePython - Selenium WebDriver
.
- Selecciona la carpeta de pruebas y guárdalo como test_selenium_ide.py.
Ejecutar el test exportado:
Y ya puedes ejecutar el test exportado utilizando pytest:
$ pytest tests/test_selenium_ide.py
Esto ejecutará el test generado por Selenium IDE
en tu navegador usando Selenium WebDriver
.
Pruebas de Carga con Locust
Locust simulará múltiples usuarios accediendo a la aplicación simultáneamente, realizando operaciones como cargar la lista de tareas y agregar nuevas tareas.
Archivo locustfile.py
:
from locust import HttpUser, task, between
class WebsiteTestUser(HttpUser):
wait_time = between(1, 5)
@task(2)
def load_tasks(self):
print("Cargando la lista de tareas...")
response = self.client.get("/tasks")
if response.status_code == 200:
print("Lista de tareas cargada correctamente.")
else:
print(f"Error al cargar la lista de tareas: {response.status_code}")
@task(1)
def create_task(self):
print("Creando una nueva tarea...")
response = self.client.post("/tasks", json={"title": "Tarea generada por Locust"})
if response.status_code == 201:
print("Tarea creada correctamente.")
else:
print(f"Error al crear la tarea: {response.status_code}")
Ejecución de Locust
- Inicia la aplicación Flask si no estaba en ejecución:
$ python app.py
- Inicia Locust:
$ locust -f locustfile.py
- Abre la interfaz de Locust en tu navegador (
http://localhost:8089
) y configura el número de usuarios -por ejemplo, 10-, la tasa de generación (cada cuánto tiempo se lanza un nuevo usuario)-por ejemplo, 1- y el host sobre el que realizar las pruebas (http://localhost:5000
). Luego, inicia la prueba.
- En la terminal verás mensajes como estos hasta que se haya lanzado el número de clientes indicado:
Cargando la lista de tareas... Lista de tareas cargada correctamente. Creando una nueva tarea... Tarea creada correctamente. Creando una nueva tarea... Tarea creada correctamente. Cargando la lista de tareas... Lista de tareas cargada correctamente. Creando una nueva tarea... Tarea creada correctamente. Creando una nueva tarea... Tarea creada correctamente. [2024-10-07 17:35:02,798] hostname/INFO/locust.runners: All users spawned: {"WebsiteTestUser": 10} (10 total users)
Y además en la interfaz de Locust en tu navegador (http://localhost:8089
) puedes navegar por un informe interactivo con los resultados.
¿Cómo han ido las pruebas? ¿Ha aguantado el sistema esta carga?
Parte 2: Creamos pruebas para nuestra aplicación UVLHUB
Con lo que hemos aprendido hasta ahora, ya tenemos las herramientas necesarias para poder diseñar pruebas automatizadas para nuestra aplicación UVLHUB. Por ejemplo, ¿qué tipo de pruebas podrías desarrollar para probar la nueva funcionalidad de notepads que creaste en la primera práctica del curso?
No obstante, antes de lanzarte a ello quizá pueda ser buena idea echar un ojo a las pruebas ya existentes en el repositorio, que te pueden dar pistas, ideas y estrategias para diseñar las tuyas. Y al hacerlo verás que en UVLHUB se usa rosemary
, que facilita todavía más las tareas de testing: https://docs.uvlhub.io/rosemary/testing
.
Pero no te agobies por tener que aprender ahora algo nuevo como rosemary
, ya que si echas un ojo al código del repositorio vas a ver que, en realidad, para lanzar las pruebas rosemary
hace llamadas a pytest
. Su uso es totalmente opcional, aunque es cierto nos hace la vida un poquito más sencilla.
¡Mucho ánimo!