Hola Habr!
Durante mucho tiempo y casi sin éxito, hemos estado buscando una cabeza brillante que quiera expulsar al Sr. Kent Beck en el mercado, es decir, estamos buscando a alguien que esté listo para escribir un libro sobre TDD para nosotros. Con ejemplos reales, una historia sobre tus propios conos y logros. Hay muy pocos libros sobre este tema, y no disputarás los clásicos ... tal vez es por eso que aún no nos hemos encontrado con esta cabeza.
Por lo tanto, decidimos no solo recordarnos nuevamente que estábamos buscando a esa persona, sino también ofrecer una traducción de un artículo bastante controvertido, cuyo autor, Doug Arcuri, comparte sus propios pensamientos sobre por qué TDD no se convirtió en la corriente principal. Discutamos si tiene razón, y si no, por qué.
Esta no es una introducción al desarrollo a través de pruebas. Aquí presentaré mis propias ideas sobre el reinicio de esta disciplina y hablaré sobre las dificultades prácticas de las pruebas unitarias.El legendario programador Kent Beck es el autor de la metodología TDD (desarrollo a través de pruebas) en su sentido moderno. Kent también, junto con Erich Gamma, contribuyó a la creación de JUnit, un marco de prueba ampliamente utilizado.
En su libro
XP Explicado (segunda edición), Kent describe cómo se forman los
principios en la intersección de
valores y
prácticas . Si construyes una lista de conceptos y los sustituyes en una especie de fórmula, obtienes una transformación.
[KISS, Quality, YAGNI, ...] + [Testing, Specs, ...] == [TDD, ...]
Respeto profundamente este trabajo, que es para Kent un trabajo de toda la vida, no solo por sus obras maestras en programación, sino también por el hecho de que explora incansablemente la esencia de la
confianza , el
coraje , el
otorgamiento , la
simplicidad y la
vulnerabilidad . Todos estos atributos fueron indispensables para la invención de la Programación Extrema (XP).
TDD es un principio y
disciplina que se cumple en la comunidad XP. Esta disciplina ya tiene 19 años.
En este artículo compartiré mi opinión sobre cómo TDD logró asimilarse. Luego compartiré interesantes observaciones personales que aparecieron durante mi sesión de TDD. Finalmente, intentaré explicar por qué el TDD no disparó tan fuerte como parecía. Vamos
TDD, investigación y profesionalismo.Durante los últimos 19 años, la disciplina de TDD ha sido objeto de controversia en la comunidad de programación.
La primera pregunta que le haría un analista profesional es "¿cuál es el porcentaje de desarrolladores que usan TDD hoy?" Si le preguntaras a un amigo de Robert Martin (tío Bob) y a un amigo de Kent Beck sobre esto, la respuesta sería "100%".
Solo el tío Bob está seguro de que
es imposible considerarse un profesional si no practica el desarrollo a través de las pruebas .
El tío Bob ha estado muy involucrado en esta disciplina durante varios años, por lo que es natural prestarle atención en esta revisión. El tío Bob defendió TDD y expandió significativamente los límites de esta disciplina. Puede estar seguro de que tengo el mayor respeto por el tío Bob y su dogmatismo pragmático.
Sin embargo, nadie hace la siguiente pregunta: "después de todo, practicar significa" usar conscientemente ", pero no permite juzgar el porcentaje, ¿verdad?" En mi opinión subjetiva, la mayoría de los programadores
no trataron con TDD ni siquiera por ningún período simbólico.
La realidad es que realmente no conocemos estos números, ya que nadie ha investigado activamente este porcentaje. Todos los datos específicos se limitan a una pequeña selección de empresas recopiladas en el
sitio web
WeDoTDD . Aquí encontrará estadísticas sobre tales empresas, entrevistas con quienes practican TDD todo el tiempo, pero esta lista no es grande. Además, está incompleto, porque incluso una simple búsqueda revela otras grandes organizaciones involucradas en TDD, pero, tal vez, no a plena capacidad.
Si no sabemos cuántas empresas practican TDD, surge la siguiente pregunta: "¿Qué tan efectiva es TDD, a juzgar por sus méritos medibles"?
Probablemente le agradará que a lo largo de los años se hayan realizado una serie de estudios que confirman la efectividad de la TDD. Entre ellos se encuentran definitivamente informes autorizados de
Microsoft ,
IBM , la Universidad de Carolina del Norte y la
Universidad de Helsinki .
Un diagrama expresivo tomado de un informe de la Universidad de Helsinki.Hasta cierto punto, estos informes prueban que la densidad del error puede reducirse en un 40-60%, lo que requiere más trabajo; El tiempo de ejecución aumenta en un 15-35%. Estos números ya están comenzando a rastrearse en libros y nuevas metodologías industriales, en particular en la comunidad DevOps.
Respondiendo parcialmente estas preguntas, pasamos a la última: "¿En qué puedo contar cuando empiezo a practicar TDD?" Fue por la respuesta que formulé mis observaciones personales de TDD. Pasemos a ellos.
1. TDD requiere un enfoque verbalizadoCuando practicamos TDD, comenzamos a encontrarnos con el fenómeno de la "designación de objetivos". En pocas palabras, los proyectos breves como preparar pruebas fallidas y exitosas son un serio desafío intelectual para el desarrollador. El desarrollador tiene que articular claramente: "Creo que esta prueba tendrá éxito" y "Creo que esta prueba fallará" o "No estoy seguro, déjenme reflexionar después de probar este enfoque".
El IDE se ha convertido para el desarrollador en ese pato de goma que ruega por hablar activamente con ella. Como mínimo, en las empresas TDD, las conversaciones de este tipo deberían fusionarse en un zumbido continuo.
Piensa primero, y luego da tu próximo paso (o pasos).
Tal refuerzo juega un papel clave en la comunicación: le permite no solo predecir su próximo paso, sino también estimularlo a escribir el código
más simple para asegurarse de que la prueba de la unidad pase. Por supuesto, si el desarrollador guarda silencio, entonces seguramente perderá su curso, después de lo cual tendrá que volver a la pista.
2. TDD bombea memoria del motorEl desarrollador, que avanza en sus primeros ciclos TDD, se cansa rápidamente; después de todo, este proceso es inconveniente y se detiene constantemente. Esta es una situación común con cualquier actividad que una persona recién comienza, pero que aún no ha dominado. El desarrollador recurrirá a atajos, tratando de optimizar este ciclo para llenar su mano y mejorar la memoria del motor.
La memoria motora es indispensable para que el trabajo sea divertido y funcione como un reloj. En TDD, esto es necesario debido a la repetición de acciones.
Obtenga la hoja de trucos con estos atajos. Aproveche al máximo los atajos de teclado en su IDE para que los bucles sean efectivos. Entonces sigue buscando.
En solo unas pocas sesiones, el desarrollador domina perfectamente la selección de accesos directos, en particular, unas pocas sesiones son suficientes para ensamblar y ejecutar un banco de pruebas. Cuando practique crear nuevos artefactos, resaltar texto y navegar a través del IDE, todo esto le parecerá completamente natural. Finalmente, se convertirá en un verdadero profesional y dominará todas las técnicas de refactorización: en particular, extracción, cambio de nombre, generación, elevación, reformateo y descenso.
3. TDD requiere al menos un poco de reflexión sobre sus acciones de antemanoCada vez que un desarrollador piensa en iniciar un TDD, debe tener en cuenta un breve mapa mental de las tareas que deben resolverse. En el enfoque tradicional de la programación, dicho mapa no siempre está ahí, y la tarea en sí misma puede presentarse "a nivel macro" o tener una naturaleza de investigación. Tal vez el desarrollador no sabe cómo resolver el problema, pero solo imagina aproximadamente el objetivo. Las pruebas unitarias se descuidan hacia este objetivo.
Mientras está sentado en el trabajo y termina con otro "sentarse", también intente hacer un ritual con esto. Piensa y escribe primero. Juega con eso. Lista más. Entonces proceda, haga, piense. Celebrar Repite varias veces. Luego piensa de nuevo y detente.
Sea inflexible sobre el trabajo. Haga un seguimiento de lo que ya se ha hecho: marque las casillas. Nunca doble hasta que haya al menos uno. Piensa!
Quizás la redacción de la lista llevará algún tiempo que no se ajuste al ciclo de trabajo. Sin embargo, antes de comenzar, debe tener una lista. Sin ella, no sabes a dónde te estás moviendo. En ninguna parte sin una tarjeta.
// // "" -> // "a" -> // "aa" -> // "racecar" -> // "Racecar" -> // //
El desarrollador debe
enumerar las pruebas como lo describe Kent Beck. La lista de prueba le permite resolver el problema en forma de ciclos que se transmiten sin problemas. Por encima de la lista de pruebas, debe procesar y actualizar constantemente, aunque solo sea unos segundos antes de las pruebas. Si la lista de prueba se pasa casi por completo menos la última etapa, el resultado es "rojo" y la prueba completa falla.
4. TDD depende de la comunicación con colegasUna vez completada la lista anterior, algunos pasos pueden bloquearse porque no describen con claridad qué hacer. El desarrollador no entendió la lista de pruebas. También sucede lo contrario: la lista es demasiado cruda, en la que hay muchos supuestos sobre los requisitos que aún no se han formulado. Si obtienes algo como esto, detente de inmediato.
Actuar sin TDD puede resultar en implementaciones demasiado complejas. Trabajar al estilo de TDD, pero sin pensar, sin una lista, no es menos peligroso.
Si ve que hay vacíos en la lista de prueba, levántese y dígalo en voz alta.
En TDD, el desarrollador debe comprender qué producto hacer, guiándose por la idea de los requisitos necesarios en la interpretación del propietario, y nada más. Si el requisito en este contexto no está claro, entonces la lista de pruebas comienza a desmoronarse. Este fracaso necesita ser discutido. Una discusión tranquila rápidamente ayuda a generar confianza y respeto. Además, así de rápido se forman los bucles de retroalimentación.
5. TDD requiere una arquitectura iterativaEn la primera edición de su libro XP, Kent sugirió que las pruebas deberían ser la fuerza impulsora detrás de la arquitectura. Sin embargo, en el transcurso de varios años, han aparecido historias sobre cómo los equipos de sprint tropiezan con una pared en algunos sprints.
Por supuesto, construir una arquitectura basada en pruebas es irracional. El propio tío Bob estuvo de acuerdo con otros expertos en que no era bueno. Se requiere un mapa más extenso, pero no muy lejos de las listas de prueba que desarrolló "en el campo".
Muchos años después, Kent también expresó esta tesis en su libro
TDD By Example .
La competitividad y la
seguridad son dos áreas principales en las que TDD no puede ser la fuerza impulsora, y el desarrollador debe tratarlas por separado. Podemos decir que la competitividad es un nivel diferente de diseño del sistema, la competitividad debe desarrollarse mediante iteraciones, coordinando este proceso con TDD. Esto es especialmente cierto hoy, ya que algunas arquitecturas están evolucionando hacia un
paradigma reactivo y extensiones reactivas (la
reactividad es competitividad en su cenit).
Construya un mapa más grande de toda la organización. Ayudando a ver las cosas un poco en perspectiva. Asegúrese de que usted y el equipo se estén moviendo en el mismo curso.
Sin embargo, la idea más importante es la
organización de todo el sistema, y no se proporciona una organización TDD. El hecho es que las pruebas unitarias son algo de bajo nivel. La arquitectura iterativa y la orquestación de TDD son complejas en la práctica y requieren confianza entre todos los miembros del equipo, programación de pares y revisiones de código sólido. No está del todo claro cómo lograr esto, pero pronto puede ver que se deben realizar breves sesiones de diseño al unísono con la implementación de listas de pruebas en el área temática.
6. TDD revela la fragilidad de las pruebas unitarias y la implementación degeneradaLas pruebas unitarias tienen una característica divertida, y TDD la delata por completo. No permiten probar la corrección. E.V.Dijkstra trabajó en este problema y discutió cómo la prueba matemática es posible en nuestro caso para llenar este vacío.
Por ejemplo, en el siguiente ejemplo, se resuelven todas las pruebas relacionadas con un hipotético palíndromo imperfecto dictado por la lógica empresarial. Se desarrolla un ejemplo utilizando la metodología TDD.
// @Test fun `Given "", then it does not validate`() { "".validate().shouldBeFalse() } @Test fun `Given "a", then it does not validate`() { "a".validate().shouldBeFalse() } @Test fun `Given "aa", then it validates`() { "aa".validate().shouldBeTrue() } @Test fun `Given "abba", then it validates`() { "abba".validate().shouldBeTrue() } @Test fun `Given "racecar", then it validates`() { "racecar".validate().shouldBeTrue() } @Test fun `Given "Racecar", then it validates`() { "Racecar".validate().shouldBeTrue() }
De hecho, hay fallas en estas pruebas. Las pruebas unitarias son frágiles incluso en los casos más triviales. Nunca ha sido posible demostrar su corrección, porque si lo intentáramos requeriría un trabajo mental increíble, y la entrada requerida para esto sería imposible de imaginar.
// , fun String.validate() = if (isEmpty() || length == 1) false else toLowerCase() == toLowerCase().reversed() // , fun String.validate() = length > 1 length > 1
length > 1
puede llamarse una
implementación degenerada . Es suficiente resolver la tarea, pero en sí misma no informa nada sobre el problema que estamos tratando de resolver.
La pregunta es: ¿cuándo debería un desarrollador dejar de escribir pruebas? La respuesta parece simple: cuando es
suficiente desde el punto de vista de la lógica empresarial , y no según el autor del código. Puede dañar nuestra
pasión por el diseño , y la simplicidad puede
enojar a la gente . Estos sentimientos se compensan con la satisfacción al ver nuestro propio código limpio y el entendimiento de que posteriormente el código puede ser refactorizado con confianza. Todo el código será muy ordenado.
Tenga en cuenta que para toda la falta de fiabilidad, las pruebas unitarias son necesarias. Comprender sus fortalezas y debilidades. Si la imagen completa no cuadra, quizás esta brecha ayudará a completar las
pruebas de mutación .
TDD tiene sus beneficios, pero esta metodología puede distraernos de construir castillos de arena innecesarios. Sí, esta es una
limitación , pero gracias a ella, puedes moverte más rápido, más lejos y de manera más confiable. Quizás esto es lo que tío Bob tenía en mente al describir lo que, desde su punto de vista, significa
ser un profesional .
Pero! No importa cuán frágiles nos parezcan las pruebas unitarias, son absolutamente imprescindibles. Son ellos quienes convierten el
miedo en
coraje . Las pruebas proporcionan una refactorización de código suave; Además, pueden servir como
guía y
documentación para cualquier desarrollador nuevo que pueda ponerse en marcha de inmediato y trabajar en beneficio del proyecto, si este proyecto está bien cubierto por las pruebas unitarias.
7. TDD demuestra el bucle inverso de las declaraciones de pruebaDa un paso más allá. Para comprender los siguientes dos fenómenos, estudiamos extraños eventos recurrentes. Para comenzar, echemos un vistazo rápido a FizzBuzz. Aquí está nuestra lista de pruebas.
// 9 15. [OK] // , 3, Fizz . // ...
Dimos unos pasos hacia adelante. Ahora nuestra prueba falla.
@Test fun `Given numbers, replace those divisible by 3 with "Fizz"`() { val machine = FizzBuzz() assertEquals(machine.print(), "?") } class FizzBuzz { fun print(): String { var output = "" for (i in 9..15) { output += if (i % 3 == 0) { "Fizz " } else "${i} " } return output.trim() } } Expected <Fizz 10 11 Fizz 13 14 Fizz>, actual <?>.
Naturalmente, si
assertEquals
datos de aserción esperados en
assertEquals
, se logra el resultado deseado y se realiza la prueba.
A veces, las pruebas fallidas dan el resultado correcto necesario para pasar la prueba. No sé cómo nombrar tales eventos ... tal vez
pruebas de vudú . La cantidad de veces que ve esto, en parte depende de su pereza y etiqueta cuando realiza las pruebas, pero he notado esas cosas muchas veces cuando una persona intenta obtener una implementación que funcione normalmente con conjuntos de datos predecibles y listos para usar.
8. TDD muestra la secuencia de transformacionesTDD puede atraparte. Sucede que el desarrollador se confunde en las transformaciones realizadas por él mismo, que utiliza para lograr la implementación deseada. En algún momento, el código de prueba se convierte en un cuello de botella donde nos detenemos.
Se forma un
callejón sin salida . El desarrollador tiene que retroceder y desarmarse, eliminando algunas de las pruebas para salir de esta trampa. El desarrollador permanece desprotegido.
El tío Bob probablemente caería en tales puntos muertos a lo largo de los años de su carrera, después de lo cual aparentemente se dio cuenta de que para pasar la prueba, tenía que establecer la secuencia correcta de acciones para minimizar la probabilidad de entrar en un callejón sin salida. Además, tuvo que darse cuenta de otra condición.
Cuanto más específicas se vuelven las pruebas, más generalizado es el código .
La secuencia de transformaciones. Siempre debe luchar por la opción más simple (en la parte superior de la lista).Esta es la
condición de prioridad de transición . Aparentemente, existe un cierto orden de riesgo de refactorización, que estamos listos para alcanzar al pasar la prueba. Por lo general, es mejor elegir la opción de conversión que se muestra en la parte superior de la lista (la más simple); en este caso, la probabilidad de entrar en un callejón sin salida sigue siendo mínima.
TPP o
el análisis de prueba del tío Bob , por así decirlo, es uno de los fenómenos más intrigantes, tecnológicos y emocionantes que se observan actualmente.
Úselo para mantener su código lo más simple posible.
Imprima la lista de TPP y colóquela en su escritorio. Consulte con él para evitar entrar en callejones sin salida. Que sea una regla: el orden debe ser simple.
Esto concluye la historia de mis observaciones principales. Sin embargo, en la parte final del artículo, me gustaría volver a la pregunta que olvidamos responder al principio: "¿Cuál es el porcentaje de programadores profesionales que usan TDD hoy?" Yo respondería: "Creo que hay pocos de ellos". Me gustaría investigar esta pregunta a continuación y tratar de explicar por qué.
¿TDD está arraigado en la práctica?Lamentablemente no. Subjetivamente, parece que el porcentaje de sus partidarios es bajo, y sigo buscando datos. Mi experiencia en reclutamiento, liderazgo de equipo y autodesarrollo (que me fascina) me permite hacer las siguientes observaciones.
Razón 1: falta de contacto con la cultura de prueba realPuedo suponer razonablemente que la mayoría de los desarrolladores no tuvieron la oportunidad de aprender y trabajar en una
cultura de prueba real.
La cultura de prueba es un entorno en el que los desarrolladores practican y mejoran conscientemente en el arte de las pruebas. Capacitan constantemente a colegas que aún no tienen suficiente experiencia en este campo. La retroalimentación se ha establecido en cada solicitud de par y grupo que ayuda a todos los participantes a desarrollar habilidades de evaluación. Además, existe un apoyo serio y una sensación de codo en toda la jerarquía de ingenieros. Todos los gerentes entienden la esencia de las pruebas y creen en ellas. Cuando los plazos comienzan a agotarse, la disciplina de prueba no se descarta, pero se sigue.
Aquellos que tuvieron la suerte de probarse a sí mismos en una cultura de pruebas de este tipo, como por ejemplo, tuve la oportunidad de hacer tales observaciones. .
2:TDD, ,
xUnit Patterns Effective Unit Testing . , -, , , . .
. , . . , , , … .
3:: , , . , , ; - , – .
4:, , TDD . , .
, , : « , ». : , « » — .
– .
ConclusiónXP –
,
. – , . TDD.
, , . «» , , – , .
XP Explained. , , ., - .
, – , . , , , .
, , , .
TDD « » , . TDD . TDD .
. TDD , . TDD — , , . , TDD , . , .
@Test fun `Given software, when we build, then we expect tests`() { build(software) shoudHave tests }
, TDD – ,
,
. . , , , .