¿Qué es el data flow testing?

El data flow testing se enfoca en el ciclo de vida de las variables: dónde son definidas (se les asigna un valor), dónde son usadas (leídas) y dónde son eliminadas (salen de scope o son reasignadas). Al rastrear estos eventos a lo largo de los caminos de ejecución, el data flow testing revela defectos que otras técnicas no detectan.

Mientras el control flow testing pregunta “¿qué caminos toma el código?”, el data flow testing pregunta “¿qué pasa con los datos a lo largo de esos caminos?”

Estados de variables: Define, Use, Kill

Cada variable pasa por tres estados:

Define (d): La variable recibe un valor.

total = 0           # definición de total
user = get_user()   # definición de user

Use (u): El valor de la variable es leído. Dos tipos:

  • c-use (uso computacional): Valor usado en un cálculo: result = total * tax_rate
  • p-use (uso en predicado): Valor usado en una condición: if total > 100:

Kill (k): La variable deja de existir o es redefinida.

Anomalías de flujo de datos

Las anomalías son patrones sospechosos que frecuentemente indican bugs:

Anomalía dd (define-define)

Una variable es definida dos veces sin ser usada entre definiciones.

price = get_base_price()     # define
price = get_sale_price()     # define de nuevo — primera definición desperdiciada
discount = price * 0.1       # use

Anomalía ur (uso sin definición)

Una variable es usada antes de ser definida.

def calculate_total():
    total = total + tax    # BUG: total usada antes de definición
    return total

Anomalía du (definición sin uso)

Una variable es definida pero nunca usada.

def process():
    result = expensive_computation()  # define
    return "done"                     # result nunca usada

Pares Define-Use (pares DU)

Un par DU es un par (d, u) donde:

  • d es una sentencia donde la variable v es definida
  • u es una sentencia donde la variable v es usada
  • Existe al menos un camino de d a u que no redefine v (un camino libre de definición)

Ejemplo

def process_payment(amount, discount_code):
    price = amount                    # Línea 1: define price

    if discount_code == "SAVE10":     # Línea 2: use discount_code (p-use)
        discount = 0.10               # Línea 3: define discount
    elif discount_code == "SAVE20":   # Línea 4: use discount_code (p-use)
        discount = 0.20               # Línea 5: define discount
    else:
        discount = 0                  # Línea 6: define discount

    final = price * (1 - discount)    # Línea 7: use price, use discount (c-use)
    return final                      # Línea 8: use final (c-use)

Pares DU para discount: (3, 7), (5, 7), (6, 7)

Criterios de cobertura de flujo de datos

De más débil a más fuerte:

Cobertura All-Defs

Para cada definición de variable, al menos un par DU desde esa definición es cubierto.

Cobertura All-Uses

Para cada definición de variable, cada uso alcanzable es cubierto.

Cobertura All-DU-Paths

Para cada par DU, cada camino libre de definición entre la definición y el uso es cubierto.

Bugs comunes de flujo de datos

Null pointer por definición condicional:

if condition:
    connection = create_connection()
# BUG: connection indefinida si condition es False
connection.execute(query)

Ejercicio: Análisis de flujo de datos

Problema 1

Identifica todos los pares DU y anomalías en esta función:

def calculate_grade(scores, curve):
    total = 0                        # Línea 1
    count = 0                        # Línea 2
    average = 0                      # Línea 3

    for score in scores:             # Línea 4
        total = total + score        # Línea 5
        count = count + 1            # Línea 6

    if count > 0:                    # Línea 7
        average = total / count      # Línea 8

    average = average + curve        # Línea 9

    if average >= 90:                # Línea 10
        grade = "A"                  # Línea 11
    elif average >= 80:              # Línea 12
        grade = "B"                  # Línea 13
    else:
        grade = "C"                  # Línea 14

    return grade                     # Línea 15
Solución

Pares DU principales:

  • total: (1, 5-use), (5, 5-use), (5, 8)
  • count: (2, 6-use), (6, 6-use), (6, 7), (6, 8)
  • average: (3, 9) si count==0, (8, 9) si count>0
  • grade: (11, 15), (13, 15), (14, 15)

Anomalía: Línea 3 define average = 0. Si scores está vacío, count=0, línea 8 se salta. Línea 9 usa el average = 0 inicial, resultando en grade = curve. Posible bug lógico.

Test cases para all-uses:

#scorescurveCubre
1[85, 95]5Loop ejecuta, count>0, average calculado, >=90
2[70, 80]0Loop ejecuta, 80<=avg<90
3[50, 60]0Loop ejecuta, avg<80
4[]10Scores vacío, camino count=0

Problema 2

Encuentra y corrige bugs de flujo de datos:

def process_order(items, coupon):
    subtotal = 0
    shipping = 0

    for item in items:
        subtotal += item.price * item.quantity

    if subtotal > 50:
        shipping = 0

    if coupon:
        discount = subtotal * coupon.percent / 100

    total = subtotal - discount + shipping
    return total
Solución

Bug 1: anomalía ur — discount usada antes de definición. Si coupon es falsy, discount nunca se define. Fix: inicializar discount = 0.

Bug 2: anomalía dd — shipping siempre 0. Fix: agregar un valor de shipping para pedidos pequeños.

def process_order(items, coupon):
    subtotal = 0
    discount = 0

    for item in items:
        subtotal += item.price * item.quantity

    if subtotal > 50:
        shipping = 0
    else:
        shipping = 9.99

    if coupon:
        discount = subtotal * coupon.percent / 100

    total = subtotal - discount + shipping
    return total

Herramientas para análisis de flujo de datos

  • SonarQube — Detecta código muerto, variables no usadas, riesgos de null pointer
  • SpotBugs (Java) — Encuentra lecturas no inicializadas
  • Pylint/Pyflakes (Python) — Reporta variables no usadas, nombres indefinidos
  • ESLint (JavaScript) — Reglas no-unused-vars, no-undef
  • Coverity — Herramienta comercial con análisis avanzado

Puntos clave

  • El data flow testing rastrea variables a través del ciclo define → use → kill
  • Los pares DU conectan definiciones con usos a lo largo de caminos libres de definición
  • Tres niveles de cobertura: all-defs (más débil), all-uses, all-du-paths (más fuerte)
  • Las anomalías (dd, ur, du) frecuentemente indican bugs reales
  • Bug más común: variable usada en solo un branch de un condicional
  • Las herramientas de análisis estático automatizan la detección de anomalías
  • Aplica pensamiento de flujo de datos durante code review incluso sin herramientas formales