Código Simple en Java QA: Enseña Más, Confunde Menos”

Código Simple en Java QA: Enseña Más, Confunde Menos
¿Alguna vez escribiste código en Java, lo mostraste a tu equipo y alguien preguntó
"¿y eso qué hace?" señalando una sola línea que para ti era obvia?
Eso pasa más de lo que creemos en automatización QA. Llevamos las herramientas al máximo,
usamos lambdas, streams y method chaining porque el IDE no protesta… y terminamos con
código que funciona perfecto pero que nadie más puede mantener.
En el stack SDTE trabajamos con equipos mixtos: hay Juniors que llevan 6 meses con Java
y Semi-seniors que ya tienen criterio propio. Si el código que generas o enseñas solo lo
entiende el nivel más alto, estás fallando al nivel más bajo — y eso cuesta caro en
onboarding, revisiones de código y bugs nocturnos.
La regla que adoptamos es simple: simple por defecto, avanzado solo si se pide.
Al final de este artículo encontrarás el Gancho Práctico con los ejemplos comparativos
listos para copiar en Selenium, Appium y Karate DSL.

¿Por Qué el Código “Elegante” Puede Ser un Problema Pedagógico?

No hay nada malo con las lambdas o los streams. El problema es usarlos como default cuando tu objetivo es enseñar, no impresionar.

Cuando un Junior ve esto por primera vez:

java

List<WebElement> visibles = driver.findElements(By.tagName("a"))
    .stream()
    .filter(WebElement::isDisplayed)
    .collect(Collectors.toList());

Necesita entender simultáneamente: streams, method references, generics y la API de Selenium. Son cuatro conceptos a la vez. Si falla, no sabe en cuál de los cuatro está el error.

La versión simple separa esos conceptos en pasos que se pueden leer de arriba hacia abajo, depurar línea por línea y explicar en clase sin saltarse nada.

La simplicidad no es lo opuesto de la calidad. Es una decisión pedagógica consciente.


Los Dos Niveles de Generación SDTE

En el stack SDTE definimos dos niveles de escritura de código:

NivelCuándo usarCaracterísticas
NIVEL 1 — DEFAULTSiempre, salvo que se pida otra cosafor clásico, if/else explícito, variables intermedias con nombres descriptivos
NIVEL 2 — AVANZADOSolo si el usuario pide: “versión avanzada”, “refactoriza”, “usa streams”Streams, lambdas, method chaining, patrones funcionales

Cinco reglas concretas del Nivel 1:

  1. Bucles: for clásico antes que stream() / forEach con lambda
  2. Condicionales: if/else explícito antes que operador ternario encadenado
  3. Variables: intermedias con nombres descriptivos, sin encadenar métodos
  4. Métodos: cortos y nombrados, sin lógica inline larga
  5. Comentarios: breve en líneas no obvias — el qué y el por qué

Selenium WebDriver: For vs Stream

El ejemplo más común en proyectos reales: filtrar una lista de elementos visibles.

❌ Nivel 2 — Funciona, pero confunde al Junior

java

// Capa: web-selenium | Patrón: sin separación de pasos
// Principio SOLID: funciona, pero sacrifica legibilidad
List<WebElement> visibles = driver.findElements(By.tagName("a"))
    .stream()
    .filter(WebElement::isDisplayed)
    .collect(Collectors.toList());

✅ Nivel 1 DEFAULT — Simple y depurable

java

// ============================================
// Filtrar elementos visibles — Selenium 4
// Capa    : web-selenium
// Patrón  : Page Object (este código va dentro de un método de página)
// Principio SOLID: S — cada método hace una sola cosa
// Nivel   : Junior / Mixto
// ============================================

// Paso 1: obtener TODOS los enlaces de la página
List<WebElement> todosLosEnlaces = driver.findElements(By.tagName("a"));

// Paso 2: preparar la lista donde guardaremos solo los visibles
List<WebElement> enlacesVisibles = new ArrayList<>();

// Paso 3: recorrer cada enlace y filtrar los que están visibles en pantalla
for (WebElement enlace : todosLosEnlaces) {
    if (enlace.isDisplayed()) {          // solo agregar si el usuario lo puede ver
        enlacesVisibles.add(enlace);
    }
}

// Resultado: enlacesVisibles contiene solo los que están en pantalla
System.out.println("Enlaces visibles encontrados: " + enlacesVisibles.size());

Y el mismo principio aplica para interactuar con un elemento con espera explícita:

❌ Nivel 2 — Una sola línea, difícil de depurar

java

wait.until(ExpectedConditions.elementToBeClickable(By.id("btn-login"))).click();

✅ Nivel 1 DEFAULT — Tres pasos claros

java

// ============================================
// Clic con espera explícita — Selenium 4
// Capa    : web-selenium
// Principio SOLID: S — cada variable tiene un propósito claro
// ============================================

// Paso 1: definir el locator del botón (separado para reusar si hace falta)
By localizadorBoton = By.id("btn-login");

// Paso 2: esperar hasta que el botón sea clickeable (máximo 10 segundos)
WebElement botonLogin = wait.until(
    ExpectedConditions.elementToBeClickable(localizadorBoton)
);

// Paso 3: hacer clic ahora que sabemos que el botón está listo
botonLogin.click();

💡 Versión avanzada disponible: si quieres ver esta misma lógica con method chaining en una sola línea, pídemela.


Appium Mobile: Buscar en Lista y Condicional de Plataforma

En Mobile los mismos principios aplican. Dos casos frecuentes:

Buscar un elemento dentro de una lista de TextViews

❌ Nivel 2 — Stream + Optional encadenado

java

driver.findElements(By.xpath("//android.widget.TextView"))
      .stream()
      .filter(e -> e.getText().contains("Iniciar sesión"))
      .findFirst()
      .ifPresent(WebElement::click);

✅ Nivel 1 DEFAULT — For + break

java

// ============================================
// Buscar y hacer clic en texto de lista — Appium 2
// Capa    : mobile-appium
// Patrón  : Screen Object (este método va en una Screen class)
// Principio SOLID: S — método busca Y hace clic en un solo elemento
// ============================================

// Paso 1: obtener todos los textos visibles en la pantalla actual
List<WebElement> textos = driver.findElements(
    By.xpath("//android.widget.TextView")
);

// Paso 2: recorrer hasta encontrar el que necesitamos
for (WebElement texto : textos) {
    if (texto.getText().contains("Iniciar sesión")) {
        texto.click();           // encontrado: hacer clic
        break;                   // salir del bucle, ya no necesitamos seguir buscando
    }
}

Detectar plataforma (Android vs iOS)

❌ Nivel 2 — Ternario encadenado ilegible

java

String locator = plataforma.equals("Android") ? "//android.widget.Button"
               : plataforma.equals("iOS")     ? "//XCUIElementTypeButton"
               : "//button";

✅ Nivel 1 DEFAULT — If/else explícito

java

// ============================================
// Seleccionar locator según plataforma — Appium 2
// Capa    : mobile-appium
// Patrón  : Strategy (locator cambia según la plataforma activa)
// Principio SOLID: O — agregar nueva plataforma = agregar else if, sin tocar lógica existente
// ============================================

String locator;   // declarar primero, asignar según plataforma

if (plataforma.equals("Android")) {
    locator = "//android.widget.Button";      // locator nativo Android

} else if (plataforma.equals("iOS")) {
    locator = "//XCUIElementTypeButton";      // locator nativo iOS

} else {
    locator = "//button";                     // fallback web/híbrido
}

// Usar el locator ya seleccionado
WebElement boton = driver.findElement(By.xpath(locator));
boton.click();

💡 Versión avanzada disponible: si quieres ver esto con un Map<String, String> o con el patrón Strategy completo, pídemela.


Karate DSL: Pasos Separados vs JavaScript Inline

Karate tiene su propio lenguaje, pero el principio es el mismo: una acción por línea, variables con nombre descriptivo.

Configurar headers de autenticación

❌ Nivel 2 — Todo inline en dos líneas

gherkin

* def token = karate.call('classpath:auth.feature')['access_token']
* configure headers = { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }

✅ Nivel 1 DEFAULT — Cuatro pasos nombrados

gherkin

# ============================================
# Configurar headers de autenticación — Karate DSL
# Capa   : api-karate
# Patrón : Template Method (Background ejecuta esto antes de cada Scenario)
# SOLID  : S — Background solo configura, no valida negocio
# ============================================

# Paso 1: llamar al feature de login y guardar el resultado completo
* def resultadoAuth = karate.call('classpath:features/auth/login.feature')

# Paso 2: extraer solo el token del resultado (hace el código más legible)
* def accessToken = resultadoAuth['access_token']

# Paso 3: construir el valor del header Authorization
* def headerAuth = 'Bearer ' + accessToken

# Paso 4: configurar los headers para todas las llamadas siguientes
* configure headers = { Authorization: headerAuth, 'Content-Type': 'application/json' }

Validar un campo específico del response

❌ Nivel 2 — JsonPath inline complejo

gherkin

* match response.data.users[?(@.activo == true)].length() == 3

✅ Nivel 1 DEFAULT — Extraer, luego validar

gherkin

# Paso 1: extraer la lista de usuarios activos en una variable con nombre claro
* def usuariosActivos = response.data.users[?(@.activo == true)]

# Paso 2: validar la cantidad — ahora la intención es obvia al leer
* match usuariosActivos.length() == 3

💡 Versión avanzada disponible: si quieres ver la validación en una sola expresión JsonPath compacta, pídemela.


🔧 Gancho Práctico — Plantilla Base: Simple vs Avanzado

Aquí tienes la plantilla completa para aplicar esta regla en cualquier componente del stack SDTE. Cópiala y úsala como punto de partida:

java

// ============================================
// [NOMBRE DE TU COMPONENTE]
// Capa    : [web-selenium | mobile-appium | framework-testng]
// Patrón  : [nombre del patrón]
// Principio SOLID: [letra] — [nombre]
// Nivel   : Mixto (Junior → Semi-senior)
// ============================================

// --- NIVEL 1 DEFAULT (siempre entregar esto primero) ---

// Paso 1: [acción clara con variable de nombre descriptivo]
TipoDato nombreDescriptivo = operacion();

// Paso 2: [siguiente acción — una sola cosa por paso]
if (condicion) {
    // rama verdadera — explicar por qué
    hacerAlgo(nombreDescriptivo);
} else {
    // rama alternativa — explicar por qué
    hacerOtraCosa();
}

// Paso 3: [resultado o verificación]
System.out.println("Resultado: " + nombreDescriptivo);

// --- NOTA AL FINAL (incluir siempre que exista versión avanzada) ---
// 💡 Versión avanzada disponible: si quieres ver esta misma lógica
//    con [streams / lambdas / method chaining], pídemela.

🚀 Tu Turno — 3 Niveles de Práctica

Nivel 1 — Ejecuta (5 min) Toma uno de los snippets de Selenium de este artículo, agrégalo a un Page Object existente en tu proyecto y ejecútalo. Verifica que el output en consola muestra el número correcto de elementos visibles.

Objetivo: confirmar que el entorno funciona y entender QUÉ hace cada paso

Nivel 2 — Modifica (15 min) Cambia el locator By.tagName("a") por By.cssSelector(".nav-item") y adapta el comentario de cada paso para reflejar el nuevo contexto. Agrega un System.out.println dentro del for que imprima el texto de cada elemento visible.

Objetivo: entender CÓMO funciona el bucle y la condición de filtro

Nivel 3 — Extiende (30+ min) Toma el ejemplo de Karate DSL y agrega un Scenario Outline con una tabla de 3 usuarios distintos, validando que cada uno recibe su token correctamente. Usa los pasos separados (Nivel 1) para que cualquier miembro del equipo pueda leer el feature sin conocer Karate.

Objetivo: construir un flujo BDD + autenticación legible para QA funcional y técnico


¿Llegaste al Nivel 3? Comparte tu feature o tu Page Object en los comentarios 👇


Conclusión

Escribir código simple no es escribir código malo. Es una decisión de diseño que pone a las personas antes que a la sintaxis.

Tres puntos clave para llevarte hoy:

  • Simple por defecto: for, if/else y variables con nombre descriptivo deben ser tu primera opción en un equipo mixto
  • Avanzado bajo pedido: lambdas, streams y ternarios encadenados tienen su lugar, pero ese lugar lo define quien lee el código, no quien lo escribe
  • La nota al final: incluir siempre “💡 Versión avanzada disponible si la pides” le da al Semi-senior la puerta de salida sin dejar al Junior atrás

¿Qué parte de tu stack SDTE implementarías con esta regla primero: Selenium, Appium o Karate?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *