El Capítulo 4 del syllabus ISTQB es el más importante del examen. Punto. Tiene la mayor cantidad de preguntas y cubre las técnicas que usas (o deberías usar) todos los días.

El problema es que el syllabus las explica así:

“La partición de equivalencia divide los datos en particiones de tal manera que todos los miembros de una partición se espera que sean procesados de la misma manera.”

¿Lo entendiste? Yo tampoco la primera vez.

Aquí vas a aprender estas técnicas de otra forma: escribiendo tests reales. Cuando termines, vas a poder responder las preguntas del examen Y aplicar las técnicas en tu trabajo. Las dos cosas.

Serie ISTQB con código

Esta es la Parte 1 de 3. Cubrimos partición de equivalencia y valores límite — las dos técnicas más preguntadas del examen. En la Parte 2 veremos tablas de decisión y transición de estados. En la Parte 3, un caso práctico E2E completo aplicando las cuatro.

¿Usas Selenium, Cypress u otro framework?

Los ejemplos de código están en Playwright con TypeScript, pero las técnicas de diseño de pruebas son universales. Lo que importa es cómo diseñas los datos de test — las particiones, los valores límite, la estructura. Eso es idéntico sin importar tu herramienta. Adaptar el código a Selenium, Cypress, TestCafe o cualquier otro framework es cambiar las líneas de interacción con el navegador, no la lógica de los tests.


Partición de equivalencia

Qué dice el syllabus (en 3 líneas)

Divides las entradas posibles en grupos (particiones) donde todos los valores del grupo se comportan igual. Pruebas al menos un valor de cada partición. Si un valor del grupo funciona, asumes que todos los demás del mismo grupo también.

El bug que lo explica

En un proyecto fintech tenía un formulario de transferencias con un campo “monto”. Los requisitos decían:

  • Monto mínimo: $1
  • Monto máximo: $10,000
  • No se permiten montos negativos ni cero

El equipo de desarrollo escribió la validación. Yo probé con $100, $500 y $5,000. Todo verde. Pasó a producción.

Una semana después, un usuario transfirió $0.50 y el sistema lo aceptó. Después otro transfirió -$200 — y el saldo del destinatario disminuyó.

¿Qué pasó? Probé tres valores que pertenecían a la misma partición (montos válidos entre $1 y $10,000). Nunca probé las otras particiones: valores inválidos por debajo, por encima, negativos, cero.

Las particiones de este caso

Partición inválida: valores negativos (-200, -1, -0.01)
Partición inválida: cero (0)
Partición inválida: entre 0 y 1 (0.50, 0.99)
Partición válida: entre 1 y 10,000 (1, 100, 5000, 10000)
Partición inválida: mayor a 10,000 (10001, 50000)

Con un valor de cada partición, habría detectado el bug antes de que llegara a producción.

El diseño de los datos (esto es lo que importa)

Antes de escribir una sola línea de automatización, diseñas tus datos de prueba. Esta estructura funciona igual sin importar tu framework:

// Datos de prueba — diseño de particiones
// Esto es ISTQB puro. No depende de ninguna herramienta.
const particiones = {
válidas: [
{ monto: "1", descripción: "monto mínimo permitido" },
{ monto: "5000", descripción: "monto medio" },
{ monto: "10000", descripción: "monto máximo permitido" },
],
inválidas: [
{
monto: "-200",
descripción: "negativo",
error: "El monto debe ser mayor a 0",
},
{ monto: "0", descripción: "cero", error: "El monto debe ser mayor a 0" },
{
monto: "0.50",
descripción: "menor al mínimo",
error: "El monto mínimo es $1",
},
{
monto: "10001",
descripción: "mayor al máximo",
error: "El monto máximo es $10,000",
},
{
monto: "999999",
descripción: "valor extremo",
error: "El monto máximo es $10,000",
},
],
};

Fíjate: 5 particiones, 8 valores de prueba. Un valor por cada partición inválida, tres por la válida (para cubrir inicio, medio y fin del rango). Este diseño es idéntico uses Playwright, Selenium, Cypress o cualquier otra herramienta.

En código (ejemplo con Playwright)

import { test, expect } from "@playwright/test";
test.describe(
"Transferencias — partición de equivalencia",
{
tag: "@istqb",
},
() => {
// Tests para la partición válida
particiones.válidas.forEach(({ monto, descripción }) => {
test(`acepta monto válido: ${descripción} ($${monto})`, async ({
page,
}) => {
await page.goto("/transferencias");
await page.getByLabel("Monto").fill(monto);
await page.getByRole("button", { name: "Transferir" }).click();
await expect(page.getByText("Transferencia exitosa")).toBeVisible();
});
});
// Tests para las particiones inválidas
particiones.inválidas.forEach(({ monto, descripción, error }) => {
test(`rechaza monto inválido: ${descripción} ($${monto})`, async ({
page,
}) => {
await page.goto("/transferencias");
await page.getByLabel("Monto").fill(monto);
await page.getByRole("button", { name: "Transferir" }).click();
await expect(page.getByText(error)).toBeVisible();
});
});
},
);

El mismo array particiones funciona con Selenium (driver.findElement), con Cypress (cy.get), o con cualquier framework. Lo que cambia son las 4-5 líneas que interactúan con el navegador. El diseño — las particiones, los valores, la estructura — es exactamente el mismo.

Lo que el examen te va a preguntar

El examen no te pide código — te pide identificar particiones. Te dan un escenario y te preguntan cuántas particiones hay, o cuál es el mínimo de tests necesarios.

Regla de oro: el número mínimo de tests = número de particiones. Si tienes 5 particiones, necesitas al menos 5 tests (uno por partición).

Si estás probando 10 valores de la misma partición, estás desperdiciando tiempo. Uno basta.


Valores límite (Boundary Value Analysis)

Qué dice el syllabus (en 3 líneas)

Los bugs se acumulan en los bordes de las particiones, no en el centro. En vez de probar cualquier valor de una partición, pruebas exactamente los valores en los límites: el mínimo, el máximo, uno por debajo del mínimo, y uno por encima del máximo.

El bug que lo explica

Mismo proyecto fintech. El campo de edad para abrir una cuenta decía: “Debe tener entre 18 y 65 años.”

El equipo probó con 25, 30, 45. Todo bien. Yo probé con 17 y 18. Con 17, debía rechazar. Con 18, debía aceptar.

El resultado: ambos fueron aceptados.

El código decía if (edad > 18) en vez de if (edad >= 18). Un error de operador que solo se detecta probando exactamente en el límite.

Si hubiera probado con 20 y 30, nunca lo habría encontrado. El bug vive en el borde.

Los valores límite de este caso

Para el rango 18 a 65:

17 Justo debajo del mín.
18 Límite inferior
65 Límite superior
66 Justo encima del máx.

Esos 4 valores son los que más bugs encuentran. No 25. No 40. Los bordes.

El diseño de los datos

Esta función genera automáticamente los valores límite para cualquier rango. Es puro diseño de pruebas — funciona en cualquier lenguaje y framework:

// Generador de valores límite — reutilizable en cualquier proyecto
function valoresLímite(min: number, max: number) {
return {
bajoMínimo: min - 1, // debe ser rechazado
mínimo: min, // debe ser aceptado (el borde)
máximo: max, // debe ser aceptado (el borde)
sobreMáximo: max + 1, // debe ser rechazado
};
}
const edad = valoresLímite(18, 65);
// Resultado: { bajoMínimo: 17, mínimo: 18, máximo: 65, sobreMáximo: 66 }

Cuatro valores. Cuatro tests. Eso es todo lo que necesitas para encontrar el bug del > vs >=.

En código (ejemplo con Playwright)

import { test, expect } from "@playwright/test";
test.describe(
"Registro — valores límite de edad",
{
tag: "@istqb",
},
() => {
test(`rechaza edad ${edad.bajoMínimo} (bajo el mínimo)`, async ({
page,
}) => {
await page.goto("/registro");
await page.getByLabel("Edad").fill(String(edad.bajoMínimo));
await page.getByRole("button", { name: "Registrarse" }).click();
await expect(
page.getByText("Debes tener al menos 18 años"),
).toBeVisible();
});
test(`acepta edad ${edad.mínimo} (límite inferior)`, async ({ page }) => {
await page.goto("/registro");
await page.getByLabel("Edad").fill(String(edad.mínimo));
await page.getByRole("button", { name: "Registrarse" }).click();
await expect(page.getByText("Registro exitoso")).toBeVisible();
});
test(`acepta edad ${edad.máximo} (límite superior)`, async ({ page }) => {
await page.goto("/registro");
await page.getByLabel("Edad").fill(String(edad.máximo));
await page.getByRole("button", { name: "Registrarse" }).click();
await expect(page.getByText("Registro exitoso")).toBeVisible();
});
test(`rechaza edad ${edad.sobreMáximo} (sobre el máximo)`, async ({
page,
}) => {
await page.goto("/registro");
await page.getByLabel("Edad").fill(String(edad.sobreMáximo));
await page.getByRole("button", { name: "Registrarse" }).click();
await expect(page.getByText("La edad máxima es 65 años")).toBeVisible();
});
},
);
Llévate la función valoresLímite

Esta función es tuya. Cópiala, tradúcela a Java, Python, C# — lo que uses. Funciona con cualquier rango numérico: precios, cantidades, fechas, longitudes de texto. Genera los 4 valores que más bugs encuentran, sin importar el framework.

Lo que el examen te va a preguntar

El examen distingue entre dos variantes:

Valores límite a 2 valores (two-value BVA): Para cada límite, pruebas el valor válido y el inválido. En un rango de 18 a 65: pruebas 17, 18, 65 y 66. Total: 4 tests.

Valores límite a 3 valores (three-value BVA): Para cada límite, pruebas el valor válido, el inválido, y el adyacente válido. En un rango de 18 a 65: pruebas 17, 18, 19, 64, 65 y 66. Total: 6 tests.

2 valores por límite (BVA estándar) — lo más común en el examen y en la práctica
3 valores por límite (BVA extendido) — más exhaustivo, útil en sistemas críticos

Pregunta típica del examen: “Un campo acepta valores entre 1 y 100. ¿Cuántos valores límite se deben probar con BVA de 2 valores?” Respuesta: 4 (0, 1, 100, 101).


Combinándolas: el power duo

Partición de equivalencia y valores límite no compiten — se complementan. La primera te dice qué grupos probar. La segunda te dice dónde probar dentro de cada grupo.

En la práctica, siempre las uso juntas. Primero diseño los datos, después los implemento:

// Diseño de datos: descuento por cantidad de productos
// Reglas:
// 1-9 productos → sin descuento
// 10-49 productos → 10% descuento
// 50+ productos → 25% descuento
const casosDeTest = [
// Partición 1: sin descuento (1-9) + valores límite
{
cantidad: "0",
descuento: null,
válido: false,
descripción: "cero — inválido",
},
{
cantidad: "1",
descuento: "0%",
válido: true,
descripción: "límite inferior sin descuento",
},
{
cantidad: "9",
descuento: "0%",
válido: true,
descripción: "límite superior sin descuento",
},
// Partición 2: 10% descuento (10-49) + valores límite
{
cantidad: "10",
descuento: "10%",
válido: true,
descripción: "límite inferior 10%",
},
{
cantidad: "49",
descuento: "10%",
válido: true,
descripción: "límite superior 10%",
},
// Partición 3: 25% descuento (50+) + valores límite
{
cantidad: "50",
descuento: "25%",
válido: true,
descripción: "límite inferior 25%",
},
{
cantidad: "100",
descuento: "25%",
válido: true,
descripción: "valor alto — 25%",
},
];
// Hasta aquí: diseño puro. Independiente de cualquier herramienta.
// Lo que sigue es la implementación — en este caso con Playwright,
// pero el array casosDeTest funciona igual en Selenium, Cypress o lo que uses.
test.describe(
"Descuento por cantidad — partición + límites",
{
tag: "@istqb",
},
() => {
casosDeTest
.filter((c) => c.válido)
.forEach(({ cantidad, descuento, descripción }) => {
test(`${descripción}: ${cantidad} productos → ${descuento}`, async ({
page,
}) => {
await page.goto("/carrito");
await page.getByLabel("Cantidad").fill(cantidad);
await page.getByRole("button", { name: "Calcular" }).click();
await expect(page.getByTestId("descuento")).toHaveText(descuento!);
});
});
casosDeTest
.filter((c) => !c.válido)
.forEach(({ cantidad, descripción }) => {
test(`${descripción}: ${cantidad} productos → error`, async ({
page,
}) => {
await page.goto("/carrito");
await page.getByLabel("Cantidad").fill(cantidad);
await page.getByRole("button", { name: "Calcular" }).click();
await expect(
page.getByText("La cantidad debe ser al menos 1"),
).toBeVisible();
});
});
},
);

Fíjate en algo: con solo 7 tests cubrimos 3 particiones y todos los valores límite. Sin esta técnica, podrías escribir 50 tests probando valores aleatorios y aún así no encontrar el bug que vive en el borde entre la partición del 10% y la del 25%.

La partición te dice dónde mirar. El valor límite te dice dónde apuntar.


Ejercicio: aplícalo tú

Un formulario de registro tiene estos campos:

CampoReglas
EmailDebe contener @ y un dominio válido
ContraseñaMínimo 8 caracteres, máximo 64
EdadEntre 13 y 120 años
Código postalExactamente 5 dígitos numéricos

Tu tarea:

  1. Identifica las particiones válidas e inválidas de cada campo
  2. Identifica los valores límite de los campos numéricos (edad, longitud de contraseña, código postal)
  3. Diseña el array de datos de prueba con la misma estructura que viste arriba
  4. Impleméntalo en tu framework — Playwright, Selenium, Cypress, el que uses
La solución en la Parte 2

Inténtalo antes de mirar. En la Parte 2 arrancamos con la solución completa de este ejercicio — con las tablas de particiones, los valores límite y el array de datos diseñado.

Truco para el examen

Cuando el examen te dé un escenario como este, no pienses en código — piensa en una tabla. Una columna por campo, una fila por partición. Los valores límite van en los bordes de cada partición. El número mínimo de tests es el número de filas de tu tabla.


Cheat sheet

ConceptoQué haceCuántos tests mínimo
Partición de equivalenciaDivide entradas en grupos que se comportan igual1 por partición
Valores límite (2 valores)Prueba en los bordes de cada partición2 por límite (válido + inválido)
Valores límite (3 valores)Prueba bordes + adyacente válido3 por límite
CombinadasPartición dice dónde mirar, límite dice dónde apuntarParticiones + límites

Lo que viene

En la Parte 2 veremos las otras dos técnicas estrella del Capítulo 4: tablas de decisión y transición de estados. Misma filosofía: primero el bug, después el concepto, y siempre con código que puedes adaptar a tu stack.

Si no quieres perderte la siguiente parte, suscríbete al newsletter. Llega antes que nadie.