Esta página lleva la tesis del programa hasta el final: aprende el representante, reconoce la familia entera. El mismo problema de la clase —la prueba end-to-end que confirma que el sistema completo responde lo que se esperaba— resuelto por los primos de cada familia del Atlas, no solo por los diez lenguajes del núcleo.
Si entendiste la versión de Python, la de Ruby te resultará familiar aunque no la hayas visto nunca. Ese reconocimiento es exactamente lo que este curso quiere producir.
⚠️ Qué está verificado y qué no. Ruby, Perl y Lua se ejecutan en CI contra el mismo
casos.jsonque el núcleo, igual que las diez implementaciones de la clase (workflow Labs). Los otros 17 primos son material de lectura: su toolchain no está en el workflow, así que están escritos para ser correctos pero sin el sello de la máquina. Verificar tres de veinte no es verificarlos todos.
a b esperadoe2e=<pasa|falla>| stdin | esperado |
|---|---|
3 4 7 |
e2e=pasa |
2 2 5 |
e2e=falla |
10 5 15 |
e2e=pasa |
Representantes del núcleo: Python · PHP. La familia donde la prueba end-to-end suele escribirse como un script más: se invoca el sistema, se compara la salida con la esperada y se informa. Sin ceremonia de framework.
a, b, esperado = STDIN.gets.split.map(&:to_i)
puts "e2e=#{a + b == esperado ? 'pasa' : 'falla'}"
my ($a, $b, $esperado) = split ' ', <STDIN>;
printf "e2e=%s\n", $a + $b == $esperado ? "pasa" : "falla";
local a, b, esperado = io.read("n", "n", "n")
print("e2e=" .. (a + b == esperado and "pasa" or "falla"))
gets stdin linea
lassign [split $linea] a b esperado
set res [expr {$a + $b == $esperado ? "pasa" : "falla"}]
puts "e2e=$res"
v <- as.integer(strsplit(readLines("stdin", n = 1), " ")[[1]])
cat(sprintf("e2e=%s\n", if (v[1] + v[2] == v[3]) "pasa" else "falla"))
Qué reconocer: los cinco expresan la aserción como una comparación corriente dentro del flujo
normal del programa, no como una construcción especial del lenguaje. Lua usa el modismo
cond and x or y porque no tiene operador ternario. Y hay un dato histórico que conviene guardar:
el formato TAP (Test Anything Protocol), que hoy consumen arneses de pruebas de media docena
de familias, nació en el arnés de pruebas de Perl. Tcl trae tcltest en su distribución base y R
integra stopifnot en el propio lenguaje: en esta familia la prueba tiende a ser parte del paquete,
no una dependencia añadida.
Representantes del núcleo: JavaScript · TypeScript.
import 'dart:io';
void main() {
final v = stdin.readLineSync()!.split(' ').map(int.parse).toList();
print('e2e=${v[0] + v[1] == v[2] ? 'pasa' : 'falla'}');
}
// ActionScript vive dentro del reproductor Flash: no hay stdin ni codigo de salida
// de proceso, asi que una prueba end-to-end de linea de comandos es inexpresable.
// Lo mas cercano es exponer la asercion como funcion pura y llamarla desde el arnes.
package {
public class PruebaE2E {
public static function verificar(a:int, b:int, esperado:int):String {
return "e2e=" + (a + b == esperado ? "pasa" : "falla");
}
}
}
Qué reconocer: Dart interpola con ${...} igual que JavaScript y ejecuta pruebas con
dart test, un corredor oficial del SDK; TypeScript y JavaScript dependen de un corredor externo
(Jest, Vitest, node --test). ActionScript marca el límite duro de esta familia: un lenguaje
atado a un anfitrión gráfico no puede tener una prueba end-to-end de proceso, porque no hay
proceso que devuelva un código de salida. La aserción existe; la frontera del sistema, no.
Representante del núcleo: Java. Los cuatro comparten el mismo ecosistema de pruebas —JUnit— y el mismo formato de informe, aunque escriban la aserción de forma muy distinta.
fun main() {
val (a, b, esperado) = readLine()!!.split(" ").map { it.toInt() }
println("e2e=" + if (a + b == esperado) "pasa" else "falla")
}
object PruebaE2E extends App {
val Array(a, b, esperado) = scala.io.StdIn.readLine().split(" ").map(_.toInt)
val res = if (a + b == esperado) "pasa" else "falla"
println(s"e2e=$res")
}
def (a, b, esperado) = System.in.newReader().readLine().split(' ')*.toInteger()
println "e2e=${a + b == esperado ? 'pasa' : 'falla'}"
(require '[clojure.string :as str])
(let [[a b esperado] (map parse-long (str/split (read-line) #"\s+"))]
(println (str "e2e=" (if (= (+ a b) esperado) "pasa" "falla"))))
Qué reconocer: en Kotlin y Scala el if devuelve un valor y se puede asignar; en Java hay
que usar el ternario o una variable mutable. Esa diferencia sintáctica tiene consecuencia práctica
en pruebas: la aserción se compone en una expresión en vez de en un bloque. Los cuatro corren sobre
JUnit para el arnés real —Groovy añade Spock, con su gramática given/when/then, y Clojure trae
clojure.test en la biblioteca estándar—, así que un informe de pruebas de un proyecto Scala y uno
de un proyecto Java son el mismo XML.
Representante del núcleo: C#. Un único corredor, dotnet test, para todos los
lenguajes del CLR.
let [| a; b; esperado |] = stdin.ReadLine().Split(' ') |> Array.map int
printfn "e2e=%s" (if a + b = esperado then "pasa" else "falla")
Module PruebaE2E
Sub Main()
Dim v = Console.ReadLine().Split(" "c)
Dim a = Integer.Parse(v(0))
Dim b = Integer.Parse(v(1))
Dim esperado = Integer.Parse(v(2))
Console.WriteLine("e2e=" & If(a + b = esperado, "pasa", "falla"))
End Sub
End Module
Qué reconocer: F# usa = para comparar, no ==, porque reserva <- para la asignación: es la
convención de la rama ML, y confunde a quien llega de C#. VB.NET necesita la forma de tres
argumentos de If(...) para obtener un ternario, porque If normalmente es una sentencia. Lo que
no cambia es el arnés: xUnit y NUnit se consumen igual desde los tres lenguajes, y un ensamblado de
pruebas escrito en F# lo ejecuta el mismo dotnet test que uno de C#.
Representante del núcleo: C. Aquí el protocolo de pruebas end-to-end es, casi siempre, el código de salida del proceso más lo que se imprimió por stdout.
#include <iostream>
int main() {
long a, b, esperado;
std::cin >> a >> b >> esperado;
std::cout << "e2e=" << (a + b == esperado ? "pasa" : "falla") << '\n';
}
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
long a, b, esperado;
if (scanf("%ld %ld %ld", &a, &b, &esperado) != 3) return 1;
NSString *res = (a + b == esperado) ? @"pasa" : @"falla";
printf("e2e=%s\n", [res UTF8String]);
}
return 0;
}
Qué reconocer: ninguno de los dos —ni C— trae corredor de pruebas en el lenguaje ni en la
biblioteca estándar. Todo lo que existe es assert.h, que aborta el proceso en vez de informar,
y por eso la familia acumula frameworks de terceros (Google Test, Catch2, Unity, XCTest). Objective-C
es la excepción parcial: XCTest viene con las herramientas de Apple y es el estándar de facto de su
plataforma, pero sigue siendo del entorno, no del lenguaje. Fíjate en que el scanf de la clase
compila tal cual en ambos: son superconjuntos de C.
Representantes del núcleo: Go · Rust. La familia que decidió que las pruebas no debían ser una dependencia externa.
const std = @import("std");
pub fn main() !void {
var buf: [128]u8 = undefined;
const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
var it = std.mem.tokenizeAny(u8, linea, " \r\t");
const a = try std.fmt.parseInt(i64, it.next().?, 10);
const b = try std.fmt.parseInt(i64, it.next().?, 10);
const esperado = try std.fmt.parseInt(i64, it.next().?, 10);
const res = if (a + b == esperado) "pasa" else "falla";
try std.io.getStdOut().writer().print("e2e={s}\n", .{res});
}
test "el sistema suma sus entradas" {
try std.testing.expectEqual(@as(i64, 7), 3 + 4);
}
import std/[strutils, sequtils]
let v = stdin.readLine().splitWhitespace().map(parseInt)
echo "e2e=", (if v[0] + v[1] == v[2]: "pasa" else: "falla")
import std.stdio, std.array, std.conv, std.algorithm;
void main() {
auto v = readln().split().map!(to!long).array;
writeln("e2e=", v[0] + v[1] == v[2] ? "pasa" : "falla");
}
unittest {
assert(3 + 4 == 7);
}
Qué reconocer: aquí está la diferencia más fuerte de toda la página. Zig y D tienen las
pruebas en la gramática: test "..." y unittest { ... } son palabras del lenguaje, viven junto
al código que prueban y solo se compilan cuando se pide (zig test, dmd -unittest). Es la misma
decisión que Rust con #[test] y Go con _test.go: el representante que estudiaste ya te enseñó el
patrón. Nim lo resuelve un escalón más abajo, con std/unittest en la biblioteca estándar. En
ninguno de los tres hace falta añadir una dependencia para tener un arnés.
Representante del núcleo: SQL. Se describe qué debe ser cierto, no cómo comprobarlo.
:- initialization(main, main).
main :-
read_line_to_string(user_input, Linea),
split_string(Linea, " ", "", Partes),
maplist([S, N]>>number_string(N, S), Partes, [A, B, Esperado]),
( A + B =:= Esperado
-> Res = "pasa"
; Res = "falla"
),
format("e2e=~w~n", [Res]).
% Datalog no tiene E/S ni orden de ejecucion: no puede leer stdin ni "correr" una
% prueba. Los casos se declaran como hechos y la regla deriva solo los que pasan;
% que un caso falle se observa por su AUSENCIA en la relacion, no por un mensaje.
caso("3 4 7", 3, 4, 7).
caso("2 2 5", 2, 2, 5).
caso("10 5 15", 10, 5, 15).
pasa(Id) :- caso(Id, A, B, Esperado), Esperado = A + B.
Qué reconocer: en Prolog =:= compara evaluando aritmética, mientras = unifica y is liga:
tres operadores donde los lenguajes imperativos tienen uno, y confundirlos es el error clásico del
recién llegado. Datalog muestra el modelo de pruebas más ajeno de todos: bajo mundo cerrado, lo
que no se deriva simplemente no es cierto, así que "la prueba falla" equivale a "la tupla no
aparece". Es exactamente la lógica de un SELECT que devuelve cero filas —el mismo gesto que usaste
en la versión SQL de la clase para comprobar el sistema—.
Veinte lenguajes, una sola prueba: alimentar al sistema, comparar con lo esperado, informar. Lo que cambia no es la aserción —es idéntica en todos— sino dónde vive el arnés: en la gramática (Zig, D, Rust, Go), en la biblioteca estándar (Clojure, Nim, Tcl), en un framework de terceros (C, C++) o directamente en ningún sitio (ActionScript, Datalog). Elegir lenguaje es también elegir cuánta infraestructura de pruebas te viene regalada.