El botón verde de "run test", pero en Emacs: ejecuta el escenario donde está el cursor


11 de junio de 2026

La última milla

Venía cerrando mi flujo de tests de Behat dentro de Emacs. Ya tenía dos piezas montadas en posts anteriores:

Faltaba la que más se usa, la del iconito verde del IDE: estar dentro de un escenario, pulsar una tecla, y que se ejecute solo ese. Sin escribir la ruta, sin acordarme de la línea, sin salir a la terminal.

El truco es el mismo de siempre: `fichero:línea`

Behat (como pytest, como casi todos) acepta que le digas exactamente qué correr con la sintaxis fichero.feature:42 — "ejecuta el escenario que empieza en la línea 42". Y el editor sabe en qué fichero estás y en qué línea tienes el cursor. Une las dos cosas y ya está: coges el fichero, pegas el número de línea, lanzas.

Las funciones

(defvar my/behat-command "bin/behat"
  "Ejecutable de Behat, relativo a la raiz del proyecto.")

(defvar my/behat-default-args "--tags '~@pendiente'"
  "Argumentos por defecto (ajusta a tu gusto, o cadena vacia).")

(defun my/behat--root ()
  "Raiz del proyecto Behat: el dir ancestro que CONTIENE bin/behat.
Lo busca subiendo desde el fichero, asi acierta aunque trabajes desde un
proyecto-paraguas que agrupa varios servicios (ver mas abajo)."
  (let ((file (or (buffer-file-name) default-directory)))
    (expand-file-name
     (or (locate-dominating-file
          file (lambda (d) (file-exists-p (expand-file-name "bin/behat" d))))
         (locate-dominating-file file "behat.yml")
         default-directory))))

(defun my/behat--run (&optional line)
  "Ejecuta Behat sobre el .feature del buffer, desde la raiz del proyecto.
Si LINE se da, corre solo el escenario que empieza ahi (fichero:LINE)."
  (let* ((root (my/behat--root))
         (rel  (file-relative-name (buffer-file-name) root))
         (target (if line (format "%s:%d" rel line) rel))
         (default-directory root))          ; correr desde la raiz del servicio
    (compile (string-trim (format "%s %s %s"
                                  my/behat-command my/behat-default-args target)))))

(defun my/behat-run-scenario ()
  "Ejecuta con Behat solo el escenario bajo el cursor."
  (interactive)
  (my/behat--run (line-number-at-pos)))

(defun my/behat-run-feature ()
  "Ejecuta con Behat toda la feature del buffer."
  (interactive)
  (my/behat--run))

Tres ideas y se entiende solo:

Y un detalle que importa: compile hereda el entorno del buffer. Si usas direnv/Nix (el módulo envrc en Doom), el bin/behat sale con las herramientas correctas en el PATH, igual que en tu terminal. Sin eso, "no encuentra php" y a volverse loco.

Atarlo a una tecla (y de paso, adiós a Ruby)

(after! feature-mode
  ;; El runner propio de feature-mode venia apuntando a cucumber (Ruby).
  ;; Lo mandamos a Behat:
  (setq feature-cucumber-command "bin/behat {options} \"{feature}\"")
  (map! :map feature-mode-map
        :localleader
        "v" #'my/behat-run-scenario    ; SPC m v -> escenario bajo el cursor
        "V" #'my/behat-run-feature))   ; SPC m V -> feature entera

feature-mode (el modo de los .feature) trae de fábrica comandos para correr escenarios, pero pensados para Cucumber/Ruby. Con esa línea su ejecutor pasa a usar Behat. Y los dos atajos nuevos son la guinda: cursor en un escenario, SPC m v, y corre solo ese.

Por qué busco `bin/behat` y no "la raíz del proyecto"

Lo natural sería preguntarle a tu gestor de proyectos (en Emacs, projectile) "¿cuál es la raíz?". Y funciona… hasta que tienes un monorepo de microservicios: una carpeta con veinte servicios, cada uno su repo, y una feature que toca tres a la vez. Si marcas la carpeta de arriba como proyecto-paraguas para buscar y navegar a lo ancho, "la raíz del proyecto" pasa a ser esa de arriba… donde no hay ningún bin/behat. Y el comando revienta con un "no such file".

Por eso my/behat--root no pregunta "¿cuál es el proyecto?", sino "¿dónde está el bin/behat más cercano subiendo?". Esa pregunta acierta siempre: te da el servicio concreto, tengas activo el paraguas o el micro. La lección general: cuando un comando necesita un fichero concreto, busca ese fichero, no "el proyecto" — en cuanto el árbol se complica, son cosas distintas.

El flujo completo, sin tocar la terminal

Ahora mi bucle de trabajo con Behat vive entero dentro del editor:

Quiero… Pulso…
Ir del paso al método PHP gd
Ejecutar este escenario SPC m v
Ejecutar la feature entera SPC m V
Re-ejecutar lo último g en el buffer de resultados
Saltar a un fallo Enter sobre la línea del error

Esa era la última cosa que me ataba al IDE para mi día a día de PHP. Y van unas cuantas tachadas de la lista. El patrón se repite tanto que ya casi me aburre decirlo: lo que crees que te falta no falta, está apagado o se monta en quince líneas. Un editor de los setenta, un poco de Elisp, y tu botón verde de "run test" hecho a tu medida.

— doomguru

Comparte este post:

Es tu post

Estas seguro? Esto no se puede deshacer.

Comentarios (0)

Sin comentarios todavia. Se el primero!

Deja un comentario