Ejecutar Tests y Analizar

Si todavía tenemos problemas incluso después de haber revisado los problemas de instalación y de entorno y haber incluido código de depuración, es el momento de usar herramientas para probar y analizar nuestro programa.

Trabajar Detrás de la Silla con jdb

Aunque hay algunas muy buenas herramientas IDE en el mercado, la herramienta de depuración JavaTM, jdb y sus sucesores tienen un papel importante que jugar en la prueba y depuración de programa. algunas ventajas de jdb sobre los IDE es que es gratis, es independiente de la plataforma (algunos IDE no lo son), y se ejecuta como un proceso separado al programa que está depurando. El beneficio de ejecutar jdb como un proceso separado es que podemos añadir una sesión de depurado a un programa que está ejecutándose.

El lado negativo de usar jdb es que sólo hay un interface de línea de comandos, y trata con el mismo código que estámos tratando de depurar. Esto significa que si hay un bug enla máquina virtual Java, jdb se podría equivocar al intentar diagnisticar el mismo bug!

La nueva arquitectura JBUG se creó para resolver estos problemas en el jdb. JBUG, entre otras cosas, proporciona un API de ayuda de depuración en la máquina virtual Java llamado "Java VM Debug Interface" (JVMDI). Este ayudante se comunica con el depurador desde el final usando el "Java Debug Wire Protocol" (JDWP). La depuración desde el final usa el interface remoto "Java Debug Interface" (JDI) para enviar y recibir comando sobre el protocolo JDWP. JBug está disponible para la plataforma Java 2, y tiene un estilo jdb que aprenderemos más adelante.

Prueba Sencilla con jdb

De vuelta a la clásica herramienta jdb. Aquí tenemos uno sencillos pasos para analizar un programa usando jdb. Este primer ejemplo depura un programa de la aplicación startup. El ejemplo Depuración Remota muestra como conectarlo con una aplicación que se está ejecutando.

Arrancar la Sesión

Para empezar una sesión de depurado, compilamos el programa SimpleJdbTest.java con información completa de depurado usando javac y la bandera -g. En este ejemplo, el programa SimpleJdbTest.java es una aplicación pero también podría ser un applet. Los procedimientos para depurar aplicaciones son iguales que para depurar applets una que se ha empezado la sesión de depurado.
javac -g SimpleJdbTest.java
Luego arrancamos la herramienta jdb con el nombre de la clase del programa como parámetro:
jdb SimpleJdbTest
Initializing jdb...
0xad:class(SimpleJdbTest)
Para depurar un applet en el appletviewer usamos el parámetro -debug como en este ejemplo:
$ appletviewer -debug MyApplet.html
Initializing jdb...
0xee2f9808:class(sun.applet.AppletViewer)
> 

Seleccionar un método de ruptura y métodos de listado

En este punto, sólo se ha cargado la clase SimpleJdbTest; no se ha llamado al constructor de la clase. Para hacer que el jdb se pare cuando el programa se inicializa por primera vez, ponemos un stop, o punto de ruptura, en el constructor usando el comando stop in. Cuando se seleccionan puntos de ruptura, instuirmos al jdb a ejecutar nuestro programa usando el comando run de esta forma:
stop in SimpleJdbTest.<init>
Breakpoint set in SimpleJdbTest.<init>
run
run SimpleJdbTest
running ...
main[1]
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:10)
La herramienta jdb se para en la primera línea del constructor. Para listar los método que fueron llamados hasta llegar a este punto de ruptura, introducimos el comando where:
main[1] where
[1] SimpleJdbTest.<init> (SimpleJdbTest:10)
[2] SimpleJdbTest.main (SimpleJdbTest:29)
Los métodos numerados de la lista es el último marco de pila que ha alcanzado la JVM. En este caso el último marco de pula es el constructor SimpleJdbTest que fue llamado desde el SimpleJdbTest main.

Siempre que se llama a un nuevo método, se sitúa en esta lista de pila. La tecnología Hotspot consigue alguna de sus ganancias de velocidad elimando un nuevo marco de pila cuando se llama a un nuevo método. Para obtener una apreciación general de dónde se paró el código, introducimos el comando list.

main[1] list
6 		Panel p;
7 		Button b;
8 		int counter=0;
9
10 		SimpleJdbTest() {
11 			setSize(100,200);
12 			setup();
13 		}
14 		void setup (){

Localizar la Fuente

Si el fuente del fichero class parado no está disponible en el path actual, podemos decirle a jdb donde encontrar el fuente con el comando use dándole el directorio fuente como un parámetro. En el siguiente ejemplo el fuente está un subdirectorio o carpeta llamado book.
main[1] list
Unable to find SimpleJdbTest.java
main[1] use book
main[1] list
6 		Panel p;
7 		Button b[];
8 		int counter=0;
9 
10 	=> 	SimpleJdbTest() {

Buscar un Método

Para ver que sucede en el método setup de SimpleJdbText, usamos el comando step para pasar a través de sus 4 líneas y ver lo que pasa.
main[1] step
main[1]
Breakpoint hit: java.awt.Frame.<init> (Frame:222)
Pero espera un minuto! Este es ahora el constructor de la clase Frame! Si lo seguimos pasaremos a través del constructor de la clase Frame y no el de la clase SimpleJdbText. Porque SimpleJdbTest desciende de la clase Frame, el constructor padre, que en este caso es Frame, es llamado sin avisarnos.

El comando step up

Podríamos continuar pasando y eventualmente volveríamos al constructor de SimpleJdbTest, pero para retornar inmediatamente podemos usar el comando step up para volver al constructor de SimpleJdbTest.
main[1] step up
main[1]
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:8)

El comando next

También podemos usar el comando next para obtener el método setup. En este siguiente ejemplo, la herramienta jdb ha aproximado que el fichero fuente está fuera del constructor cuando procesó el último comando step up. Para volver al constructor, usamos otro comando step, y para obtener el método setup, usamos un comando next. Para depurar el método setup, podemos step (pasar) a través del método setup.
main[1] step
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:11)
main[1] list
7 		Button b[]=new Button[2];
8		int counter=0;
9
10 		SimpleJdbTest() {
11 			setSize(100,200);<
12 			setup();
13 		}
14 		void setup (){
15 			p=new Panel();
16		}
main[1] next
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:12)
main[1] step
Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:15)

El comando stop in

Otra forma de obtener el método setup es usar el comando stop in SimpleJdbTest.setup. Podemos listar el fuente de nuevo para saber donde estamos:
main[1] list
11 			setSize(100,200);
12 			setup();
13 		}
14 		void setup (){
15	=>		p=new Panel();
16 			  b[0]= new Button("press");
17 			  p.add(b[0]);
18 			  add(p);
19

El comando print

Lo primero que hace el método setup es crear un Panel p. Si intentamos mostrar el valor de p con el comando print p, veremos que este valor es null.
main[1] print p
p = null
Esto ocurre porque la línea aún no se ha ejecutado y por lo tanto al campo p no se le ha asignado ningún valor. Necesitamos pasar sobre la sentencia de asignación con el comando next y luego usar de nuevo el comando print p.
main[1] next

Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:16)
main[1] print p
p = java.awt.Panel[panel0,0,0,0x0,invalid,
		     layout=java.awt.FlowLayout]

Seleccionar Puntos de Ruptura en Métodos Sobrecargado

Aunque pasar a través de clases pequeñas es rápido, como regla general en grandes aplicaciones, es más rápido usar puntos de ruptura. Esto es así porque jdb tiene un conjunto de comandos muy simples y no teine atajos, por eso cada comando teine que ser pegado o tecleado por completo.

Para seleccionar un punto de ruptura en la clase Button, usamos stop in java.awt.Button.<init>

main[1] stop in java.awt.Button.<init>
java.awt.Button.<init> is overloaded,
          use one of the following:
void <init>
void <init>java.lang.String)
El mensaje explica porque jdb no puede parar en este método sin más información, pero el mensaje nos explica que sólo necesitamos ser explícitos en el tipo de retorno para los métodos sobrecargados en los que queremos parar. Para parar en el constructor de Button que crea este Button, usamos stop in java.awt.Button.<init>java.lang.String).

El comando cont

Para continuar la sesión jdb, usamos el comando cont . La siguiente vez que el programa cree un Button con un constructor String, jdb se parará para que podamos examinar la salida.
main[1] cont
main[1]
Breakpoint hit: java.awt.Button.<init> 
                          (Button:130)
Si la clase Button no ha sido compilada con información de depurado como se describió antes, no veremos los campos internos desde el comando print.

Limpiar Puntos de Ruptura

Para limpiar este punto de ruptura y que no pare cada vez que se cree un Button se usa el comando clear. Este ejemplo usa el comando clear sin argumentos para mostrar la lista de puntos de ruptura actuales, y el comando clear con el argumento java.awt.Button:130. para borrar el punto de ruptura java.awt.Button:130..
main[1] clear
Current breakpoints set:
SimpleJdbTest:10
java.awt.Button:130
main[1] clear java.awt.Button:130
Breakpoint cleared at java.awt.Button: 130

Mostrar Detalles del Objeto

Para mostrar los detalles de un objeto, usamos el comando print para llamar al método toString del objeto, o usar el comando dump para mostrar los campos y valores del objeto.

Este ejemplo pone un punto de ruptura en la línea 17 y usa los comandos print y dump para imprimir y volcar el primer objeto Button del array de objetos Button. La salica del comando The dump ha sido abreviada.

main[1] stop at SimpleJdbTest:17
Breakpoint set at SimpleJdbTest:17
main[1] cont
main[1]
Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:17)

main[1] print b[0]
b[0] = java.awt.Button[button1,0,0,0x0,invalid,
					label=press]
main[1] dump b[0]
b[0] = (java.awt.Button)0x163 {
private int componentSerializedDataVersion = 2
boolean isPacked = false
private java.beans.PropertyChangeSupport 
			changeSupport = null
long eventMask = 4096
transient java.awt.event.InputMethodListener 
			inputMethodListener = null
....
java.lang.String actionCommand = null
java.lang.String label = press
}

Finalizar la Sesión

Esto finaliza el sencillo ejemplo jdb. Para terminar una sesión jdb, se usa el comando quit:
0xee2f9820:class(SimpleJdbTest)
> quit

Depuración Remota

El jdb es un proceso de depuración externo, lo que significa que depura el programa enviándole mensajes hacia y desde el ayudante de la máquina virtual Java. Esto hacer muy fácil la depuración de un programa en ejecución, y nos ayuda a depurar un programa que interactua con el usuario final. Una sesión de depuración remota desde la línea de comandos no interfiere con la operación normal de la aplicación.

Arrancar la Sesión

Antes de la versión Java 2, lo único que se requería para permitir la depuración remota era arrancar el programa con la bandera -debug como primer argumento, y si la aplicación usa librerías nativas, terminanos el nombre de la librería con una _g. Por ejemplo, necesitaríamos una copia de la librería nativelib.dll como nativelib_g.dll para depurar con esta librería.

En Java 2, las cosas son un poco más complicada. Necesitamos decirla a la JVM dónde está el ficheo tools.jar usando la variable CLASSPATH. El fichero tools.jar normalmente se encuentra en el directorio lib de la instalación de la plataforma Java.

También necesitamos desactivar el compilador "Just In Time" (JIT) si existe. Este compilador se desactiva seleccionado la propiedad java.compiler a NONE o a una cadena vacía. Finalmente, como la opción -classpath sobreescribe cualquier classpath seleccionado por el usuario, también necesitamos añadir el CLASSPATH necesario para nuestra aplicación.

Poniéndo todo esto junto, aquí está línea de comandos necesaria para arrancar un programa en modo de depuración remoto. Se pone todo en una sóla línea e incluimos todas las clases necesarias en la línea de comandos.

Windows:

$ java -debug -classpath C:\java\lib\tools.jar;. 
-Djava.compiler=NONE SimpleJdbTest
Agent password=4gk5hm

Unix:

$ java -debug -classpath /usr/java/lib/tools.jar:. 
-Djava.compiler=NONE SimpleJdbTest
Agent password=5ufhic
La salida es el password del agente (en este caso, 4gk5hm) si el programa se arranca de forma satisfactoria. La password de agente se suministra cuando se arranca jdb para que éste peuda encontrar la aplicación arrancada correspondiente en modo depuración en esa máquina.

Para arrancar jdb en modo depuración remoto, suministramos un nombre de host, que puede ser la misma máquina donde se está ejecutando el programa o localhost si estámos depurando en la misma máquina que el programa remoto, y la password de agente.

jdb -host localhost -password 4gk5hm

Listar Threads

Una vez dentro de la sesión jdb, podemos listar los threads activos actualmente, con el comando threads, y usar el comando thread <threadnumber>, por ejemplo, thread 7 para seleccionar un thread para analizarlo. Una vez seleccionado un thread, usamos el comando where para ver los métodos que han sido llamados por este thread.
$ jdb -host arsenal -password 5ufhic
Initializing jdb...
> threads
Group system:
1. (java.lang.Thread)0x9        Signal dispatcher  
			       cond. waiting
2. (java.lang.ref.Reference     0xb Reference Handler
      $ReferenceHandler)       cond. waiting
3. (java.lang.ref.              Finalizer
      Finalizer                cond. waiting
      $FinalizerThread)0xd            
						
4. (java.lang.Thread)0xe       Debugger agent     
		              running    
5. (sun.tools.agent.           Breakpoint handler
      Handler)0x10            cond. waiting
6. (sun.tools.agent.           Step handler
      StepHandler)0x12        cond. waiting
Group main:
7. (java.awt.                  AWT-EventQueue-0 
       EventDispatchThread)   cond. waiting 
       0x19 					         
8. (sun.awt.                    PostEventQueue-0 
      PostEventQueue)0x1b      cond. waiting          
9. (java.lang.Thread)0x1c       AWT-Motif        
			       running                
10. (java.lang.Thread)0x1d      TimerQueue       
			       cond. waiting          
11. (sun.awt.                   Screen Updater
       ScreenUpdater)0x1f      cond. waiting          
12. (java.lang.Thread)0x20      Thread-0         
			       cond. waiting          
> thread 7
AWT-EventQueue-0[1] where
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Object.wait (Object:424)
  [3] java.awt.EventQueue.getNextEvent 
                            (EventQueue:179)
  [4] java.awt.EventDispatchThread.run 
	         (EventDispatchThread:67)

Listar el Fuente

Para listar el fuente, el thread necesita ser suspendido usando el comando suspend. Para permitir que un thread continúe usamos el comando resume. El ejemplo usa resume 7.
AWT-EventQueue-0[1] suspend 7
AWT-EventQueue-0[1] list
Current method is native
AWT-EventQueue-0[1] where
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Object.wait (Object:424)
  [3] java.awt.EventQueue.getNextEvent 
                            (EventQueue:179)
  [4] java.awt.EventDispatchThread.run 
		 (EventDispatchThread:67)
AWT-EventQueue-0[1] resume 7

Finalizar la Sesión

Cuando finalizamos de depurar remotamente este programa, eliminamos cualquier punto de ruptura restante antes de salir de la sesión de depuración. Para obtener una lista de estos puntos de ruptura usamos el comando clear, y para eliminarlos introducimos el comando clear class:linenumber de esta forma:
main[1] clear
Current breakpoints set:
SimpleJdbTest:10

main[1] clear SimpleJdbTest:10
main[1] quit

Usar el Piloto Automático

Un truco poco conocido del jdb es el fichero de arranque jdb. jdb automáticamente busca un fichero llamado jdb.ini en el directorio user.home. Si tenemos varios proyecto, es una buena idea seleccionar una propiedad user.home diferente para cada proyecto cuando arranquemos jdb. Para arrancar jdb con un fichero jdb.ini en el directorio actual, tecleamos esto:
jdb -J-Duser.home=.
El fichero jdb.ini nos permite seleccionar los comandos de configuración de jdb, como use, sin tener que introducir los detalles cada vez que ejecutamos jdb. El siguiente fichero de ejemplo jdb.ini empieza una sesión jdb para la clase FacTest. Incluye los fuentes de la plataforma Java en el path de fuentes y le pasa el parámetro número 6 al programa. Se ejecuta y para en la línea 13, muestra la memoria libre, y espera una entrada posterior.
load FacTest
stop at FacTest:13
use /home/calvin/java:/home/calvin/jdk/src/
run FacTest 6
memory
Aquí está salida de la ejecución del fichero jdb.ini:
$ jdb -J-Duser.home=/home/calvin/java
Initializing jdb...
0xad:class(FacTest)
Breakpoint set at FacTest:13
running ...
Free: 662384, total: 1048568
main[1]
Breakpoint hit: FacTest.compute (FacTest:13)
main[1]
Podríamos peguntarnos si los ficheros jdb.ini pueden usarse para controlar una sesión jdb completa. Desafortunadamente, los comandos en un fichero jdb.ini se ejecutan de forma síncrona, y jdb no espera hasta que se llegue a un punto de ruptuira para ejecutar el siguiente comando. Podemos añadir retardos artificiales con comandos help repetidos, pero no hay garantía de que el thread se suspenda cuando necesitamos que lo haga.

Crear un Diálogo de Sesión

Podemos usar una característica poco conocida de jdb para obtener un registro de nuestra sesión de depuración. La salida es similar a la que veríamos si ejecutáramos jdb -dbgtrace.

Para permitir el diario jdb, creamos un fichero llamado .agentLog en el directorio donde estámos ejecutando jdb o java -debug. En el fichero .agentLog, ponemos el nombre del fichero en el que se escriba la información de la sesión en la primera línea.Por ejemplo, un fichero .agentLog podría tener estos contenidos:

jdblog
Cuando luego ejecutamos jdb o java -debug, veremos que la información de sesión jdb se muestra de esta forma. Podemos usar esta información para recuperar los puntos de ruptura y los comandos introducidos por si necesitamos reproducir esta sesión de depuración.
---- debug agent message log ----
[debug agent: adding Debugger agent to 
system thread list]
[debug agent: adding Breakpoint handler 
to system thread list]
[debug agent: adding Step handler to 
system thread list]
[debug agent: adding Finalizer to 
system thread list]
[debug agent: adding Reference Handler to 
system thread list]
[debug agent: adding Signal dispatcher to 
system thread list]
[debug agent: Awaiting new step request]
[debug agent: cmd socket: 
Socket[addr=localhost/127.0.0.1,
port=38986,localport=3 8985]]
[debug agent: connection accepted]
[debug agent: dumpClasses()]
[debug agent: no such class: HelloWorldApp.main]
[debug agent: Adding breakpoint bkpt:main(0)]
[debug agent: no last suspended to resume]
[debug agent: Getting threads for HelloWorldApp.main]

Ozito