vim Ultisnip para mamba

En mi día a día de trabajo hago un uso intesivo de mamba, este pequeño snippet me ahora escribir bloque de código repetitivo cuando voy a escribir tests

https://gitlab.com/-/snippets/2286835

# code comment
global !p
def sanitize_spec_fn(fn):
    return fn.replace("spec.py", "").replace("_", " ")
endglobal

snippet mamba "mamba spec skel"
from mamba import description, context, it
from expects import expect, equal

with description("${1:`!p snip.rv = sanitize_spec_fn(snip.fn)`}"):
	with context("${2:context}"):
		with it("${3:test expectation}"):
			expect(True).to(equal(False))
endsnippet

shell interactivas y shell con login

Algunos de los conceptos de linux con complicados de entender, incluso años después de serguir usando GNU/Linux sigo aprendiendo cosas muy básicas.

Voy a intentar explicar(me) qué es una shell interactiva y una shell con login, y qué suecede cuando entras en cada una de ellas

shell login

Cuando tienes que introducir usuario y password para acceder, ya sea terminal, ssh, etc…

A login shell is one whose first character of argument zero is a -, or one started with the –login option.

man 1 bash

lo importante de aquí es ver que se ejecutan esta secuencia de ficheros para configurar el entorno de usuairo:

/etc/profile, 
~/.bash_profile
~/.bash_login
~/.profile

A destacar sólo .profile ya que busca si tenemos un bin propio para el usuario y lo mete en el PATH

shell interactiva

Una shell que abres una vez que ya estás en el sistema, por ejemplo cuando abres un xterm o un mate terminal

An interactive shell is one started without non-option arguments (unless -s is specified) and without the -c option whose standard input and error are both connected to terminals (as determined by isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.

man 1 bash

El problema que tengo actualmente es que esta shell interactiva no está ejecutando ~/.profile y no sé por qué, por que si debería según la documentación [WIP]

When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist. This may be inhibited by using the –norc option. The –rcfile file option will force bash to read and execute commands from file instead of /etc/bash.bashrc and ~/.bashrc.

man 1 bash

En resumen, todo este hilo es por que necesitaba tener de alguna forma cómoda y elegante un PATH a ~/.local/bin y finalmente lo que voy a hacer es agregar este código a ~/.bashrc

# add at the bottom of ~/.bashrc
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

#/etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

if [ "$(id -u)" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
fi
export PATH

if [ "${PS1-}" ]; then
  if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
    # The file bash.bashrc already sets the default PS1.
    # PS1='\h:\w\$ '
    if [ -f /etc/bash.bashrc ]; then
      . /etc/bash.bashrc
    fi
  else
    if [ "$(id -u)" -eq 0 ]; then
      PS1='# '
    else
      PS1='$ '
    fi
  fi
fi

if [ -d /etc/profile.d ]; then
  for i in /etc/profile.d/*.sh; do
    if [ -r $i ]; then
      . $i
    fi
  done
  unset i
fi

# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
	. "$HOME/.bashrc"
    fi
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

Nerdtree: Navegador de ficheros

Nerdtree está muy difundido, hay muchos otros gestores/navegadores de ficheros pero Nerdtree funciona muy bien y no he econtrado ningún otro que me ofrezca ventajas sobre este.

Instalación

Agregar en nuestro bloque de plugins en vimrc

Plugin 'scrooloose/nerdtree'

Enlace:
https://github.com/valsorym/scrooloose-nerdtree

Características:

  • navegar por rutas de directorio
  • permite búsqueda como si fuera un buffer cualquiera
  • operar con ficheros y directorios ( lo que Nerdtree llama nodos )
  • permite hacer bookmarks de ficheros y/o directorios

Operaciones destacadas

  • ? para ‘togglear’ la ayuda
  • enter para collapsar o expandir un árbol
  • ‘m’ para el menú de manipulación de ficheros o directorios, Nerdtree los llama nodos. Te permite borrar, renombar, crear, etc… sin tener que salir a la terminal
  • t/T para abrir en un nuevo tab. t abre tab y mueve el cursor al tab y T abre tab pero el cursor permanece en nerdtree
  • Para abrir ficheros puedes usar el comando ‘g’ + o/i/s que es un preview y el modo de mostrarlo. El concepto de preview es que te abre el contenido en un buffer y mantiene el cursor en el arbol de NerdTree, esto es útil si quieres ojear ficheros rápidamente sin tener que desplazar el cursor al bufer y volver a Nerdtree, te ahorras pulsaciones de teclado.
  • go / o. Previsualizar/abrir en el buffer actual
  • gi / i . Previsualiza/abre fichero spliteando el buffer actual en horizontal
  • gs / s . Previsualiza/abre fichero spliteando el buffer actual en vertical
  • e . Explore, abre el nodo de directorios actual en otro buffer para que puedas separar la visualización de los directorios.
  • ‘m’ es el menu de operaciones con nodos
NERDTree Menu. Use j/k/enter, or the shortcuts indicated   
=========================================================  
> (a)dd a childnode                                        
  (m)ove the current node                                  
  (d)elete the current node                                
  (r)eveal the current node in file manager                
  (o)pen the current node with system editor               
  (c)opy the current node                                  
  copy (p)ath to clipboard                                 
  (l)ist the current node                                  
  Run (s)ystem command in this directory                   
                                                           

VundleVim: gestor de plugins

Mi vimrc usa VundleVim como gestor de plugins, dentro del bloque de Vundle, hay que agregar lineas con Plugin seguido de la fuente del plugin. Puede ser una url completa o un path de github.

Enlace

https://github.com/VundleVim/Vundle.vim

Instalación

" Vundle start plugin            
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()              

Plugin 'VundleVim/Vundle.vim'

call vundle#end() " required

Una vez que tenemos estas lineas en nuestro vimrc ya podemos instalar y actualizar plugins. Hay dos formas desde linea de comandos o desde el propio vim

" Desde vim 
:PluginInstall
:PluginUpdate

" Desde linea de comandos
$ vim +PluginInstall
$ vim +PluginUpdate

Cómo uso VIM como IDE para programar en Python I

Desde hace varios años trabajo habitualmente con VIM, es una herramienta muy potente con muchas ventajas pero también con muchos incovenientes.

A lo largo de estos años he pasado por diversas etapas y cada día aparecen nuevos plugins o incorporo mejoras que me hacen la vida más fácil. El objetivo de esta serie de posts es comentaros cómo trabajo actualmente, qué uso, plugins, keybindings, etc… y por qué los uso.

Una de las dificultades a la hora de usar VIM es que hay multitud de plugins, todos parecen funcionar bien y muchos hacen lo mismo, identificar cuál es el más apropiado para cada uno y configurarlo correctamente a veces es laborioso. Espero que compartiendo mi experiecia pueda ayudar a otras personas a reducir esa frustración y conseguir una mejor experiencia al entrar al mundo VIM.

Como referencia mi vimrc ( ~/.vimrc ) lo tengo publicado en https://gitlab.com/jsenin/vimrc intento documentar lo mejor posible los cambios que hago, lo mantengo bastante actualizado.

Una de las ventajas de VIM es que puedo poner en funcionamiento mi entorno de trabajo en unos pocos minutos, clonando mi fichero, actualizar plugins e instalar algún paquete de sistema, es suficiente y muy muy rápido. La portabilidad del entorno de trabajo de una forma muy cómoda y rápida para mí es una gran ventaja.

La forma en que yo actualizo mi vimrc está documentada en https://gitlab.com/jsenin/vimrc/-/blob/master/README.md pero lo realmente necesario es saber que toda tu configuración está en el fichero ~/.vimrc

usa :q para salir de vim

Como eliminar ^M de mi código

Algunos editores como IntelliJ o VisualStudio Code, notepad, etc… podrían agregarte un molesto fin de de linea ^M a tu código fuente.

Este ^M es una marca de fin de linea que meten algunos editores cuando quieren representar salto de linea de \r ( en unix es \n)

Para limpiar todos tus ficheros afectado puedes usar esta linea

sed $'s/\r//' -i $(grep -lr $'\r' *)

Formas posibles de solucionarlo en editores

Para VI/VIM

:set ff=unix

Para IntelliJ

https://www.jetbrains.com/help/idea/configuring-line-endings-and-line-separators.html

Existe otra estrategia que es configurar git para que transforme esos valores de manera automática. Hay un hilo en stackoverflow bastante interesante que habla de .gitattributes y de la configuración core.autocrlf https://stackoverflow.com/questions/170961/whats-the-strategy-for-handling-crlf-carriage-return-line-feed-with-git

TDD and Hard to Test Areas, Part1 / TDD las partes difíciles Parte 1

Traducción del artículo de Ian Cooper publicado el 7 de Julio de 2008. http://codebetter.com/iancooper/2008/07/07/tdd-and-hard-to-test-areas-part1/

Quería hablar sobre los problemas que las personas tienen cuando comienzan a trabajar con TDD, los mismos problemas que tienden a hacer que abandonen TDD después de un primer contacto. Esas son las áreas «difíciles de probar», las cosas que debe hacer el código de producción, que esas presentaciones y libros introductorios no parecen explicar bien. En esta publicación, comenzaremos con una revisión rápida de TDD y luego veremos por qué las personas fallan cuando comienzan a intentar usarlo. La próxima vez analizaremos más de cerca las soluciones.

Revisión

Clean Code

TDD es un enfoque de desarrollo en el que escribimos nuestro
test antes de escribir el código de producción. Los beneficios de esto son:

  • Ayuda a mejorar la calidad: los tests nos dan una respuesta rápida. Recibimos
    confirmación inmediata de que nuestro código se comporta como se esperaba. Indica dónde hay que arreglar un defecto.
  • Ayuda a pasar menos tiempo en el depurador. Cuando algo rompe nuestros tests
    a menudo son lo suficientemente granulares como para mostrarnos lo que ha salido mal, sin tener que depurar. Si no es así, probablemente no tengamos la granularidad de test suficiente o no están bien redactados. La depuración requiere tiempo, así que cualquier cosa que ayude a mantenernos fuera del depurador nos ayuda a entregar código con un coste menor.
  • Ayuda a producir código limpio: no agregamos funcionalidad especulativa, solo
    código para el que tenemos un test.
  • Ayuda a ofrecer un buen diseño: nuestro test no sólo muestra nuestro código, sino también
    diseño, porque el acto de escribir un test nos obliga a tomar decisiones sobre el diseño del SUT.
  • Ayuda a mantener un buen diseño: Nuestros tests nos permiten refactorizar, cambiando la
    implementación para eliminar los olores de código, al tiempo que confirma que nuestro código sigue funcionando correctamente. Esto nos permite hacer una re-arquitectura incremental, mantener el diseño simple y ajustado mientras agregamos nuevas funciones.
  • Ayuda a documentar nuestro sistema: si quieres saber cómo debe comportarse el SUT
    los test son un medio eficaz de comunicar esa información. Los tests proporcinan esos ejemplos.

Automatizar los tests reducen el coste de realizar estos tests. Pagamos el coste de hacer los tests una vez, pero luego podemos volver a ejecutar una y otra vez nuestros tests sin esfuerzo y eso nos ayuda a mantener el beneficio durante toda la vida útil del sistema. Automatizar los tests es «el regalo que sigue dando». El software pasa más tiempo de su vida en mantenimiento que en desarrollo, por lo que reducir el coste de mantenimiento reduce el coste del software.

Los pasos

Los pasos en TDD a menudo se describen como Red-Green-Refactor

  • Red (Rojo) : escriba una prueba fallida (no hay pruebas para pruebas, por lo que
    esto verifica su prueba por usted)
  • Green (Verde) : hazlo pasar, que no falle el test
  • Refactor: Resuelve los olores en la implementación del código que acabamos de agregar.

¿ Quieres profundizar más ?

El libro de Kent Beck Test-Driven Development, By Example es un clásico para aprender los conceptos básicos de TDD.

Definiciones rápidas


System Under Test (SUT): Independientemente de lo que estemos probando, esto puede diferir según el nivel del test. Para un test unitaria, esto podría ser una clase o método en esa clase. Para tests de aceptación, esto puede ser una parte de la aplicación.

Depended Upon Component (DOC): algo de lo que depende el SUT, una clase o
componente.

Shared fixtures: Es el conjunto de datos que se usan para la preparación de un tests en un medio externo. Por ejemplo un volcado de base de datos que se inserta antes de un tests para preparar el estado inicial previo a la ejecución.

Nota del traductor:  Agrego esta defición ya que es importante para entender contenido posterior y que el autor no ha incluído. Shared Fixture: http://xunitpatterns.com/Shared%20Fixture.html

¿Qué entendemos por difícil de probar?

The Wall ( El muro )

Cuando comenzamos a usar TDD, rápidamente nos topamos con un muro con zonas difíciles de probar. Quizás el ciclo simple de refactorización rojo-verde comienza a empantanarse cuando comenzamos a trabajar con el código de la capa de infraestructura que habla con la persistencia (base de datos) o un servicio web externo. Quizás no sepamos cómo testear nuestra UI (interfaz de usuario) con un framework xUnit. O tal vez teníamos una base de código heredada, y colocando incluso el la parte más pequeña bajo prueba se convirtió rápidamente en un maratón en lugar de en carreras cortas.

Los novatos en TDD a menudo encuentran que todo se vuelve un poco complicado y
frente a la presión del horario, dejan TDD. Al abandonar pierden la fe en
la capacidad de TDD y continúan con la presión de su horario. Todos hacemos
lo mismo, bajo presión recurrimos a lo que sabemos; cuando encontramos algunas dificultades con TDD, los desarrolladores dejan de escribir pruebas.

El denominador común entre las áreas difíciles de probar es que
romper el ritmo de desarrollo desde nuestros test rápidos y el ciclo de testeo, y son
costosos y lento de escribir. Las pruebas suelen ser frágiles y fallan erráticamente y son difíciles de mantener.

La base de datos

  • Tests lentos: Los tests de la base de datos se ejecutan lentamente, hasta 50 veces más lentamente que las pruebas normales. Esto rompe el ciclo de TDD. Los desarrolladores tienden a omitir la ejecución de todas las pruebas porque lleva demasiado tiempo.
  • Shared fixture bugs: una base de datos es un ejemplo de un dispositivo compartido. Un shared fixture comparte el estado en múltiples tests. El peligro aquí es que el test A y el test B pasan de forma aislada, pero ejecutar el test A después del test B cambia el valor de ese fixture para que el test falle inesperadamente. Este tipo de errores son costosos de rastrear y corregir. Terminas con un patrón de búsqueda binario para intentar resolver problemas de shared fixture: probar combinaciones de test para ver qué combinaciones fallan. Debido a que eso consume mucho tiempo, los desarrolladores tienden a ignorar o eliminar estas pruebas cuando fallan.
  • Tests oscuros: para evitar problemas de shared fixtures, las personas a veces intentan comenzar con una base de datos limpia. En el setup de su test, completan la base de datos con los valores que necesitan y, en el teardown, los limpian. Estas pruebas se vuelven oscuras, porque el código de setup y teardown agrega mucho ruido, lo que distrae de lo que realmente se está probando. Esto hace que los tests sean difíciles de leer, ya que son menos granulares y, por lo tanto, es más difícil encontrar la causa de la falla. El código de setup y teardown de la persistencia es otro punto de falla. Recuerda que el único test que tenemos para test en sí mismas es escribir un test que falle. Una vez que obtiene demasiada complejidad en su test, puede resultar difícil saber si su test está funcionando correctamente. También los hace más difíciles de escribir. Dedica mucho tiempo a escribir la configuración y eliminar el código que desvía su enfoque del código que está tratando de poner a prueba, rompiendo el ritmo TDD.
  • Lógica condicional: Los tests de bases de datos también tienden a terminar con lógica condicional; no estamos realmente seguros de lo que vamos a obtener, así que tenemos que insertar un check condicional para ver lo que obtuvimos. Nuestros tests no deben contener lógica condicional. Deberíamos poder predecir el comportamiento de nuestros tests. Entre otros problemas, probamos nuestros tests haciendo que fallen primero. Introducir demasiados caminos crea el riesgo de que los errores estén en nuestro test no en el SUT.

El UI / interfaz de usuario

  • No es el punto fuerte de xUnit: las herramientas de xUnit son excelentes para manejar una API, pero son menos buenas para manejar una IU. Esto tiende a deberse a que una interfaz de usuario se ejecuta en un marco que el ejecutor de pruebas necesitaría emular o con el que interactuar. Probar una aplicación WinForms necesita el bombeo de mensajes, probar una aplicación WebForms necesita un pipeline de ASP.NET. Las soluciones como NUnitAsp han demostrado ser menos efectivas para probar las IU que las herramientas de scripting como Watir o Selenium, que a menudo carecen de soporte para funciones como JavaScript en las páginas.
  • Pruebas lentas: las pruebas de IU tienden a ser pruebas lentas porque son de extremo a extremo, pasando por todo el stack hasta la persistencia.
  • Pruebas frágiles: las pruebas de IU tienden a ser frágiles, porque a menudo fallan en los intentos de refactorizar nuestra IU. Por lo tanto, cambiar el orden y la posición de los campos en la interfaz de usuario, o el tipo de control utilizado, a menudo romperá nuestras pruebas. Esto hace que las pruebas de IU sean costosas de mantener.

Los sospechosos de siempre

Podemos identificar una lista de los sospechosos habituales, que causan problemas.

  • Comunicaciones de red
  • Sistema de archivos
  • Requisitos de configuración del entorno
  • Una llamada fuera de proceso (incluye hablar con Db)
  • UI

Dónde encontrar más Patrones de XUnit

XUnit Patterns: El sitio y el libro de Gerard Meszaros son una lectura esencial si desea comprender los patrones involucrados en el desarrollo basado en pruebas.

Working with Legacy Code: el libro de Michael Feathers es la guía definitiva para el desarrollo de prueba primero en escenarios en los que se trabaja con código heredado que no tiene pruebas.

La próxima vez veremos cómo resolvemos estos problemas.

Avoid Testing Implementation Details, Test Behaviours / Evita testear detalles de implementación, testea comportamiento

Traducción del texto original de Ian Cooper el 6 de Octubre de 2011 en http://codebetter.com/iancooper/2011/10/06/avoid-testing-implementation-details-test-behaviours/

De vez en cuando vuelvo a leer el libro Test-Driven Development de Kent Beck. Sinceramente, creo que es uno de los mejores libros de desarrollo de software jamás escritos. Lo que me encanta del libro es su sencillez. Tiene una austeridad que engaña, como si se tratara de una aproximación superficial del tema. Pero si continúas leyendo a medida que avanzas con TDD te das cuenta que es bastante completo. Donde trabajos posteriores a este libro han agregado páginas e ideas, a menudo han disminuido la técnica, cargándola de complejidad, generando malentendidos acerca del poder de TDD. Insto a cualquiera de vosotros que lo haya practicado TDD por un tiempo, a volver a leer este libro regularmente.

Para mí, una de las ideas clave de TDD, que a menudo se pasa por alto, se expresa en el ciclo: Red, Green, Refactor. Primero escribimos una prueba que falle, luego la implementamos el código de la manera más rápida y sucia que podemos, y luego la hacemos elegante. En este post del blog, quiero hablar sobre algunas de las ideas del segundo y tercer paso que se pierden. (Aunque debe recordar el primer paso: las pruebas no tienen pruebas, por lo que debe probarlas).

Mucha gente se bloquea cuando van a implementar, porque tratan de implementar bien, pensando en buenos patrones OO, SOLID, etc. ¡Alto! El objetivo es poner el test en verde lo antes posible. No intentes crear una solución decente en este punto, simplemente escribe la solución más rápida para el problema que puedas encontrar. Corta y pega si puedes, copia algoritmos de CodeProject o StackOverflow si puedes.

Del libro Test-Driven Development.

Detente. Espera. Puedo escuchar a los «estirados» burlándose entre ustedes y votimando. ¿ copiar y pegar? ¡ El fin de la abstracción ! ¡ La muerte del clean code !

Si estás molesto, respire profundamente. Aspira por la nariz … aguanta 1, 2, 3 … expira por la boca. Ahora. Recuerda, nuestro ciclo tiene diferentes fases (pasan rápidamente, a menudo en segundos, pero son fases):

1. Escriba una prueba.

2. Haz que compile.

3. Ejecútalo para ver si falla.

4. Hazlo funcionar.

5. Elimina la duplicación .

Es ese paso final (que es nuestro paso de refactorización) es en el que hacemos clean code. No antes, sino después.

Hacer clean code al mismo tiempo que hacemos el paso «que funcione» puede ser demasiado para hacer a la vez. Tan pronto como puedas, comprueba el paso «hágalo funcionar» y luego «limpie el código» cuando lo desees.

Ahora bien, aquí hay una implicación importante que a menudo se pasa por alto. No tenemos que escribir tests para las clases que refactorizamos a través de ese último paso. Serán implícitos a nuestra implementación y ya están cubiertos por la primera prueba. No es necesario que agreguemos nuevas pruebas para el siguiente nivel, a menos que consideremos que no sabemos cómo dirigirnos al siguiente paso.

Del libro Test-Driven Development nuevamente:

Debido a que estamos usando Pairs como claves, tenemos que implementar equals() y hashCode(). No voy a escribir tests para estos métodos, porque estamos escribiendo código en el contexto de una refactorización. Si estamos en el paso de refactorización y pasan todos los tests, entonces suponemos que ese nuevo código se ha ejercitado correctamente. Si estuviera programando con alguien que no viera exactamente a dónde vamos con esto, o si la lógica se volviera un poco compleja, comenzaría a escribir pruebas por separado.

¡ El código desarrollado en el contexto de la refactorización no requiere nuevos tests ! Ya está cubierto, y las técnicas de refactorización seguras significan que no deberíamos estar introduciendo cambios específicos, sólo limpiando la implementación aproximada que solíamos hacer en verde. En este punto, más pruebas te ayudarán a dirigir el test (pero tienen un coste)

El aprendizaje es que un test por clase no es la esencia de TDD. Agregar una nueva clase no es el desencadenante para escribir un test. El motivo que lleva a escribir un test es implementar un requisito. Por lo tanto, deberíamos probar de afuera hacia adentro (aunque recomendaría usar puertos y adaptadores y hacer el ‘afuera’ del puerto), escribir pruebas para cubrir los casos de uso (escenarios, ejemplos, GWT, etc.), pero sólo escribir pruebas para cubrir los detalles de implementación de eso cuando necesitemos comprender mejor la refactorización de la implementación simple con la que comenzamos.

Un resultado positivo de esto será que gran parte de nuestra implementación será interna o privada, y nunca se expondrá fuera. Eso reducirá el acoplamiento de nuestra solución y facilitará que efectuemos el cambio.

Del libro Test-Driven Development nuevamente:

En TDD usamos la refactorización de una manera interesante. Por lo general, una refactorización no puede cambiar la semántica del programa bajo ninguna circunstancia. En TDD, las circunstancias que nos preocupan son los tests que ya están pasando.

Cuando refactorizamos, no queremos romper los tests. Si nuestros tests saben demasiado sobre nuestra implementación, será difícil refactorizar, porque los cambios en nuestra implementación necesariamente darán como resultado que reescribamos los tests, momento en el que no estamos refactorizando. Diríamos que hemos sobreespecificado a través de nuestros tests. En lugar de ayudar al cambio, nuestros tests ahora han comenzado a ser un obstaculo. Como alguien que ha cometido este error, puedo dar fé del hecho de que es posible escribir demasiados tests unitarias para testear detalles. Sin embargo, si seguimos TDD como se pensó originalmente, los tests caerán de forma natural en mayor medida en nuestra interfaz pública, no en los detalles de implementación, y nos resultará mucho más fácil cumplir con el objetivo de cambiar los detalles de impementación (cambio seguro) y no la interfaz pública (cambio inseguro). Por supuesto, esta observación, que muchos practicantes de TDD han pasado por alto esta idea, formó el base de BDD (original de Dan North) y es la razón por la que BDD se centró en escenarios y se enfoca afuera hacia adentro (inside-out) para TDD.