que - ¿Cómo se/obligaría usted a hacer TDD en lugar de TAD?
tdd vs bdd (12)
Programación de par
Me doy cuenta de que esto puede no ser una opción para todos, y que a muchos desarrolladores no les gusta esta idea. Pero he descubierto que si sincronizo el programa con alguien que también está comprometido con TDD, tendemos a "mantenernos honestos unos a otros" y permanecer con TDD mucho más de lo que podía programar solo por voluntad propia.
He estado tratando de subir al carro TDD desde hace un tiempo, y ha ido bien, excepto por una cosa crucial, normalmente lo que termino haciendo es probar después del desarrollo.
Necesito un cambio mental y me pregunto cómo te obligaste a escribir pruebas primero?
Ayuda si tienes un marco de prueba genérico.
Tenga una biblioteca de funciones genéricas aplicable a varios tipos de pruebas que ejecuta. Luego, reutilícelos como bloques de construcción para construir pruebas para el proyecto en el que se encuentra.
Para llegar allí, tenga en cuenta las cosas comunes que hace en las pruebas que escribe después . Resúmalos en la biblioteca generalizada uno por uno.
Hacerlo le permitirá hacer muchas pruebas más simples muy rápidamente sin tener que volver a hacer el aburrido y lento código de prueba del controlador, en lugar de concentrarse en los casos de prueba reales.
Haga el enfoque de "prueba como documentación". No agregue / cambie ninguna frase en la documentación que no esté respaldada por las pruebas apropiadas.
Esto ahorra tiempo, no tiene que volver a analizar la documentación / requisitos otro tijme solo para construir las pruebas más tarde, así como también ayuda con el cambio mental sobre el que preguntó.
Realice la incorporación progresiva gradual: agregue pruebas para las nuevas características / cambios a medida que comienzan a trabajar.
A nadie le gusta cambiar sus formas fría pavo - la naturaleza humana. Deje que el hábito bueno se deslice y eventualmente se convierta en una segunda naturaleza.
Inmediatamente reserve el tiempo para escribir pruebas al comienzo de su cronograma de desarrollo en un proyecto
Esto lo forzará a adoptar los hábitos adecuados (suponiendo que usted cumpla con su plan de proyecto) y lo protegerá contra las fechas de vencimiento debido al tiempo "extra" dedicado a la construcción de pruebas.
Por supuesto, el tiempo "extra" para TDD termina ahorrando tiempo neto, pero no siempre se realiza en la etapa inicial del proyecto, lo que ejerce una presión negativa sobre la práctica de TDD ("¿Dónde están las capturas de pantalla del prototipo?" ¿quieres decir que todavía estás escribiendo pruebas? ").
Además, trate de seguir las prácticas recomendadas habituales de clases y funciones pequeñas y de un solo propósito. Esto, entre todos los otros beneficios, permite una escritura de prueba de unidad mucho más fácil. Combínalo con el # 2 (al escribir pruebas unitarias como parte de la documentación API, al diseñar la API), y tus diseños API mejoran "mágicamente", ya que comienzas a notar los puntos débiles inmediatamente debido a que escribes las pruebas basadas en ellos. Como señalaron otras personas, el uso de algún tipo de patrón / marco de Inyección de Dependencia ayuda a simplificar la construcción de las pruebas.
¡Lee el código de prueba!
Lo que me bloqueó de las pruebas primero es la falta de perspicacia al tratar de recrear el entorno en el que ese módulo debe ejecutarse dentro de un arnés de prueba.
Para superar estas dificultades, debe leer el código de prueba de otros programadores y aplicar ese código a sus necesidades. De la misma manera que lo hace cuando está aprendiendo a programar usando un nuevo idioma o una nueva biblioteca.
Aunque nunca pudimos implementar un TDD completo, el concepto de informar un defecto y crear un caso de prueba de phpunit (en nuestro taller php) que falló, pero que pasaría cuando se resolvió el defecto, demostró ser deseable para todas las partes (dev y qa) debido a la claridad de la especificación del defecto y la verificación del código modificado. También incorporamos estas pruebas unitarias en el conjunto de regresión, en caso de que no encontremos estas ramas de "arreglo defectuoso" en el código publicado.
Como desarrollador solo, una cosa que me ayudó a hacer el cambio a TDD fue establecer un umbral de cobertura de código para mí.
En mi secuencia de comandos de compilación, utilicé una herramienta de cobertura de código (NCover) para determinar el porcentaje de código cubierto por las pruebas e inicialmente establecí el umbral en 80%. Si dejé de escribir mis pruebas primero, el porcentaje de cobertura caería por debajo del umbral del 80% y mi secuencia de comandos de compilación provocaría un error. Luego, inmediatamente me abofeteaba la muñeca y escribía las pruebas faltantes.
Gradualmente incrementé el umbral de cobertura de código y eventualmente me convertí en TDD completo.
El cambio mental para mí fue darse cuenta de que TDD se trata de diseño, no de prueba. TDD te permite razonar críticamente sobre la API de lo que estás construyendo. Escriba las pruebas primero y, a menudo, es muy claro qué API es más conveniente y apropiada. Luego escribe tu implementación.
Por supuesto, también deberías escribir pruebas (pruebas de regresión, pruebas de integración, etc.). TDD a menudo produce un buen diseño, pero no necesariamente un buen código de prueba.
Llegué un gran momento para mí con TDD cuando leí una cita (no recuerdo dónde) que el momento del triunfo para una prueba es el momento en que la prueba cambia de rojo a verde .
Probablemente esto sea todo lo que ha leído antes, pero a menos que comience con una prueba fallida, y se convierta en una prueba aprobada, es cuando obtengo los enormes beneficios psicológicos. Se siente bien cambiar de rojo a verde. Y si eres coherente con entregar ese momento a ti mismo, se vuelve adictivo, y luego mucho más fácil hacerlo tú mismo.
Pero el truco para mí fue aislar ese momento y deleitarme con él.
Lo que me ayudó a inculcar la disciplina habitual fue, antes de hacer cualquier cambio, decirme a mí mismo: "¿Qué prueba debo escribir para demostrar que el cambio funcionó?". Si bien no es estrictamente TDD (dado que el enfoque es bastante pequeño), puso las pruebas al frente de mi pensamiento cada vez que se cambiaba el sistema.
Comience poco a poco, con una barrera baja para entrar, practique diariamente y el hábito se convierte en una segunda naturaleza. Después de un tiempo, su alcance para pensar en las pruebas se amplía naturalmente para incluir el diseño y la integración y las pruebas del sistema.
Descubrí que "empezar desde cero" funcionaba bien en los proyectos heredados que tenían pocas pruebas de unidad y donde la intertia de llevarlo hasta cero era demasiado grande para que nadie se molestara. Pequeños cambios, correcciones de errores, etc. a menudo podían ser fácilmente probados en unidades incluso cuando el paisaje de prueba para todo el proyecto era bastante estéril.
Nuestro TDD impulsa el desarrollo, desde el nombre. Se aprende mejor de alguien que ya es extremo / disciplinado al respecto. Si su velocidad afectó la aplicación de TDD inmediatamente en el proyecto de trabajo, ¿qué le impide crecer sus músculos TDD fuera del trabajo en un proyecto paralelo?
Aquí hay un resumen de cómo me convertí en una conversión BDD / TDD:
Hace un año, tenía poca idea de cómo hacer TDD (pero realmente quería (qué frustrante)) y nunca había escuchado sobre BDD ... ahora hago ambas compulsivamente. He estado en un entorno de desarrollo .Net, no en Java, pero incluso reemplacé el botón "F5 - Ejecutar" con una macro para ejecutar Cucumber (BDD) o MBUnit (TDD) dependiendo de si se trata de una característica / escenario o especificación. Sin depurador si es posible. $ 1 en el jar si usa el depurador (JOKING (tipo de)).
El proceso es muy impresionante. El marco que estamos usando adicionalmente es el Oracle que he tenido la bendición de encontrar, y la absorción de información de, y el marco que él / nosotros usamos es MavenThought.
Todo comienza con BDD. Nuestro BDD es de pepino recto de rubí de hierro.
Característica:
Escenario: .... Dado que hago bla ...
Cuando hago otra cosa ... Entonces suceden cosas maravillosas ...
Escenario: ...
Y eso no es una prueba unitaria, sino que maneja la función, escenario por escenario y, a su vez, las especificaciones de la unidad (prueba). Así que comienzas en un escenario, y con cada paso necesitas completar en el escenario que maneja tu TDD .
Y el TDD que hemos estado usando es una especie de BDD de alguna manera, porque observamos los comportamientos que el SUT (Sistema Bajo Prueba) requiere y se especifica un comportamiento por especificación (archivo de "prueba" de clase).
Ejemplo:
Aquí está la especificación de un comportamiento: cuando se crea el sistema bajo prueba.
Existe una especificación más (C # When_blah_happens class file) para otro comportamiento cuando una propiedad cambia, pero se separa en otro archivo.
using MavenThought.Commons.Testing;
using SharpTestsEx;
namespace Price.Displacement.Module.Designer.Tests.Model.Observers
{
/// <summary>
/// Specification when diffuser observer is created
/// </summary>
[ConstructorSpecification]
public class When_diffuser_observer_is_created
: DiffuserObserverSpecification
{
/// <summary>
/// Checks the diffuser injection
/// </summary>
[It]
public void Should_return_the_injected_diffuser()
{
Sut.Diffuser.Should().Be.SameInstanceAs(this.ConcreteDiffuser);
}
}
}
Este es probablemente el comportamiento más simple para un SUT, porque en este caso cuando se crea, la propiedad Difusor debe ser la misma que el difusor inyectado. Tuve que usar un difusor de concreto en lugar de un simulacro porque en este caso el difusor es un objeto núcleo / dominio y no tiene notificación de propiedad para la interfaz. El 95% de las veces nos referimos a todas nuestras dependencias como Dep (), en lugar de referirnos a algo real.
A menudo tenemos más de uno [It] Should_do_xyz (), y algunas veces un poco de configuración como quizás hasta 10 líneas de stubbing. Este es solo un ejemplo muy simple sin GivenThat () o AndGivenThat AfterCreated () en esa especificación.
Para la configuración de cada especificación, en general, solo necesitamos anular un par de métodos de la especificación:
GivenThat () ==> esto sucede antes de que se cree el SUT.
CreatSut () ==> Automáticamente simulamos la creación del sut con StructureMap y el 90% de las veces nunca necesitamos anular esto, pero si usted es un constructor que inyecta un concreto, debe anularlo.
AndGivenThatAfterCreated () => esto sucede después de que se crea el SUT.
WhenIRun () => a menos que sea una [ConstructorSpecification] usamos esto para ejecutar UNA línea de código que es el comportamiento que estamos especificando para el SUT
Además, si hay un comportamiento común de dos o más especificaciones del mismo SUT, lo trasladamos a la especificación de base.
Todo lo que tengo que hacer para ejecutar la Especificación es resaltar su nombre, ejemplo "When_diffuser_observer_is_created" y presionar F5, porque recuerde, para mí F5 ejecuta una tarea de Rake o prueba: feature [tag] if Cucumber, o test: class [SUT]. Tiene sentido para mí porque cada vez que ejecuta el depurador está a un tiro de distancia, no se crea código (ah, y cuesta $ 1 (bromeando)).
Esta es una manera muy, muy limpia de especificar el comportamiento con TDD y tener SUT realmente simples y especificaciones simples. Si tratas de ser un codificador de vaqueros y escribes un SUT con dependencias duras, etc., sentirás el dolor de tratar de hacer TDD y te cansarás / te rendirás, o morderás la bala y lo harás bien.
Y aquí está el SUT real. Tenemos un poco de fantasía y usamos PostSharp para agregar notificación de propiedad modificada en el Difusor, por lo tanto, el Post.Cast <>. Y nuevamente, es por eso que inyecté un Concreto en lugar de Simulacro. De todos modos, como puede ver, el comportamiento perdido que se define en otra especificación es cuando algo cambia en el difusor.
using System.ComponentModel;
using MavenThought.Commons.Events;
using PostSharp;
using Price.Displacement.Core.Products;
using Price.Displacement.Domain;
namespace Price.Displacement.Desktop.Module.Designer.Model.Observers
{
/// <summary>
/// Implementation of current observer for the selected product
/// </summary>
public class DiffuserObserver : AbstractNotifyPropertyChanged, IDiffuserObserver
{
/// <summary>
/// gets the diffuser
/// </summary>
public IDiffuser Diffuser { get; private set; }
/// <summary>
/// Initialize with a diffuser
/// </summary>
/// <param name="diffuser">The diffuser to observe</param>
public void Initialize(IDiffuser diffuser)
{
this.Diffuser = diffuser;
this.NotifyInterface().PropertyChanged += (x, e) => this.OnPropertyChanged(e.PropertyName);
}
/// <summary>
/// Gets the notify interface to use
/// </summary>
/// <returns>The instance of notify property changed interface</returns>
protected INotifyPropertyChanged NotifyInterface()
{
return Post.Cast<Diffuser, INotifyPropertyChanged>((Diffuser)Diffuser);
}
}
}
En conclusión, este estilo de desarrollo de BDD / TDD es fantástico. Me tomó un año pero soy un converso total como una forma de vida. No habría aprendido esto por mi cuenta. Recogí todo de The Oracle http://orthocoders.com/ .
Pastilla roja o azul, la elección es tuya.
Para mí, todo se trataba de darme cuenta de los beneficios. Reparar errores después del hecho es mucho más difícil que nunca escribirlos en primer lugar.
la forma más fácil de comenzar, imo, es comenzar con un nuevo componente. TDD, y las pruebas de unidades efectivas en general, requieren que usted arme su código de una manera que permita realizar pruebas sin dependencias (lo que significa que necesita tener interfaces para implementaciones simuladas de objetos, etc.). en cualquier pieza compleja de software esto tiene implicaciones reales en la estructura de su código.
TDD funciona mejor y mejor cuanto más experiencia tengas con él. Pero es difícil alcanzar el nivel de experiencia de punto de equilibrio que hace que sea más fácil hacer tdd en lugar de tad.
por lo que la pregunta opuesta: "Lo que me impide hacer tdd" puede ayudar a obtener un buen punto de partida:
- Presión de tiempo si no tengo experiencia con tdd
- corrección de errores o mejora de aplicaciones que no se desarrollaron en TestDriven o donde todavía no hay pruebas de unidades (aplicaciones de campo marrón).
- entornos rápidos de desarrollo de aplicaciones donde cambio algún código y puedo (casi) ver de inmediato si esto funciona usando la interfaz gráfica de usuario final.
la motivación viene para mí si los beneficios indignan el dolor.
Actualmente estoy desarrollando partes en una tienda java / tomcat / web donde compilar todo y comenzar los servidores / tienda requiere aproximadamente 10 minutos, lo cual es opuesto al desarrollo rápido de aplicaciones.
La compilación y ejecución de businesslogic y unittest requiere menos de 10 segundos.
De modo que hacer tdd es mucho más rápido que tad, siempre que sea fácil escribir la prueba unitaria.
el mismo rad se aplica a mi proyecto actual de Android donde desarrollo la parte de lib de Java independiente de Android que se puede probar de forma sencilla. No es necesario implementar código en el dispositivo para ver si el código se está ejecutando.
En mi opinión, es un buen punto de partida para tener más experiencia con tdd: espere una nueva aplicación de campo verde donde no tenga presión de tiempo al principio.
Una vez que comencé a aprovechar la inyección de dependencia, mis clases se volvieron más pequeñas y más especializadas, lo que me permitió escribir pruebas simples de unidad para confirmar que funcionaban. Dado el número limitado de pruebas que sabía que mi clase tenía que pasar al trabajo, el objetivo de mi esfuerzo TDD se hizo más claro. También fue más fácil identificar qué clases requerían pruebas de integración debido a las dependencias de los recursos externos y cuáles clasificaron las pruebas unitarias requeridas que inyectaron objetos falsos / trozos / falsos en el SUT para reducir el enfoque de mi prueba.