2009-06-18 23 views
27

Actualmente nuestro proyecto tiene más de 3000 pruebas unitarias, y "ant testAll" lleva más de 20 minutos. además de obtener un mejor hardware, ¿hay formas de acelerar las cosas?¿Cómo se aceleran las pruebas de la unidad java?

+0

Debe ser más específico de lo que está soportando esas pruebas. – starblue

+0

Me uní a este proyecto hace solo dos meses y ya tiene una gran base de código. No es una aplicación web y, salvo un montón de archivos de configuración xml, tampoco lo hace File IO. Estamos utilizando nuestro TestRunner personalizado. pero el problema es que solo estamos desarrollando complementos para el sistema y, como tal, no tenemos el código fuente de TestRunner. Supongo que el implementador de TestRunner fue lo suficientemente inteligente como para haber optimizado la configuración, por lo que, por ejemplo, no tendrá que analizar las configuraciones xml en cada ejecución de prueba. –

+0

¿Está utilizando objetos simulados para burlar los objetos de acceso lento de las pruebas de su unidad? En el trabajo tenemos 6000 pruebas unitarias y tardan aproximadamente un minuto o quizás dos en ejecutarse. –

Respuesta

1

Sin más conocimiento de lo que se está probando, los únicos dos enfoques que se presentan fácilmente a sí mismos son:

  • uso mejor hardware (lo siento)
  • simplificar la lógica de prueba

Usted puede desea incluso ejecutar un generador de perfiles sobre la ejecución de prueba y ver si hay alguna prueba particularmente implementada ineficientemente.

+0

supongo que tendré que ejecutar un generador de perfiles en los casos de prueba. –

25

De la misma manera que acelerarías cualquier otro código. Descubra qué pruebas toman más tiempo y vea cómo se pueden optimizar.

Hay muchas operaciones que pueden ser lentas, y si las haces 3000 veces, se acumulan. A veces, la reutilización de datos entre pruebas vale la pena (incluso si no se supone que debes hacer eso en pruebas unitarias, puede ser necesario si eso es lo que se requiere para que tus pruebas se ejecuten a una velocidad aceptable).

Mida sus análisis. Por lo general, el 90% de ellos se ejecutará casi al instante, y el último 10% tomará casi todo el tiempo. Encuentra esos 10% y ve lo que están haciendo.

Ejecute el código a través de un generador de perfiles y observe dónde se está gastando el tiempo. Adivinar es una pérdida de tiempo. Lo que piensa que hace el corredor de pruebas no tiene sentido. En lugar de adivinar, averigüe qué está haciendo. Entonces sabrás cómo acelerarlo.

2

Bueno, no sé lo que está haciendo su Prueba de unidad, pero debe preguntarse por qué está tardando 20 minutos. Según mi experiencia, a menudo hay muchas pruebas que se ejecutan en unos pocos milisegundos y pocas pruebas que constituyen el resto del tiempo requerido. La mayoría de las veces se trata de pruebas que involucran IO/network/DB-Stuff. Por ejemplo, si pasa mucho tiempo esperando debido a la latencia de la red, puede considerar ejecutar sus pruebas en paralelo.

Puede buscar esas pruebas y buscar mejoras. Pero hacer tus pruebas más rápidas no mejora tu código real. Es posible que desee buscar pruebas que requieren mucho tiempo porque la clase bajo prueba no es óptima. Identificar y mejorar situaciones como estas seguramente hará que tu producto sea mejor/más rápido también.

13

Algunas ideas:

  • utilizar un marco de burla para evitar golpear las bases de datos (o realizar llamadas de servicios web, etc.).
  • Si está haciendo la misma configuración o una similar para muchas pruebas individuales, intente hacerlo en una instalación de dispositivo de prueba (es decir, algo que se hace una vez por accesorio en vez de una vez por prueba).
  • creo que algunos marcos de prueba permite llevar a cabo pruebas en paralelo
+0

+1 por burlarse. –

+1

Los dos mejores marcos burlones que he encontrado son Mockito y JMockit. JMockit permite la burla de los métodos estáticos y finales, y Mockito es realmente cómodo para los simulacros rápidos. La combinación de los dos funciona EXCELENTE. – Epaga

+0

Upvoted para burlarse ... – ABcDexter

10

Es posible que desee dividir las pruebas unitarias en suites. ¿Tu aplicación está modularizada? ¿Con qué frecuencia necesita ejecutar todas las pruebas?¿Sería aceptable que sus desarrolladores solo ejecutaran las pruebas unitarias relevantes para su propio módulo y que la matriz de pruebas se ejecutara todas las noches y/o en un IC?

¿Existen pruebas unitarias particulares que son muy complejas (lo sé, lo estoy resbalando en las pruebas funcionales y de integración aquí, pero la línea es a veces poco clara), pero cualquiera de ellos puede ser ejecutado en un cordura- nivel durante el desarrollo y luego se ejecuta completamente en el CI?

Edición: Sólo por diversión, describiré brevemente las rutinas de prueba en uno de mis proyectos anteriores

En primer lugar, el crecimiento del sistema de prueba era orgánica, lo que significa que no se había previsto inicialmente fuera pero fue modificado y cambiado a medida que crecía. Por lo tanto, no era perfecto, y había algunas convenciones de nombres que se habían vuelto apócrifas con el tiempo.

  • A nivel de desarrollador, usamos un conjunto de prueba simple de dos minutos llamado CheckIn, que verificaba que el código era lo suficientemente saludable para unir el tronco.
  • Además de eso, realizamos pruebas de cordura continuamente en una máquina CI. Estas eran versiones simplificadas de las pruebas funcionales y de integración más complejas, todas las pruebas unitarias y todas las pruebas de regresión.
  • Las suites de pruebas complejas (en el número de horas) se corrieron durante el día y la noche de forma remota y los resultados se compilaron a la mañana siguiente.

Pruebas automáticas: son las nueces del mutt.

+2

Este es un mal consejo. Debería ejecutar tantas pruebas como sea posible para asegurarse de que todos los componentes funcionen de forma independiente y en conjunto. Nunca se puede asumir que un aspecto de un sistema es 'solo funciona' porque no se está desarrollando contra esa área. –

+5

Lo "juntos" vendrá bajo alguna otra prueba, digamos su prueba de integración o algo así. La prueba unitaria se trata de la unidad, por lo que la unidad debe funcionar de forma independiente. No significa que no necesitemos probar todas las cosas juntas, pero esa no es una prueba de unidad, IMO. –

+1

@atc: Entonces nunca ha estado en un escenario en el que todo el conjunto de pruebas (pruebas de integración y todo) tome varias horas. Antes de comprometerse, todo lo que se requiere de un desarrollador es garantizar la cordura, es decir, la funcionalidad básica. Esta prueba no debería tomar más de un par de minutos, o tendrá desarrolladores inactivos. Asegurarse de que todo funciona puede hacerse por la noche. – mikek

5

Para comenzar con:
a) Obtenga las estadísticas sobre el tiempo de ejecución de sus Junit Tests. Puede que ya esté cargando esa información en sus informes de prueba.
b) Saque las 10 mejores clases de prueba (en tiempo) e intente reducir el tiempo. Esto debe hacer de forma continua.
c) Trate de reducir el tiempo de ejecución mediante la refacturación o incluso cambiando el enfoque de las pruebas.
Uno de estos casos me encontré en una clase de prueba para casos de prueba CRUD. El caso de prueba de actualización fue la primera creación del funtionlaity y luego la actualización. Pero ya hemos probado crear en el caso de prueba seprate. Así que en estos casos puede encadenar su prueba casos como

@Test() 
    public void testCreate() throws Exception 
    {} 
    @Test(dependsOnMethods = "testCreate") 
    public void testAmend() throws Exception 
    {} 
    @Test(dependsOnMethods = "testAmend") 
    public void testDelete() throws Exception 
    {} 

Así que ahorrará en hacer pruebas dupicadas.

d) Un ejemplo más en el que pude reducir significativamente el tiempo. Teníamos un sistema (inherietd) donde cada caso de prueba llamaba a SetUp (Strating Spring Server, etc.) y después de cerrar los recursos del sistema. Esto consumía mucho tiempo, así que lo refactoré para iniciar los recursos de Coomon antes de probar y después de todo el conjunto está hecho, entonces cierre esos.

e) Dependiendo de su proyecto, pueden ser otros cuellos de botella que deba solucionar.

How to manage Build time in TDD

+0

buena lista de verificación. muchas gracias –

3

Obviamente hay algo en su examen que toma mucho tiempo.

A veces, no se puede evitar la prueba lenta. Por ejemplo, probar que Spring puede leer todos sus archivos de configuración, probando que la asignación de hibernación funciona, ese tipo de cosas.Lo bueno de esas pruebas es que solo necesitan ejecutarse en una sola prueba y luego puedes simularte, pero también puedes decidir ejecutarlas como parte de la prueba de integración y dejar que el servidor de compilación se preocupe por ello.

El resto de las pruebas son lentas o bien porque están haciendo IO o porque están demasiado vinculadas a la CPU.

IO pueden ser muchas cosas. El servicio web y las llamadas a bases de datos se pueden abstraer y burlar, y las pocas llamadas reales que tiene que hacer se pueden mover a la fase de integración si es necesario. El registro también puede ralentizar las cosas, especialmente con 3.000 casos de prueba. Yo diría que simplemente apague el registro por completo y confíe en su cerebro y su depurador cuando falla una prueba.

Puede haber casos donde el propio IO es la unidad que se está probando. Por ejemplo, si está probando la parte de un servidor de base de datos que escribe datos de tabla en el disco. En ese caso, trate de mantener tanto IO en la memoria como sea posible. En Java, muchas de las abstracciones de IO tienen implementaciones en memoria.

La prueba de límite de CPU viene en diferentes sabores también. El rendimiento puro y la prueba de rendimiento deben estar en la fase de prueba de integración. Si está generando un montón de hilos para probar un error de simultaneidad, puede mover la gran prueba a la fase de integración y mantener una versión "ligera" en su conjunto de pruebas habitual.

Y, por último, el generador de perfiles es tu amigo. Es posible que partes de tu código se vuelvan más eficientes y aceleren notablemente tus pruebas.

+0

no es una aplicación web. así que estoy bastante seguro de que no está vinculado a IO. si realmente son algunos casos de prueba que toman muchísimo tiempo. Supongo que el problema es más fácil de arreglar. gracias, intentaré jprofiler. –

4

Supongo que ha realizado todos los demás pasos habituales, como burlarse de las llamadas a la base de datos, optimizar las fases de configuración de prueba, etc. y lo que hace que la prueba sea tan larga es que tiene 3000 pruebas, no pruebas individuales lento.

En caso afirmativo, un enfoque es ejecutar varias veces la prueba. Test-NG lo admite muy bien. Convertir sus pruebas de junit a test-ng no es tan difícil y solo necesita hacerse una vez.

Las pruebas que se tienen que ejecutar secuencialmente pueden ser fácilmente marcados:

@Test(sequential = true) 
public class ATest { 
    ... 

En una máquina multi-núcleo que va a ver grandes mejoras en el tiempo de ejecución. Incluso en un solo núcleo, verá una buena mejora ya que algunos subprocesos esperan operaciones io.

Ver aquí para más detalles sobre cómo configurar esto:

http://beust.com/weblog/archives/000407.html

Espero que esto ayude.

....

Algunas sugerencias más - No puedo creer que no está usando la integración continua. Confía en mí 30 desarrolladores no va a sobrecargar su servidor de CI. Incluso si no puede obtener la aprobación de CI, instale hudson en su propia máquina; su instalación tardará 10 minutos y los beneficios son enormes. Pregúntele a su gerente qué es peor que cada desarrollador sentado mientras espera que se completen las pruebas unitarias o que un servidor lo haga por usted. Tener un sombrero estúpido para usar para la persona que rompió la compilación suele ser suficiente para convencer a los desarrolladores para ejecutar sus pruebas unitarias.

Si la calidad de los checkins es realmente una gran preocupación (no olvide que un check-in siempre se puede retrotraer) considere Teamcity - ejecuta las pruebas y no confirma el código si las pruebas fallan.

Finalmente, una opción que puede adaptarse a su caso de uso también es clover and bamboo. La última versión mantiene un registro de qué código es probado por qué pruebas y cuando se realiza un cambio solo ejecuta las pruebas relevantes. Esto es potencialmente muy poderoso.

¡Pero recuerde que las herramientas inteligentes como test-ng, teamcity y trébol solo le llevarán tan lejos, las buenas pruebas no se escribirán!

Para resumir mi solución es tratar todos o algunos de los siguientes pasos:

  1. Optimizar las pruebas - burla, configuración común etc.
  2. Ejecutar las pruebas en paralelo
  3. conseguir algo más de ejecute las pruebas por usted: conviértalo en una tarea fuera de línea usando hudson o similar
  4. Ejecute solo las pruebas que deben ejecutarse; ordénelas en paquetes o use trébol y bambú.
+0

Esto parece prometedor. gracias. –

1

Acepto con Pablojim. Paralelize sus pruebas. Estamos utilizando clearcase y la transferencia de todo desde los servidores de visitas realmente ralentiza las cosas. Cuando paralelizamos en un duelo, obtuvimos una prueba más corta, 6-8 veces más rápida.

Estamos utilizando el marco CPPUnit y acabamos de agregar un scripts de python para poner en marcha diferentes suites de prueba en diferentes hilos.

También hemos utilizado clearmake para paralelizar el proceso de compilación. Nuestro siguiente paso será probablemente paralelizar las pruebas con los clientes de los desarrolladores.

+0

también estamos usando clearcase, ¿tiene claro tener algún soporte para la construcción de proyectos de Java? –

+0

No tengo experiencia en la creación de aplicaciones Java con clearmake, pero Google proporcionó rápidamente: http://publib.boulder.ibm.com/infocenter/cchelp/v7r0m0/index.jsp?topic=/com.ibm.rational.clearcase. books.cc_build_unix.doc/using_clearcase_build_tools_with_java.htm – tombjo

1

Mueva el conjunto de pruebas completo en un sistema de motor COntinuous INtegration, por lo que los desarrolladores no tienen que ejecutarlos todos siempre. Dichos sistemas tienen MUCHA paciencia que los desarrolladores.

+0

sugiero este enfoque. sin embargo, el gerente teme que los desarrolladores se vuelvan perezosos y que nadie ejecute ningún caso de prueba antes de comprometer el código. para garantizar la calidad de la rama principal, podemos configurar una creación continua para la rama de todos. tenemos alrededor de 30 desarrolladores, que pueden sobrecargar el servidor de CI. pero, sí, vale la pena intentarlo y ver cómo funciona realmente. –

+0

Si los usuarios no pueden verificar el código antes de registrarse, entonces su gerente debe tener un trabajo con ellos sobre esto. No sugeriré que tenga un administrador perezoso;) IntelliJ/TeamCity tiene una función que le permite realizar la compilación en el servidor y solo verificar el código si pasan todas las pruebas. La integración continua ahorra mucho tiempo, realmente debería tener una. –

+0

@James, eche un vistazo a Hudson. Es bastante fácil de escalar, ya que permite el uso de computadoras esclavas para construir.Si actualmente es una molestia ejecutar pruebas de unidad, entonces haz eso, y luego deja que los que rompan la construcción tengan que comprar algo para todo el equipo (solo algo pequeño). –

4

¿Está utilizando fork="yes" en su llamada junit? Si es así, asegúrese de configurar forkMode="once", de lo contrario, la tarea junit iniciará una nueva máquina virtual para cada clase TestCase. Con 3000 pruebas unitarias, eso hará una gran diferencia.

http://ant.apache.org/manual/Tasks/junit.html

+0

He visto esto. Lo que pasa es que no estamos utilizando el Testitun junit de hormig, sino uno personalizado en un jar. No sé cómo se implementó, ya que el código fuente no está disponible para nosotros :(. –

+0

Tenga en cuenta que Eclipse con un descompilador como jadclipse puede ayudarlo a mirar dentro de ese contenedor. Ponlo en la ruta de compilación y tenga una mirada. –

+0

mirando el archivo build.xml, TestRunner no iniciará un jvm para cada caso de prueba. gracias por recomendar jadclipse, estoy seguro de que será útil en el futuro. –

1

Me gustaría tratar esto como lo haría con cualquier otro problema de rendimiento:

  1. no hacen suposiciones acerca de cuál es el problema
  2. Analizar la ejecución de la prueba con un perfilador para determinar puntos de acceso
  3. Analice los puntos conflictivos de uno en uno, volviendo a realizar la prueba después de cada cambio de código.

Es posible que tenga que investigar ese corrector de prueba con el tiempo. Puede usar una herramienta de descompilación como cavaj para generar código fuente desde el archivo de clase (aunque será más difícil de leer que el código original, obviamente). Es posible que algo en la implementación del corredor de prueba esté afectando el rendimiento. Por ejemplo, ya mencionó leer archivos de configuración XML como una actividad que realiza el corredor de prueba; esto es algo que podría afectar potencialmente el rendimiento.

Otra área en la que finalmente podría encontrar problemas de rendimiento es en clases de caso de prueba 'base' personalizadas. Estos tienden a ser cosas que agregan mucha comodidad, pero puede ser difícil recordar que sus comportamientos de adición de conveniencia se amortizarán potencialmente en pruebas de 10k en un proyecto grande, independientemente de si cada prueba necesita el comportamiento de conveniencia o no.

1

Yo sugeriría que tiene dos construcciones (por pasos que se ejecuta en cada registro, y una generación completa que se ejecuta durante la noche)

Las pistas incrementales pruebas más cortas en unos 7 minutos, mientras que la construcción completa se ejecuta todas las pruebas por más tiempo en menos de 40 minutos.

Clearcase fomenta una pesadilla de ramificación, pero debería poder tener dos compilaciones por desarrollador. Me gustaría cuestionar el valor de tener cada desarrollo en su propia rama, ya que creo que hay algún beneficio en que los desarrolladores trabajen juntos (en pares o más) en la misma rama.

Nota: Un servidor de integración continua puede tener cualquier cantidad de agentes y si no puede pagar más de un servidor, puede usar las PC como agentes de compilación. (Debe tener al menos 30 de ésos)

+0

tiene razón. no todos dev trabaja en su propia rama. solo los desarrolladores que arreglan errores trabajan en sus sucursales. los desarrolladores de funciones trabajan en la misma rama. –

1

Aquí está el enfoque que tomaría.

  1. Revise sus casos de prueba, busque las pruebas redundantes. Con 3000 pruebas, es probable que sea doble y quíntuple cubriendo partes que no necesitan ser.
  2. Elija sus "canarios". Estas son las pruebas que desea ejecutar siempre, las que olerá el peligro en otras partes. Son más que probables los casos de prueba de nivel superior que prueban las interfaces API públicas que se usan entre los componentes. Si uno de ellos falla, puede ingresar y ejecutar el conjunto de pruebas completo para el componente.
  3. Comience a migrar a un marco como TestNG y comience a clasificar sus casos de prueba, luego ejecute solo la clasificación de lo que está trabajando con las pruebas completas nocturnas.
0

La forma más eficaz de acelerar un conjunto de pruebas grande es ejecutarlo de forma incremental, de modo que solo se vuelvan a ejecutar las pruebas de ese código táctil modificado desde la última ejecución de prueba. Después de todo, las pruebas más rápidas serán siempre las que están no ejecutadas. 8 ^)

La parte difícil es hacer que esto funcione. Actualmente estoy trabajando en pruebas incrementales para JUnit 4, que es parte de la herramienta "JMockit Coverage" en el kit de herramientas de prueba para desarrolladores JMockit. Todavía es inmaduro, pero creo que funcionará bien.

0

El acceso a la base de datos y la latencia de la red pueden ser un área a examinar. Si está realizando una gran cantidad de acceso a la base de datos en sus pruebas de integración, puede explorar utilizando una base de datos en memoria como HSQL, H2 o Derby en lugar de una base de datos "real" como Oracle. Si está utilizando Hibernate, también deberá cambiar la configuración en sus configuraciones de Hibernate para usar el dialecto específico de ese DB (por ejemplo, HSQLDialect en lugar de OracleDialect). Estaba en un proyecto una vez en el que cada compilación completa terminaría por tener que volver a crear un esquema completo de Oracle y realizar numerosas pruebas de DB a través de la red, que a veces tardaría hasta 20 minutos y luego encontraría a alguien registrado y las cosas se rompieron de nuevo. :(

Lo ideal sería tener solo un script DB que se pueda usar para ambas bases de datos, pero podría terminar sincronizando dos scripts de creación de bases de datos diferentes: uno para la producción y otro para las pruebas de integración.

DB en la misma JVM vs DB en toda la red, podría hacer la diferencia.

Cuestiones relacionadas