¿Por qué probar un idioma no es una característica compatible a nivel de sintaxis?

37

Puede encontrar una lista interminable de blogs, artículos y sitios web que promueven los beneficios de las pruebas unitarias de su código fuente. Está casi garantizado que los desarrolladores que programaron los compiladores para Java, C ++, C # y otros lenguajes mecanografiados utilizaron pruebas unitarias para verificar su trabajo.

Entonces, ¿por qué, a pesar de su popularidad, las pruebas están ausentes de la sintaxis de estos idiomas?

Microsoft introdujo LINQ en C # , entonces ¿por qué no podrían agregar también pruebas?

No estoy buscando predecir cuáles serían esos cambios de idioma, sino aclarar por qué están ausentes para empezar.

Como ejemplo: sabemos que puede escribir un forbucle sin la sintaxis de la fordeclaración. Se podría utilizar whileo if/ gotodeclaraciones. Alguien decidió que una fordeclaración era más eficiente y la introdujo en un idioma.

¿Por qué las pruebas no han seguido la misma evolución de los lenguajes de programación?

Reactgular
fuente
24
¿Es realmente mejor hacer que una especificación de lenguaje sea más compleja agregando sintaxis, en lugar de diseñar un lenguaje con reglas simples que se puedan extender fácilmente de manera genérica?
KChaloux
66
¿Qué quiere decir con "en el nivel de sintaxis"? ¿Tiene algún detalle / idea sobre cómo se implementaría?
SJuan76
21
¿Podría dar un ejemplo de pseudocódigo de prueba como una característica compatible con la sintaxis? Tengo curiosidad por ver cómo crees que se vería.
FrustratedWithFormsDesigner
12
@FrustratedWithFormsDesigner Vea el lenguaje D para ver un ejemplo.
Doval
44
Otro lenguaje con características de prueba de unidad incorporadas es Rust.
Sebastian Redl

Respuestas:

36

Al igual que con muchas cosas, las pruebas unitarias se admiten mejor a nivel de biblioteca, no a nivel de idioma. En particular, C # tiene numerosas bibliotecas de Unit Testing disponibles, así como elementos nativos de .NET Framework como Microsoft.VisualStudio.TestTools.UnitTesting.

Cada biblioteca de Unit Testing tiene una filosofía y una sintaxis de prueba algo diferentes. En igualdad de condiciones, más opciones son mejores que menos. Si las pruebas unitarias estuvieran integradas en el idioma, estaría bloqueado en las elecciones del diseñador de idiomas, o estaría usando ... una biblioteca y evitando por completo las funciones de prueba de idiomas.

Ejemplos

  • Nunit : marco de prueba de unidad de uso general diseñado idiomáticamente que aprovecha al máximo las características del lenguaje de C #.

  • Moq : marco burlón que aprovecha al máximo las expresiones lambda y los árboles de expresión, sin una metáfora de grabación / reproducción.

Hay muchas otras opciones. Las bibliotecas como Microsoft Fakes pueden crear simulacros "shims ..." que no requieren que escriba sus clases utilizando interfaces o métodos virtuales.

Linq no es una característica del idioma (a pesar de su nombre)

Linq es una función de biblioteca. Tenemos muchas características nuevas en el lenguaje C # de forma gratuita, como expresiones lambda y métodos de extensión, pero la implementación real de Linq está en .NET Framework.

Hay un poco de azúcar sintáctica que se agregó a C # para hacer que las declaraciones de linq sean más limpias, pero ese azúcar no es necesario para usar linq.

Robert Harvey
fuente
1
Estoy de acuerdo con el punto de que LINQ no es una característica del lenguaje, pero para que LINQ suceda, tenía que haber algunas características subyacentes del lenguaje, especialmente los genéricos y los tipos dinámicos.
Wyatt Barnett
@WyattBarnett: Sí, y lo mismo es cierto para las pruebas unitarias sencillas, como la reflexión y los atributos.
Doc Brown
8
LINQ necesitaba lambdas y árboles de expresión. Los genéricos ya estaban en C # (y .Net en general, están presentes en el CLR), y la dinámica no tiene nada que ver con LINQ; puedes hacer LINQ sin dinámica.
Arthur van Leeuwen
El DBIx :: Class de Perl es un ejemplo de funcionalidad similar a LINQ implementada más de 10 años después de que todas las características necesarias para implementarla hayan estado en el lenguaje.
slebetman
2
@ Carson63000 Creo que te refieres a tipos anónimos en combinación con la varpalabra clave :)
M.Mimpen
21

Hay muchas razones Eric Lippert ha declarado muchas veces que la razón feature Xno está en C # es porque simplemente no está en su presupuesto. Los diseñadores de idiomas no tienen una cantidad infinita de tiempo ni dinero para implementar cosas, y cada nueva característica tiene costos de mantenimiento asociados. Mantener el lenguaje lo más pequeño posible no solo es más fácil para los diseñadores de lenguaje: también es más fácil para cualquiera que escriba implementaciones y herramientas alternativas (por ejemplo, IDE) Además, cuando algo se implementa en términos del lenguaje en lugar de parte de él, se obtiene portabilidad gratis. Si las pruebas unitarias se implementan como una biblioteca, solo necesita escribirla una vez y funcionará en cualquier implementación conforme del lenguaje.

Vale la pena señalar que D tiene soporte de nivel de sintaxis para pruebas unitarias . No sé por qué decidieron incluir eso, pero vale la pena señalar que D está destinado a ser un "lenguaje de programación de sistemas de alto nivel". Los diseñadores querían que fuera viable para el tipo de código inseguro y de bajo nivel en el que C ++ se ha utilizado tradicionalmente, y un error en el código inseguro es increíblemente costoso: comportamiento indefinido. Así que supongo que tiene sentido para ellos gastar un esfuerzo adicional en cualquier cosa que lo ayude a verificar que algún código inseguro funcione. Por ejemplo, puede exigir que solo ciertos módulos confiables puedan realizar operaciones inseguras como accesos de matriz no verificados o aritmética de puntero.

El desarrollo rápido también era una prioridad para ellos, tanto es así que lo convirtieron en un objetivo de diseño que el código D compila lo suficientemente rápido como para que se pueda usar como lenguaje de script. Las pruebas de unidad de horneado directamente en el idioma para que pueda ejecutar sus pruebas simplemente pasando una bandera adicional al compilador ayuda con eso.

Sin embargo, creo que una gran biblioteca de pruebas unitarias hace mucho más que solo encontrar algunos métodos y ejecutarlos. Tome QuickCheck de Haskell, por ejemplo, que le permite probar cosas como "para todos los x e y f (x, y) == f (y, x)". QuickCheck se describe mejor como un generador de prueba de unidad y le permite probar cosas a un nivel más alto que "para esta entrada, espero esta salida". QuickCheck y Linq no son tan diferentes: ambos son lenguajes específicos de dominio. Entonces, en lugar de agregar el soporte de pruebas unitarias a un idioma, ¿por qué no agregar las características necesarias para que las DSL sean prácticas? Terminarás con no solo pruebas unitarias, sino un mejor lenguaje como resultado.

Doval
fuente
3
Para permanecer en el mundo .Net, está FsCheck, que es un puerto de QuickCheck a F #, con algunos ayudantes para usar desde C #.
Arthur van Leeuwen
Para quedarse en el mundo .NET? Esta pregunta no tiene nada que ver con .NET.
Miles Rout
@MilesRout C # es parte de .NET. La pregunta y la respuesta hablan de C # con frecuencia, y la pregunta incluso habla de LINQ también.
Zachary Dow
La pregunta no está etiquetada .NET o C #.
Miles Rout
@MilesRout Eso puede ser cierto, pero no se puede decir razonablemente que no tiene nada que ver solo porque no tiene la etiqueta. :)
Zachary Dow
13

Porque las pruebas, y particularmente el desarrollo basado en pruebas, es un fenómeno profundamente contra-intuitivo.

Casi todos los programadores comienzan su carrera creyendo que son mucho mejores manejando la complejidad de lo que realmente son. El hecho de que incluso el mejor programador no pueda escribir programas grandes y complejos sin errores graves a menos que utilicen muchas pruebas de regresión es muy decepcionante e incluso vergonzoso para muchos profesionales. De ahí la falta de inclinación frecuente contra las pruebas regulares, incluso entre los profesionales que ya deberían saber mejor.

Creo que el hecho de que las pruebas religiosas se estén volviendo cada vez más comunes y esperadas se debe en gran medida al hecho de que con la explosión en la capacidad de almacenamiento y la potencia informática, se están construyendo sistemas más grandes que nunca, y los sistemas extremadamente grandes son particularmente propensos al colapso de la complejidad que no se puede gestionar sin la red de seguridad de las pruebas de regresión. Como resultado, incluso los desarrolladores particularmente obstinados y engañados ahora admiten a regañadientes que necesitan pruebas y siempre necesitarán pruebas (si esto suena como la confesión en una reunión de AA, esto es bastante intencional; la necesidad de una red de seguridad es psicológicamente difícil de admitir para muchos individuos).

La mayoría de los idiomas populares actuales datan de antes de este cambio de actitud, por lo que tienen poco soporte incorporado para las pruebas: tienen assert, pero no tienen contratos. Estoy bastante seguro de que si la tendencia persiste, los futuros idiomas tendrán más soporte en el idioma que en el nivel de la biblioteca.

Kilian Foth
fuente
3
Exageras un poco tu caso. Si bien las pruebas unitarias son indudablemente de suma importancia, desarrollar programas de la manera adecuada , la forma que minimiza la complejidad innecesaria, es igualmente importante, si no más. Los lenguajes como Haskell tienen sistemas de tipos que mejoran la confiabilidad y la capacidad de prueba de los programas escritos en ellos, y las mejores prácticas en codificación y diseño evitan muchas de las trampas del código no probado, lo que hace que las pruebas unitarias sean menos críticas de lo que serían.
Robert Harvey
1
@RobertHarve ... y las pruebas unitarias son una muy buena manera de empujar indirectamente a los desarrolladores hacia eso. Trabajar en la API mientras crea pruebas, en lugar de usarla con otro código, ayuda a ponerlas en la mentalidad correcta para limitar o eliminar abstracciones con fugas o llamadas a métodos extraños. No creo que KillianFoth esté exagerando nada.
Izkata
@Robert: Lo único que creo que exagera es que "las pruebas religiosas se están volviendo cada vez más comunes" Si se define lentamente en términos de plazos geográficos, probablemente no esté muy lejos ..... :)
mattnz
+1 En mi trabajo actual, me sorprende el cambio radical en las actitudes de desarrollo con las nuevas bibliotecas y tecnologías disponibles. Por primera vez en mi carrera, estos muchachos me están enseñando a usar un proceso de desarrollo más sofisticado, en lugar de sentir que estoy en la cima de una colina gritando al viento. Estoy de acuerdo en que es solo cuestión de tiempo antes de que estas actitudes encuentren expresión en la sintaxis del idioma nativo.
Rob
1
Lo siento, un pensamiento más sobre el comentario de Robert Harvey. En este momento, los sistemas de tipos son una buena comprobación, pero realmente no llegan a definir las restricciones sobre los tipos: abordan la interfaz, pero no limitan el comportamiento correcto. (es decir, 1 + 1 == 3 se compilará, pero puede ser cierto o no, dependiendo de la implementación de +) Me pregunto si la próxima tendencia será ver sistemas de tipos más expresivos que combinen lo que consideramos pruebas unitarias ahora.
Rob
6

Muchos idiomas tienen soporte para pruebas. Las afirmaciones de C son pruebas de que el programa puede fallar. Ahí es donde se detienen la mayoría de los idiomas, pero Eiffel y más recientemente Ada 2012 tienen preinvariantes (cosas que deben pasar los argumentos de una función) y postinvariantes (cosas que debe pasar la salida de una función), con Ada ofreciendo la capacidad de hacer referencia a los argumentos iniciales en el Postinvariante. Ada 2012 también ofrece invariantes de tipo, por lo que cada vez que se llama a un método en una clase Ada, se verifica el tipo de invariante antes de regresar.

Ese no es el tipo de prueba completa que un buen marco de prueba puede brindarle, pero es un tipo importante de prueba que los idiomas pueden soportar mejor.

prosfilaes
fuente
2
Después de haber trabajado un poco en Eiffel y ser un gran usuario de afirmaciones, mi experiencia ha sido que todo lo que pueda hacer que sea de naturaleza contractual ayuda a descubrir muchos errores.
Blrfl
2

Algunos defensores de lenguajes funcionales fuertemente tipados argumentarían que estas características del lenguaje reducen o eliminan la necesidad de pruebas unitarias.

Dos, en mi humilde opinión, un buen ejemplo de esto es F # for Fun and Profit aquí y aquí.

Personalmente, sigo creyendo en el valor de las pruebas unitarias, pero hay algunos puntos válidos. Por ejemplo, si un estado ilegal no es representable en el código, entonces no solo es innecesario escribir pruebas unitarias para este caso, es imposible.

Pete
fuente
2

Diría que se ha perdido la adición de algunas de las características necesarias porque no se resaltaron como para las pruebas unitarias .

Por ejemplo, las pruebas unitarias en C # se basan principalmente en el uso de atributos. La característica Atributos personalizados proporciona un rico mecanismo de extensión que permite que marcos como NUnit iteren y compitan, con cosas como pruebas basadas en teoría y parametrizadas .

Esto me lleva a mi segundo punto principal: no sabemos lo suficiente sobre lo que hace que sea buena la capacidad de prueba para convertirlo en un idioma. El ritmo de innovación en las pruebas es mucho más rápido que otras construcciones de lenguaje, por lo que necesitamos tener mecanismos flexibles en nuestros idiomas para dejar libertad para innovar.

Soy un usuario pero no soy fanático de TDD: es muy útil en algunos casos, especialmente para mejorar su pensamiento de diseño. No es necesariamente útil para sistemas heredados más grandes: las pruebas de alto nivel con buenos datos y automatización probablemente producirán más beneficios junto con una fuerte cultura de inspección de código .

Otra lectura relevante:

Andy Dent
fuente