Módulo 4: Assumption And Hypothesis Testing
Barato contra fuerte: la evidencia no es binaria
Descripción
pickTest(), tal como quedó en las lecciones 4 y 5, siempre elige el mismo criterio: entre los candidatos que pueden refutar la hipótesis, el más barato. Ese criterio funcionó bien en todos los ejemplos anteriores, pero esconde un supuesto que todavía no pusiste a prueba: que un test barato y uno caro, si ambos tienen canFalsify: true, son igual de confiables. No lo son. canFalsify te dice si un test puede, en principio, devolver un resultado que contradiga la hipótesis. No te dice qué tan convincente sería ese resultado si de verdad ocurre.
Esta lección le agrega al criterio la dimensión que faltaba: la fuerza de la evidencia (strength). Vas a ver un candidato nuevo, tan barato que le gana en precio a todos los demás, con canFalsify: true de forma perfectamente honesta — y sin embargo, es la elección equivocada, porque su evidencia es tan débil que un solo resultado, en cualquier dirección, casi no cambia lo que el equipo debería creer.
Conexión con el módulo. Esta lección amplía pickTest(), agregándole un segundo modo de elección junto al que ya conoces, sin romper el comportamiento anterior — la misma función, con una capacidad nueva, siguiendo el mismo patrón con el que classifyQuestion() creció, lección a lección, en la guía anterior sobre entrevistas. Esta versión ampliada es la que corre, sin más cambios, en el proyecto de la lección 8.
Una analogía: el termómetro que casi siempre marca lo mismo
Imagina dos termómetros. El primero es de precisión: mide la temperatura real, con margen de error mínimo, y cuesta bastante. El segundo es viejo, descalibrado, y casi siempre marca algo cercano a 20°C sin importar la temperatura real del cuarto — a veces sube un par de grados si hace mucho calor, a veces baja un poco si hace mucho frío, pero la mayoría de las lecturas se agrupan cerca del mismo número central, sin importar qué tan caliente o frío esté el cuarto de verdad.
Técnicamente, el termómetro descalibrado puede mostrarte un número distinto de 20°C — no está roto del todo, no siempre marca exactamente lo mismo. Pero si lo usas para decidir si necesitas prender la calefacción, una sola lectura suya no te dice casi nada: el ruido es tan grande comparado con la señal real que cualquier número que muestre se puede explicar tanto por "hace frío de verdad" como por "el termómetro está de mal humor hoy". Un test con strength muy bajo es exactamente este termómetro: canFalsify: true en el papel, pero tan poco confiable en la práctica que un solo resultado, sin importar cuál sea, casi no mueve lo que deberías creer.
Ejemplo trabajado: ampliamos pickTest() con un modo byValue
El equipo de Mercado tiene ahora cuatro candidatos, todos con canFalsify: true — incluido uno nuevo, single_user_hallway_test, que consiste en mostrarle el mockup a una sola persona al pasar por el pasillo de la oficina y observar su reacción real (no preguntarle su opinión — eso ya sabes que no cuenta, lección 5). Es barato, y sí observa comportamiento, pero una sola persona es una muestra minúscula: su reacción individual dice muy poco sobre cómo reaccionarían los compradores de Mercado en general.
// L7: ampliamos pickTest() con un modo byValue (strength/cost), sin romper
// el comportamiento anterior (el modo por defecto sigue siendo menor costo).
function pickTest(assumption, candidateTests, options = {}) {
const byValue = options.byValue || false;
const falsifiable = candidateTests.filter((t) => t.canFalsify);
if (falsifiable.length === 0) {
return { assumption, chosen: null, discarded: candidateTests, reason: 'ningun candidato puede refutar la suposicion -- hay que disenar uno nuevo' };
}
const scored = falsifiable.map((t) => ({ ...t, value: t.strength / t.cost }));
const chosen = byValue
? scored.reduce((best, t) => (t.value > best.value ? t : best))
: scored.reduce((best, t) => (t.cost < best.cost ? t : best));
const discarded = candidateTests.filter((t) => t.type !== chosen.type);
return { assumption, chosen, discarded, mode: byValue ? 'mejor strength/cost' : 'menor costo' };
}
const recommendationsBet = {
assumption: 'Los usuarios van a comprar mas si ven recomendaciones personalizadas',
};
const candidateTestsV3 = [
{ type: 'single_user_hallway_test', cost: 1, canFalsify: true, strength: 1 },
{ type: 'fake_door_checkout', cost: 3, canFalsify: true, strength: 6 },
{ type: 'clickable_prototype_interview', cost: 5, canFalsify: true, strength: 7 },
{ type: 'build_the_engine', cost: 40, canFalsify: true, strength: 9 },
];
console.log('=== pickTest() en modo "menor costo" vs modo "byValue" (strength/cost) ===\n');
console.log('Candidatos (todos canFalsify=true):');
candidateTestsV3.forEach((t) => {
console.log(' ' + t.type.padEnd(30) + ' cost=' + t.cost + ' strength=' + t.strength + ' value=' + (t.strength / t.cost).toFixed(2));
});
const byCost = pickTest(recommendationsBet.assumption, candidateTestsV3, { byValue: false });
const byValue = pickTest(recommendationsBet.assumption, candidateTestsV3, { byValue: true });
console.log('\nModo "' + byCost.mode + '": elige ' + byCost.chosen.type + ' (cost=' + byCost.chosen.cost + ', strength=' + byCost.chosen.strength + ')');
console.log('Modo "' + byValue.mode + '": elige ' + byValue.chosen.type + ' (cost=' + byValue.chosen.cost + ', value=' + byValue.chosen.value.toFixed(2) + ')');
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== pickTest() en modo "menor costo" vs modo "byValue" (strength/cost) ===
Candidatos (todos canFalsify=true):
single_user_hallway_test cost=1 strength=1 value=1.00
fake_door_checkout cost=3 strength=6 value=2.00
clickable_prototype_interview cost=5 strength=7 value=1.40
build_the_engine cost=40 strength=9 value=0.23
Modo "menor costo": elige single_user_hallway_test (cost=1, strength=1)
Modo "mejor strength/cost": elige fake_door_checkout (cost=3, value=2.00)
Ahí está el problema, de golpe: el modo "menor costo" —el único que has usado hasta ahora— elige single_user_hallway_test, el termómetro descalibrado del ejemplo. Cuesta menos que cualquier otro candidato, y técnicamente sí puede refutar la hipótesis... pero su strength es la más baja de las cuatro (1, contra 6, 7 y 9 de los demás). Si esa única persona en el pasillo reacciona bien, ¿de verdad crees que eso confirma algo sobre miles de compradores de Mercado? Y si reacciona mal, ¿el equipo estaría dispuesto a matar la apuesta completa por la opinión de una sola persona? Casi seguro que no en ninguno de los dos casos — lo cual, si lo piensas con el criterio de la lección 5, es una señal de que el resultado se puede racionalizar en cualquier dirección, exactamente el problema que se supone que un test bien diseñado debería evitar.
El modo byValue corrige esto calculando value = strength / cost para cada candidato y eligiendo el mayor: single_user_hallway_test tiene value: 1.00, pero fake_door_checkout tiene value: 2.00 — el doble de evidencia por cada unidad de costo. clickable_prototype_interview (1.40) y build_the_engine (0.23) quedan aún más abajo: el segundo, en particular, tiene la evidencia más fuerte de las cuatro en términos absolutos (strength: 9), pero es tan caro que su value termina siendo el peor de todos.
value = strength / cost no reemplaza el rigor estadístico — lo aproxima
Este número —strength / cost— es un modelo pedagógico, igual que todos los de este módulo: una forma simple de razonar sobre el balance entre "cuánto cuesta" y "cuánto me dice" sin necesitar todavía estadística formal. En un caso real, decidir si una diferencia observada es genuina o es ruido requiere significancia estadística y un tamaño de muestra calculado con cuidado — exactamente el territorio de product-metrics-and-experimentation-guide, la guía hermana de esta. value no calcula eso; solo te da un criterio rápido y honesto para descartar, antes de correr nada, los tests tan débiles que ni siquiera vale la pena someterlos a ese rigor estadístico después.
Errores comunes
Un test tan débil que cualquier resultado "confirma". Qué pasa: el equipo elige single_user_hallway_test porque es barato y técnicamente puede refutar la hipótesis, y corre el test — pero sin importar el resultado, nadie está realmente dispuesto a actuar sobre la opinión de una sola persona, así que termina descartándose como "no concluyente" o, peor, se cita como si confirmara la hipótesis si por casualidad salió bien. Por qué pasa: el filtro de canFalsify de las lecciones 4 y 5 solo revisa si un test puede fallar en principio — no si la muestra o el diseño son lo suficientemente fuertes como para que un solo resultado sea informativo de verdad. Cómo detectarlo: antes de correr un test, pregúntate "si el resultado sale mal, ¿de verdad cambiaría nuestra decisión?" — si la respuesta es "probablemente no, es una muestra muy chica", el test es demasiado débil aunque canFalsify diga que sí. Cómo corregirlo: usa el modo byValue de pickTest() como el criterio por defecto, no solo el menor costo — un test barato pero débil casi nunca gana contra uno un poco más caro con evidencia mucho más fuerte.
Optimizar solo por costo, ignorando la fuerza de la evidencia. Qué pasa: un equipo, orgulloso de ser "eficiente", presume de haber elegido siempre el test más barato posible en cada ciclo de discovery — sin darse cuenta de que varios de esos tests, aunque baratos, dieron señales tan débiles que la confianza del equipo casi no cambió después de correrlos. Por qué pasa: el costo es fácil de medir y de reportar en una retro ("gastamos solo 1 día"); la fuerza de la evidencia es más difícil de cuantificar y, sin una herramienta como value, fácil de ignorar por completo. Cómo detectarlo: revisa el historial de tests corridos y pregúntate, para cada uno, si el confidence de la apuesta correspondiente de verdad se movió después del resultado — si no, probablemente el test era barato pero débil. Cómo corregirlo: adopta value = strength / cost (o su equivalente informal) como el criterio de elección por defecto, reservando "el más barato a secas" solo para cuando todos los candidatos válidos tienen una fuerza de evidencia razonablemente similar.
Ejercicios
Ejercicio 1 — Predice los dos modos sin ejecutar Node. Con esta lista de candidatos, ¿qué elegiría pickTest() en modo "menor costo" y qué elegiría en modo byValue?
const newCandidates = [
{ type: 'anonymous_click_tracking', cost: 2, canFalsify: true, strength: 8 },
{ type: 'single_comment_on_forum', cost: 0.3, canFalsify: true, strength: 1 },
{ type: 'ab_test_small_sample', cost: 4, canFalsify: true, strength: 7 },
];
Ver solución
Modo "menor costo": single_comment_on_forum (cost=0.3). Es el más barato de los tres, y técnicamente cumple canFalsify: true, sin importar lo débil que sea su evidencia (strength: 1).
Modo byValue: anonymous_click_tracking (value = 8/2 = 4.00). Comparando: single_comment_on_forum tiene value = 1/0.3 ≈ 3.33; ab_test_small_sample tiene value = 7/4 = 1.75. anonymous_click_tracking gana con el mayor value de los tres, combinando un costo razonable con evidencia fuerte (strength: 8, la más alta del grupo).
Ejercicio 2 — Calcula value a mano. Para un test con cost: 6 y strength: 9, calcula su value. Compáralo con fake_door_checkout del ejemplo trabajado de esta lección (cost: 3, strength: 6, value: 2.00) — ¿cuál de los dos ganaría en modo byValue?
Ver solución
value = 9 / 6 = 1.50. Comparado con fake_door_checkout (value: 2.00), este nuevo test pierde en modo byValue a pesar de tener mayor strength en términos absolutos (9 contra 6) — porque también cuesta el doble (6 contra 3). El ejercicio confirma el punto central de la lección: strength alto no gana automáticamente si el costo también es alto en la misma proporción o más; lo que decide es la relación entre los dos números, no ninguno de los dos por separado.
Ejercicio 3 — Conecta con lo que viene después del módulo. Sin entrar en detalle —eso es trabajo de otras lecciones y de otros módulos— explica en una frase por qué la idea de "fuerza de la evidencia" que viste hoy anticipa dos cosas que vas a encontrar más adelante en esta guía: (a) por qué el módulo 6 (sesgos) va a insistir en que el comportamiento observado pesa más que la opinión declarada, y (b) por qué el módulo 7 (síntesis) va a necesitar mover el confidence de una apuesta de forma distinta según la fuerza de cada resultado.
Ver solución
Una respuesta razonable: "Si la evidencia tiene distintos niveles de fuerza —como viste hoy con strength—, entonces (a) una opinión declarada por un usuario, al ser generalmente más débil que su comportamiento observado, no debería pesar lo mismo al decidir si una suposición se sostiene; y (b) el confidence de una apuesta debería subir o bajar más después de un resultado con strength alto que después de uno con strength bajo, en vez de tratar todos los resultados de discovery como si valieran exactamente lo mismo." No hace falta más detalle que este — el módulo 6 le da su lugar completo a la primera idea, y updateConfidence() en el módulo 7 implementa la segunda.
Resumen y siguiente paso
Esta lección amplió pickTest() con un segundo modo, byValue, que compara strength / cost en vez de solo el costo — sin romper el comportamiento anterior, que sigue disponible como el modo por defecto. Viste que el candidato más barato de todos (single_user_hallway_test) puede ser una elección equivocada, aunque cumpla canFalsify: true, porque su evidencia es tan débil que un solo resultado casi no informa nada — el mismo problema del termómetro descalibrado. fake_door_checkout, con el mejor balance entre costo y fuerza, sigue siendo la elección correcta para recommendations, ahora con un criterio más completo que la respalda.
Antes de avanzar deberías poder: explicar por qué un test barato con canFalsify: true puede, de todos modos, ser una mala elección; y calcular value = strength / cost para cualquier par de números.
Con esto termina la parte de diseño del módulo. La lección 8, el proyecto, corre el pipeline completo —isFalsifiable() sobre la hipótesis de recommendations, y pickTest() en su modo byValue, sobre una lista realista de candidatos que incluye una entrevista de comportamiento, un fake door, un prototipo clickable, y construir el motor completo— para llegar a la decisión final: cuál es, de verdad, el riskiest assumption test de Mercado.
Recursos
- David J. Bland y Alexander Osterwalder, resumen de Testing Business Ideas — strategyzer.com/library/testing-business-ideas-book-summary. El catálogo original clasifica cada tipo de experimento tanto por su costo relativo como por qué tan fuerte es la evidencia que produce — la fuente directa de la idea de
valueen esta lección. En inglés. - Teresa Torres, "Assumption Testing: Everything You Need to Know to Get Started" — producttalk.org/assumption-testing. Sobre por qué no todos los tests de suposiciones son igual de convincentes, incluso cuando técnicamente prueban lo mismo. En inglés.
- Marty Cagan (Silicon Valley Product Group), "The Four Big Risks" — svpg.com/four-big-risks. Un recordatorio de que evaluar el riesgo correctamente —de qué tipo, y con qué tan buena evidencia— es el trabajo central del discovery, más allá de solo elegir el test más económico. En inglés.