Cómo mejorar en la prueba de su propio código

45

Soy un desarrollador de software relativamente nuevo, y una de las cosas que creo que debería mejorar es mi capacidad de probar mi propio código. Cada vez que desarrollo una nueva funcionalidad, me resulta muy difícil seguir todos los caminos posibles para poder encontrar errores. Tiendo a seguir el camino donde todo funciona. Sé que este es un problema bien conocido que tienen los programadores, pero no tenemos evaluadores en mi empleador actual y mis colegas parecen ser bastante buenos en esto.

En mi organización, no realizamos ni pruebas de desarrollo ni pruebas unitarias. Me ayudaría mucho, pero no es probable que esto cambie.

¿Qué creen que podría hacer para superar esto? ¿Qué enfoque utilizas cuando pruebas tu propio código?

Fran Sevillano
fuente
28
El hecho de que su organización no use TDD o pruebas unitarias no significa que no pueda hacerlo, siempre y cuando continúe cumpliendo con sus plazos y produzca un código de calidad.
Thomas Owens
1
Supongo que Thomas me ganó, pero estoy en una situación similar. Escribo expectativas de muy alto nivel sobre lo que debe hacer un cambio de código, y escribo pruebas unitarias si o cuando puedo (aunque nuestra empresa no realiza pruebas unitarias oficialmente). No tiene que comprometerlos, y son excelentes maneras de aprender cómo se supone que deben actuar las funciones (o deberían actuar después de que las arregle).
Brian
66
@Brian, creo que deberías comprometerlos independientemente de si otros los usan actualmente. Quizás mostrar buenas prácticas hará que otros lo sigan.
CaffGeek

Respuestas:

20

El trabajo de un codificador es construir cosas.

El trabajo de un probador es romper cosas.

Lo más difícil es romper cosas que acabas de construir. Solo tendrá éxito al superar esta barrera psicológica.

Mouviciel
fuente
27
-1 El trabajo de un codificador es construir cosas que funcionen . Eso siempre implica cierta cantidad de pruebas. Estoy de acuerdo en que es necesario un rol de probador separado, pero no es la única línea de defensa.
Matthew Rodatus
8
@Matthew Rodatus: la cantidad de pruebas involucradas en el lado del codificador solo tiene como objetivo verificar que lo que debería funcionar realmente funcione. En el lado del probador, el objetivo es encontrar errores, no observar que el código funciona.
mouviciel
2
Eso es diferente de su respuesta, y estoy más de acuerdo con eso. Pero, todavía no estoy completamente de acuerdo. Escribir código de calidad viene a través de la práctica a medida que aprende a pensar, con anticipación, las posibilidades de fracaso. No aprendes a pensar en las posibilidades del fracaso sin aprender a crear esas posibilidades. No creo que los codificadores deberían ser la única línea de defensa, pero deberían ser la primera línea de defensa. El tema en juego es uno de minuciosidad y dominio del oficio.
Matthew Rodatus
2
@mouviciel - falsa dicotomía. El trabajo de un codificador es construir cosas que funcionen, y lo hace pensando a priori en qué condiciones se supone que su código debe funcionar. Y esto se verifica, al menos, mediante la creación de pruebas destructivas + algún análisis de límites ad-hoc (de nuevo, al menos ). Además, un buen codificador funciona en contra de las especificaciones, y las especificaciones (cuando son válidas) siempre son verificables. Por lo que un buen programador desarrolla prueba que verifican estos requisitos se cumplen en el código (y normalmente lo hacen escribiendo req pruebas que fallan inicialmente hasta que tenga código que pasa las pruebas..)
luis.espinal
2
@Dave Lasley: este es precisamente mi punto: el arquitecto no es la mejor persona para derribar su casa: está demasiado orgulloso de lo fuerte que es poder ver sus defectos. Solo otro arquitecto (no el chico de la calle) puede poner un ojo objetivo en la casa y descubrir que la casa puede romperse bajo ciertas condiciones específicas que el antiguo arquitecto era demasiado ciego para imaginar.
mouviciel
14

Franciso, voy a hacer algunas suposiciones aquí, en base a lo que has dicho:

"No hacemos ni TDD ni pruebas unitarias. Me ayudaría mucho, pero no es probable que esto cambie".

A partir de esto, sospecho que su equipo no le da mucho valor a las pruebas o la administración no le dedicará tiempo al equipo para tratar de ordenar el código existente y mantener la deuda técnica al mínimo.

En primer lugar, debe convencer a su equipo / administración del valor de las pruebas. Sé diplomático Si la administración mantiene a su equipo avanzando, debe mostrarle algunos datos, como la tasa de defectos para cada versión. El tiempo dedicado a reparar defectos podría dedicarse mejor a otras cosas, como mejorar la aplicación y hacer que se adapte mejor a los requisitos futuros.

Si el equipo y la gerencia en general son apáticos acerca de arreglar el código y usted no está contento con él, es posible que deba buscar otro lugar para trabajar, a menos que pueda convencerlos como ya he dicho. He encontrado este problema en diferentes grados en todos los lugares donde he trabajado. Podría ser cualquier cosa, desde la falta de un modelo de dominio adecuado, hasta una mala comunicación en el equipo.

Preocuparse por su código y la calidad del producto que desarrolla es un buen atributo que siempre desea alentar a otras personas.

Planeta desolado
fuente
11

Si codifica en C, Objective-C o C ++, puede usar el Analizador estático CLang para criticar su fuente sin ejecutarlo realmente.

Hay algunas herramientas de depuración de memoria disponibles: ValGrind, Guard Malloc en Mac OS X, Electric Fence en * NIX.

Algunos entornos de desarrollo ofrecen la opción de usar un asignador de memoria de depuración, que hace cosas como llenar páginas recién asignadas y páginas recién liberadas con basura, detectar la liberación de punteros no asignados y escribir algunos datos antes y después de cada bloque de montón, siendo el depurador se llama si el patrón conocido de esos datos cambia alguna vez.

Un tipo en Slashdot dijo que obtuvo mucho valor de la nueva línea de fuente de un solo paso en un depurador. "Eso es todo", dijo. No siempre sigo su consejo, pero cuando lo tengo me ha sido muy útil. Incluso si no tiene un caso de prueba que estimule una ruta de código poco común, puede girar una variable en su depurador para tomar tales rutas, por ejemplo, asignando algo de memoria, luego usando el depurador para establecer su nuevo puntero en NULL en lugar de dirección de memoria, luego recorriendo el controlador de fallas de asignación.

Utilice aserciones: la macro afirmar () en C, C ++ y Objective-C. Si su idioma no proporciona una función de aserción, escríbala usted mismo.

Use afirmaciones generosamente, luego déjelas en su código. Llamo afirmar () "La prueba que sigue probando". Los uso con mayor frecuencia para verificar las condiciones previas en el punto de entrada de la mayoría de mis funciones. Esa es una parte de "Programación por contrato", que está integrada en el lenguaje de programación de Eiffel. La otra parte son las condiciones posteriores, es decir, el uso de afirmar () en los puntos de retorno de la función, pero encuentro que no obtengo tanto kilometraje de eso como las condiciones previas.

También puede usar afirmar para verificar invariantes de clase. Si bien no se requiere estrictamente que ninguna clase tenga invariantes, la mayoría de las clases diseñadas con sensatez las tienen. Una clase invariante es una condición que siempre es cierta, aparte de dentro de las funciones miembro que pueden colocar temporalmente su objeto en un estado inconsistente. Tales funciones siempre deben restaurar la consistencia antes de que regresen.

Por lo tanto, cada función miembro podría verificar la invariante al entrar y salir, y la clase podría definir una función llamada CheckInvariant que cualquier otro código podría llamar en cualquier momento.

Use una herramienta de cobertura de código para verificar qué líneas de su fuente realmente se están probando, luego diseñe pruebas que estimulen las líneas no probadas. Por ejemplo, puede verificar los controladores de poca memoria ejecutando su aplicación dentro de una VM configurada con poca memoria física, y sin archivos de intercambio o muy pequeña.

(Por alguna razón, nunca tuve acceso, aunque el BeOS podía ejecutarse sin un archivo de intercambio, era muy inestable de esa manera. Dominic Giampaolo, quien escribió el sistema de archivos BFS, me instó a que nunca ejecutara el BeOS sin intercambio. No lo hago vea por qué eso debería importar, pero debe haber sido algún tipo de artefacto de implementación).

También debe probar la respuesta de su código a los errores de E / S. Intente almacenar todos sus archivos en un recurso compartido de red, luego desconecte su cable de red mientras su aplicación tiene una gran carga de trabajo. Del mismo modo, desconecte el cable, o apague la conexión inalámbrica, si se está comunicando a través de una red.

Una cosa que encuentro particularmente irritante son los sitios web que no tienen un código Javascript robusto. Las páginas de Facebook cargan docenas de pequeños archivos Javascript, pero si alguno de ellos no se descarga, la página entera se rompe. Simplemente tiene que haber alguna forma de proporcionar cierta tolerancia a fallas, por ejemplo, al volver a intentar una descarga, o para proporcionar algún tipo de respaldo razonable cuando algunos de sus scripts no se descargaron.

Intente eliminar su aplicación con el depurador o con "kill -9" en * NIX mientras está escribiendo un archivo grande e importante. Si su aplicación está bien diseñada, todo el archivo se escribirá o no se escribirá en absoluto, o tal vez si solo se escribe parcialmente, lo que se escribe no se corromperá, y los datos que se guardan serán completamente utilizables por la aplicación al volver a leer el archivo.

las bases de datos siempre tienen E / S de disco con tolerancia a fallas, pero casi ningún otro tipo de aplicación lo hace. Si bien los sistemas de archivos registrados evitan la corrupción del sistema de archivos en caso de falla de energía o fallas, no hacen nada para evitar la corrupción o la pérdida de datos del usuario final. Esa es la responsabilidad de las aplicaciones del usuario, pero casi ninguna otra que las bases de datos implementan tolerancia a fallas.

Mike Crawford
fuente
1
+1 muchos consejos prácticos que no necesitan el apoyo de nadie más. Lo único que agregaría es que afirmar es para documentar y verificar condiciones que no pueden fallar a menos que haya un error en el código . Nunca afirman cosas que podrían fallar debido a la 'mala suerte', como por ejemplo un archivo esencial no se encuentra, o una entrada no válida, etc.
Ian Goldby
10

Cuando miro probar mi código, generalmente paso por una serie de procesos de pensamiento:

  1. ¿Cómo divido esta "cosita" en trozos de tamaño comprobable? ¿Cómo puedo aislar solo lo que quiero probar? ¿Qué trozos / simulacros debo crear?
  2. Para cada fragmento: ¿Cómo pruebo este fragmento para asegurarme de que responde correctamente a un conjunto razonable de entradas correctas?
  3. Para cada fragmento: ¿Cómo pruebo que el fragmento responde correctamente a entradas incorrectas (punteros NULL, valores no válidos)?
  4. ¿Cómo pruebo los límites (por ejemplo, dónde van los valores de firmado a no firmado, de 8 bits a 16 bits, etc.)?
  5. ¿Qué tan bien cubren mis pruebas el código? ¿Hay alguna condición que me haya perdido? [Este es un gran lugar para las herramientas de cobertura de código.] Si hay un código que se perdió y nunca se puede ejecutar, ¿realmente necesita estar allí? [¡Esa es otra pregunta!]

La forma más fácil que he encontrado para hacer esto es desarrollar mis pruebas junto con mi código. Tan pronto como he escrito incluso un fragmento de código, me gusta escribir una prueba para ello. Intentar hacer todas las pruebas después de haber codificado varios miles de líneas de código con una complejidad de código ciclomático no trivial es una pesadilla. Agregar una o dos pruebas más después de agregar algunas líneas de código es realmente fácil.

Por cierto, solo porque la compañía en la que trabajas y / o tus colegas no hacen Pruebas unitarias o TDD, no significa que no puedas probarlos, a menos que estén específicamente prohibidos. Quizás usarlos para crear código robusto será un buen ejemplo para otros.

jwernerny
fuente
5

Además de los consejos dados en las otras respuestas, sugeriría usar herramientas de análisis estático (Wikipedia tiene una lista de varias herramientas de análisis estático para varios idiomas ) para encontrar posibles defectos antes de comenzar las pruebas, así como monitorear algunas métricas relacionadas con la capacidad de prueba del código, como la complejidad ciclomática , las medidas de complejidad de Halstead y la cohesión y el acoplamiento (puede medirlos con entrada y salida de abanico).

Encontrar un código difícil de probar y hacer que sea más fácil probarlo, bueno, te facilitará la escritura de casos de prueba. Además, la detección temprana de defectos agregará valor a todas sus prácticas de garantía de calidad (que incluyen pruebas). A partir de aquí, familiarizarse con las herramientas de prueba de unidad y las herramientas de burla le facilitará la implementación de su prueba.

Thomas Owens
fuente
1
+1 para la complejidad ciclomática. "Me resulta realmente difícil seguir todos los caminos posibles para poder encontrar errores" me implica que el código del OP puede necesitar dividirse en fragmentos más pequeños y menos complejos.
Toby
@Toby Sí, por eso decidí lanzar un análisis estático. Si no puede probar su código, tiene problemas. Y si tiene un problema con su código, puede haber otros. Use una herramienta para encontrar posibles indicadores de advertencia, evaluarlos y corregirlos según sea necesario. No solo tendrá un código más comprobable, sino también un código más legible.
Thomas Owens
3

Podría analizar el posible uso de las Tablas de verdad para ayudarlo a definir todas las rutas potenciales en su código. Es imposible tener en cuenta todas las posibilidades en funciones complejas, pero una vez que haya establecido su manejo para todas las rutas conocidas, puede establecer un manejo para el caso contrario.

Sin embargo, la mayor parte de esta habilidad particular se aprende por experiencia. Después de haber utilizado un determinado marco durante un período de tiempo significativo, comienza a ver los patrones y las características del comportamiento que le permitirán ver un fragmento de código y ver dónde un pequeño cambio podría causar un error importante. La única forma en que puedo pensar para aumentar tu aptitud en esto es practicando.

Joel Etherton
fuente
3

Si, como dijiste, no necesitas pruebas unitarias, no veo un enfoque mejor que intentar descifrar tu propio código manualmente.

Intenta llevar tu código al límite . Por ejemplo, intente pasar variables a una función que exceda los límites. ¿Tiene una función que se supone que filtra la entrada del usuario? Intenta ingresar diferentes combinaciones de caracteres.

Considere el punto de vista del usuario . Intente ser uno de los usuarios que utilizará su aplicación o biblioteca de funciones.

Jose Faeti
fuente
1
+1 por mencionar ver cosas desde el punto de vista del usuario.
Matthew Rodatus
3

pero no tenemos evaluadores en mi empleador actual y mis colegas parecen ser bastante buenos en esto

Sus colegas deben ser realmente excepcionales para no seguir TDD o pruebas unitarias y nunca generar errores, por lo que, en cierto nivel, dudo que no estén realizando ninguna prueba unitaria ellos mismos.

Supongo que sus colegas están haciendo más pruebas de las que se están haciendo, pero debido a que la gerencia no conoce este hecho, la organización sufre como resultado porque la gerencia tiene la impresión de que no se están realizando pruebas verdaderas y que los números de errores son bajos, por lo tanto la prueba no es importante y no se programará tiempo para ello.

Hable con sus colegas e intente averiguar qué tipo de pruebas unitarias están haciendo y emule eso. En un momento posterior, puede crear prototipos de mejores formas de pruebas unitarias y atributos TDD e introducir lentamente estos conceptos al equipo para una adopción más fácil.

árbol de arce
fuente
2
  • Escriba sus pruebas antes de escribir su código.
  • Cada vez que arregle un error que no fue detectado por una prueba, escriba una prueba para detectar ese error.

Debería poder obtener cobertura sobre lo que escribe, incluso si su organización no tiene cobertura total. Como tantas otras cosas en la programación, la experiencia de hacerlo una y otra vez es una de las mejores maneras de ser eficiente.

Jeff Ferland
fuente
2

Además de todos los demás comentarios, dado que usted dice que sus colegas son buenos para escribir pruebas de ruta no feliz, ¿por qué no les pide que se emparejen con usted para escribir algunas pruebas?

La mejor manera de aprender es ver cómo se hace y extraer lo que aprende de eso.

Jim Munro
fuente
2

Prueba de caja negra! Debería crear sus clases / métodos teniendo en cuenta las pruebas. Sus pruebas deben basarse en la especificación del software y deben estar claramente definidas en su diagrama de secuencia (a través de casos de uso).

Ahora, ya que es posible que no desee hacer un desarrollo basado en pruebas ...

Poner validación de entrada en todos los datos entrantes; No confíes en nadie. El marco .net tiene muchas excepciones basadas en argumentos inválidos, referencias nulas y estados inválidos. Ya debería estar pensando en usar la validación de entrada en la capa de interfaz de usuario, por lo que es el mismo truco en el middleware.

Pero realmente deberías estar haciendo algún tipo de prueba automatizada; esas cosas salvan vidas.


fuente
2

En mi experiencia

Unidad de prueba, si no es completamente automática, es inútil. Es más como un jefe de pelo puntiagudo podría comprar. ¿Por qué ?, porque Test Unit le prometió ahorrar tiempo (y dinero) automatizando algunos procesos de prueba. Pero, algunas herramientas de unidad de prueba hacen lo contrario, obligan a los programadores a trabajar de una manera extraña y obligan a otras a crear pruebas de extensión excesiva. La mayoría de las veces, no ahorrará horas de trabajo sino que aumentará el tiempo de traslado del control de calidad al desarrollador.

UML es otra pérdida de tiempo. una sola pizarra blanca + bolígrafo podría hacer lo mismo, más barato y rápido.

Por cierto, ¿cómo ser bueno en la codificación (y evitar errores)?

  • a) atomicidad. Una función que realiza una tarea simple (o algunas tareas simples). Debido a que es fácil de entender, es fácil de rastrear y es fácil de resolver.

  • b) Homología. Si, por ejemplo, llama a una base de datos utilizando un procedimiento de almacenamiento, haga lo mismo para el resto del código.

  • c) Identificar, reducir y aislar el "código creativo". La mayor parte del código es prácticamente copiar y pegar. El código creativo es lo contrario, un código que es nuevo y actúa como un prototipo, puede fallar. Este código es propenso a errores lógicos, por lo que es importante reducirlo, aislarlo e identificarlo.

  • d) El código "Thin Ice" es el código que usted sabe que es "incorrecto" (o potencialmente peligroso) pero que aún necesita, por ejemplo, código inseguro para un proceso de tareas múltiples. Evítalo si puedes.

  • e) Evite el código de recuadro negro, esto incluye el código que no ha hecho usted (por ejemplo, el marco) y la expresión regular. Es fácil perderse un error con este tipo de código. Por ejemplo, trabajé en un proyecto usando Jboss y no encontré uno sino 10 errores en Jboss (usando la última versión estable), fue un PITA encontrarlos. Evite especialmente Hibernate, oculta la implementación, de ahí los errores.

  • f) agregue comentarios en su código.

  • g) entrada del usuario como fuente de errores. identificarlo Por ejemplo, la inyección SQL es causada por una entrada del usuario.

  • h) Identificar el elemento malo del equipo y separar la tarea asignada. Algunos programadores son propensos a atornillar el código.

  • i) Evite el código innecesario. Si, por ejemplo, la clase necesita Interfaz , úsela; de lo contrario, evite agregar código irrelevante.

a) yb) son clave. Por ejemplo, tuve un problema con un sistema, cuando hice clic en un botón (guardar) no guardó el formulario. Luego hice una lista de verificación:

  • ¿funciona el botón? ... sí.
  • la base de datos almacena algo ?. no, entonces el error estaba en un paso intermedio.
  • entonces, ¿la clase que se almacena en la base de datos funciona? no <- ok, encontré el error. (estaba relacionado con el permiso de la base de datos). Luego verifiqué no solo este procedimiento sino todos los procedimientos que hacen lo mismo (debido a la homología del código). Me llevó 5 minutos rastrear el error y 1 minuto para resolverlo (y muchos otros errores).

Y una nota al margen

La mayoría de las veces el control de calidad es una mierda (como una sección separada de Dev), es inútil si no se trabaja en el proyecto. Hacen una prueba genérica y nada más. No pueden identificar la mayoría de los errores lógicos. En mi caso, estaba trabajando en un banco prestigioso, un programador terminó un código y luego lo envió a QA. QA aprobó el código y se puso en producción ... luego el código falló (un error épico), ¿sabes quién fue el culpable? Sí, el programador.

magallanes
fuente
2

Un probador y un programador enfrentan el problema desde diferentes ángulos, pero ambos roles deben probar completamente la funcionalidad y encontrar errores. Donde los roles difieren está en foco. Un probador clásico ve la aplicación solo desde el exterior (es decir, recuadro negro). Son expertos en los requisitos funcionales de la aplicación. Se espera que un programador sea un experto tanto en los requisitos funcionales como en el código (pero tiende a centrarse más en el código).

(Depende de la organización si se espera explícitamente que los programadores sean expertos en los requisitos. Independientemente, la expectativa implícita está ahí; si diseñas algo incorrecto, tú, no la persona de los requisitos, recibes la culpa).

Esta doble función de experto está afectando la mente del programador y, a excepción de los más experimentados, puede disminuir la competencia en los requisitos. Me parece que debo cambiar mentalmente de marcha para considerar a los usuarios de la aplicación. Esto es lo que me ayuda:

  1. Depuración ; establecer puntos de interrupción en el código y ejecutar la aplicación. Una vez que llegue a un punto de interrupción, avance por las líneas mientras interactúa con la aplicación.
  2. Prueba automatizada ; escribe un código que pruebe tu código. Esto solo ayuda en los niveles por debajo de la interfaz de usuario.
  3. Conozca a sus probadores ; pueden conocer la aplicación mejor que tú, así que aprende de ellos. Pregúnteles cuáles son las debilidades de su aplicación y qué tácticas usan para encontrar errores.
  4. Conozca a sus usuarios ; aprende a caminar en los zapatos de tus usuarios. Los requisitos funcionales son la huella digital de sus usuarios. A menudo, hay muchas cosas que los usuarios saben sobre la aplicación que pueden no verse claramente en los requisitos funcionales. A medida que comprenda mejor a sus usuarios, la naturaleza de su trabajo en el mundo real y cómo se supone que su aplicación los ayudará, comprenderá mejor cuál es la aplicación.
Matthew Rodatus
fuente
2

Creo que quieres trabajar en dos frentes. Una es política, lograr que su organización adopte pruebas en algún nivel (con la esperanza de que con el tiempo adopten más). Hable con ingenieros de control de calidad fuera de su lugar de trabajo. Encuentra listas de libros de control de calidad . Examine los artículos relevantes de Wikipedia . Familiarícese con los principios y prácticas de control de calidad. Aprender estas cosas lo preparará para presentar el caso más convincente posible en su organización. Existen buenos departamentos de control de calidad y agregan un valor considerable a sus organizaciones.

Como desarrollador individual, adopte estrategias para usar en su propio trabajo. Use TDD usted mismo mediante el desarrollo conjunto de código y pruebas. Mantenga las pruebas claras y bien mantenidas. Si se le pregunta por qué está haciendo esto, puede decir que está evitando las regresiones y mantiene su proceso de pensamiento mejor organizado (lo cual será cierto). Escribir un código comprobable es un arte , aprende. Sea un buen ejemplo para sus compañeros desarrolladores.

En parte, me estoy predicando a mí mismo aquí, porque hago mucho menos de lo que sé que debería.

Will Ware
fuente