¿Existe un principio de ingeniería de software que relacione el costo de la prueba de reutilización y regresión en un sistema de producción?


12

He trabajado en un gran sistema de transacciones financieras para un banco que se ocupaba de las pensiones e inversiones. Después de 15 años de cambios en las características, el costo de la prueba de regresión manual había aumentado a $ 200K por lanzamiento. (10M LOC, $ 10M tramitados por día). Este sistema también interactúa con otros 19 sistemas de la compañía que mueven muchos datos. Este sistema fue implementado en Java.

Sin embargo, lo que observamos es que cuanto más 'reutilizamos', más aumentan los costos de la prueba de regresión. (La razón es que necesita "probar el código que toca", y el código reutilizado / compartido afecta a una multiplicidad de lugares cuando se toca. Por lo tanto, a pesar de 'SECO - No se repita', es decir, no copie y pegue el código - observamos un incentivo financiero para copiar y pegar código. Esto es para reducir los costos de la prueba de regresión, porque no queremos modificar el código que podría compartirse, porque eso causará un gran impacto en la prueba de regresión).

Mi pregunta es ¿existe un principio de ingeniería de software que describa la relación entre los costos de reutilización y prueba de regresión?

La razón por la que haría esta pregunta es que posiblemente haya un beneficio de costo al descomponer el sistema en partes más pequeñas para ser probadas.

Suposiciones

  1. 'Prueba de regresión' significa 'prueba de aceptación', es decir, otro grupo que dedica tiempo a escribir pruebas nuevas y reutilizarlas contra el sistema en nombre de la empresa, incluidas las configuraciones de entorno y datos.

  2. Sé que la reacción instintiva a un gran costo de prueba de regresión es 'pruebas más automatizadas'. Este es un buen principio. En este entorno hay un par de desafíos.

    (a) Las pruebas automatizadas son menos útiles a través de los límites del sistema, a menos que ese sistema también tenga una alta cobertura de pruebas automatizadas. (Desafío de la esfera de influencia).

    (b) Es culturalmente difícil impulsar el tiempo del programador o la inversión de capital en una alta cobertura de prueba automatizada cuando su sistema ya es grande y complejo.

    (c) El costo de mantener pruebas automatizadas está oculto en un proyecto, por lo que se descartan fácilmente a nivel de proyecto.

    (d) Esta es solo la realidad cultural de trabajar en un banco.

    (e) Estoy trabajando para resolver este problema de una manera diferente (descomposición).


2
Usted hace la afirmación de que " observamos [...] que cuanto más 'reutilizamos', más suben los costos de la prueba [de aceptación] ", ¿podría ampliar eso? ¿No debería una prueba de aceptación ser independiente de los detalles de implementación, como las jerarquías de herencia, y, en cambio, jugar a través de escenarios de uso, por ejemplo?
amon

2
Su pregunta es interesante, pero contiene algunas suposiciones muy sesgadas como "la reutilización es una consecuencia de OO" o la parte que @amon mencionó. Le sugiero que elimine la parte "OO" completamente, ya que su pregunta principal es independiente del lenguaje de programación o de cualquier programación OO. Y no está claro qué tipo de "reutilización" tiene en mente: "reutilizar" es un término amplio, la reutilización de copiar y pegar es diferente de la reutilización de componentes o bibliotecas.
Doc Brown

Gracias eso es útil. Eliminé referencias a OO y amplié la idea de tocar el código compartido (o código que se convertirá en código compartido).
hawkeye

1
En la forma actual, supongo que respondería simplemente "no, no lo creo" a su pregunta, lo que supongo que no sería muy satisfactorio para usted. ¿O le interesan los principios y prácticas para reducir realmente los costos de prueba al construir un sistema grande como el suyo con algunos componentes reutilizables hechos a sí mismos?
Doc Brown

Respuestas:


11

no queremos modificar el código que podría compartirse, porque eso causará un gran impacto en la prueba de regresión

Lo anterior me suena bien. Cuanto más importante es el código, cuanto más se comparte, más altos son los requisitos de calidad, más garantía de calidad debe estar involucrada cuando cambia.

Dado que su sistema está implementado en Java, puede ver un ejemplo de lo anterior aquí, en las bibliotecas estándar de Java (JDK). Sus versiones principales son poco frecuentes y se acompañan de pruebas que requieren mucho esfuerzo. E incluso las versiones menores se ejecutan a través de un conjunto de pruebas JCK muy completo para verificar la ausencia de regresiones.

Puede pensar que esto de alguna manera sofoca la evolución del código compartido, y ... sí, esto es correcto. Cuanto más impacto y riesgo se asocie con el cambio de código, más cuidadoso debe ser al hacerlo, más esfuerzo debe involucrarse para probar sus versiones.

Idealmente, la calidad de las versiones de código ampliamente compartido debe ser tal que no necesite grandes cambios (salvo las mejoras poco frecuentes). Esta línea de pensamiento se refleja en una famosa cita de Joshua Bloch :

Las API públicas, como los diamantes, son para siempre. Tienes una oportunidad de hacerlo bien, así que da lo mejor de ti.


Sin embargo, con lo dicho anteriormente, parece que algunos de los problemas que describe están causados ​​por una estrategia ineficiente de desarrollo de código compartido. En particular, parece especialmente problemático que para el código reutilizado, solo se consideren dos opciones: duplicar este código o incluirlo inmediatamente en las bibliotecas compartidas "centrales".

Limitarse solo a estas dos opciones es innecesario y, nuevamente, puede encontrar ejemplos de cómo se puede hacer mejor en el JDK que usa. Eche un vistazo a los java.util.concurrentpaquetes ( JSR 166 ): hasta el lanzamiento de Java 5, estos eran una biblioteca separada, no una parte de los lanzamientos centrales de JDK.

Piénselo, esa es una tercera opción que pasó por alto, y bastante pragmática, la que debe considerar al "inicio" del nuevo código compartido. Cuando solo calcula un código que se puede compartir entre 2-3 componentes, nada lo obliga a incluirlo de inmediato en la API central del sistema.

Puede empaquetar y liberar este código compartido "inmaduro" como una biblioteca separada, tal como se hizo con las utilidades concurrentes de Java. De esta forma, se libera de la necesidad de realizar pruebas de regresión completas, ya que solo puede usar una cantidad relativamente pequeña de componentes involucrados. Como resultado, tiene más margen de maniobra para modificar y mejorar este código compartido y para probar cómo funciona en la producción.

Después de que su biblioteca madure y se estabilice lo suficiente como para darle la confianza de que es poco probable que se realicen más cambios en ella, puede considerar su inclusión en las bibliotecas centrales del sistema, al igual que las utilidades concurrentes finalmente se han incluido en JDK.


Un ejemplo concreto de cuánto esfuerzo (incluidas las pruebas) puede implicar el cambio de código muy reutilizado se puede encontrar, nuevamente, en JDK. En la versión 7u6, cambiaron la Stringrepresentación interna que implicaba un cambio en el substringrendimiento. Los comentarios de un desarrollador de funciones en Reddit describen cuánto esfuerzo estuvo involucrado en este cambio:

El análisis inicial salió del grupo GC en 2007 ...

Internamente, el equipo de rendimiento de Oracle mantiene un conjunto de aplicaciones representativas e importantes y puntos de referencia que utilizan para evaluar los cambios de rendimiento. Este conjunto de aplicaciones fue crucial para evaluar el cambio a la subcadena. Observamos de cerca tanto los cambios en el rendimiento como el cambio en la huella. Inevitablemente, como es el caso con cualquier cambio significativo, hubo regresiones en algunas aplicaciones, así como ganancias en otras. Investigamos las regresiones para ver si el rendimiento seguía siendo aceptable y se mantenía la corrección ...

Mi respuesta no pretende ser exhaustiva, pero es un resumen muy breve de lo que fueron casi seis meses de trabajo dedicado ...


Parece que ambos trabajamos en una respuesta en paralelo, con muchos pensamientos similares ...
Doc Brown

@DocBrown, sí, es interesante que también hayamos respondido casi simultáneamente, aproximadamente una hora después de que la pregunta se editara
mosquito

9

No creo que exista ninguna métrica para calcular el "costo de las pruebas de regresión / LOC del código reutilizado construido". Y no creo que nadie haya invertido tanto tiempo y dinero para construir el mismo sistema "grande" dos veces, una versión con muchos componentes valiosos y otra sin ella, para hacer una investigación seria al respecto.

Pero he visto problemas causados ​​por la reutilización como la suya anteriormente, y tal vez le interesen algunas ideas sobre cómo manejar esto mejor.

Primero, en realidad no es reutilizar cuál es su problema, es más bien el intento de construir sus propios componentes reutilizables y usarlos en todo su sistema. Estoy seguro de que está reutilizando muchos paquetes de software grandes donde sus problemas no surgen: piense en toda la pila de Java que está utilizando, o tal vez en algunos componentes de terceros (asumió que estaba satisfecho con esos componentes). Pero, ¿qué es diferente con ese software, por ejemplo, las bibliotecas de Java, mientras que sus propios componentes reutilizables le están causando tantos costos adicionales de pruebas de regresión? Aquí hay algunos puntos que supongo que podrían ser diferentes:

  • esos componentes son muy maduros y estables

  • son desarrollados y probados completamente de forma aislada, por una organización completamente diferente

  • para (re) usarlos, no tiene que cambiarlos (de hecho, no podría incluso si quisiera, ya que no mantiene el código fuente)

  • no recibe diariamente una nueva versión, solo actualizaciones menores (como máximo por mes) o actualizaciones importantes en intervalos por año

  • la mayoría de las actualizaciones están diseñadas para ser 100% compatibles, especialmente las actualizaciones menores

Entonces, para que sus propios componentes reutilizables sean más exitosos, debe adaptar algunas de esas cosas desde arriba para su propio desarrollo:

  • para cualquier componente reutilizable, tenga una responsabilidad clara de quién realiza el mantenimiento y asegúrese de que todas las personas que reutilizan un componente puedan asegurarse de obtener una corrección de errores de inmediato si surgen problemas.

  • establecer un estricto control de versiones y políticas de lanzamiento. Cuando desarrolle un componente reutilizable, no lo libere "a todos" todos los días (al menos, no si eso implicaría ejecutar una prueba de regresión completa de $ 200K en el sistema). En cambio, deje que las nuevas versiones solo se publiquen de vez en cuando y proporcione mecanismos para permitir que el usuario de ese componente difiera el cambio a la nueva versión.

  • cuanto más se reutiliza un componente, más importante es que proporcione una interfaz estable y un comportamiento compatible con versiones anteriores.

  • Los componentes reutilizables necesitan conjuntos de pruebas muy completos para probarlos de forma aislada.

Muchas de estas cosas significarán que el costo de construir el componente en sí mismo aumentará, pero también disminuirá los costos de los cambios causados ​​por regresiones fallidas.


0

Si bien puede haber un aumento "observable" en el costo debido a que se necesitan más pruebas, ese tipo de refactorización generalmente hace que el código sea más fácil de mantener en el futuro a medida que disminuye la deuda técnica en el sistema.

Con suerte, esto debería reducir los errores futuros y hacer que las nuevas funciones o alteraciones de las funciones existentes sean más fáciles de implementar.

Por más fácil, quiero decir que deberían tomar menos tiempo y, por lo tanto, costar menos.

Reducir, más fácil y menos son términos bastante nebulosos aquí y cualquier ahorro futuro (o más bien esperado para ahorrar) es imposible de calcular ya que aún no ha sucedido.

Una base de código más simple debería permitir que los nuevos empleados, o los empleados existentes que se trasladan al proyecto, se aceleren más rápido, especialmente para sistemas grandes.

También puede reducir la rotación de personal en que la moral de los miembros del proyecto existente podría mejorarse.

Por supuesto, no se garantiza que obtendrá estos beneficios, pero estas son cosas que deben considerarse junto con los costos (como el aumento de las pruebas) que se pueden medir.

De hecho, un mejor código eventualmente debería reducir los costos de las pruebas con el tiempo, incluso si hay un aumento inicial debido a lo que usted describe.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.