Академический Документы
Профессиональный Документы
Культура Документы
Tutorial de NUnit
1. INTRODUCCIÓN
El hecho de utilizar TDD implica 3 acciones: escribir las pruebas, escribir el código que
debe pasar las pruebas y refactorizar para eliminar el código duplicado.
La primera se lleva a cabo de una manera sencilla y simple utilizando NUnit, NUnit
soporta los lenguajes bases de .NET como C#, J#, VB y C++.
2. DESCRIPCION
3. FUNCIONAMIENTO
El segundo son las aserciones, que no son mas que métodos del framework de NUnit
utilizados para comprobar y comparar valores.
3.1 ATRIBUTOS
La versión 1x de NUnit utilizaba las convenciones de nombrado del framework de
.NET, pero desde la versión 2 en adelante NUnit usa atributos personalizados.
Dado que el framework de NUnit no deriva de otra clase o framework, el desarrollador
puede elegir el nombre de la prueba a su antojo.
TestFixture
Este atributo se utiliza para indicar que una clase contiene métodos de prueba. En
versiones anteriores para poder utilizar este atributo se debía extender (heredar de)
la clase TestCase, a partir de la versión 2 esto no es necesario, situación que hace
mucho más flexible el uso del atributo.
Existen algunas restricciones como por ejemplo que la clase debe tener un
constructor por defecto y debe ser publica para que el framework NUnit pueda
accederla.
Ejemplo:
#using <Nunit.Framework.dll>
using namespace System;
using namespace NUnit::Framework;
namespace NUnitTests
{
[TestFixture]
public __gc class SuccessTests
{
// ...
};
}
#include "cppsample.h"
namespace NUnitTests {
// ...
}
package NUnit.Tests;
import System.*;
import NUnit.Framework.TestFixture;
Imports System
Imports Nunit.Framework
Namespace Pruebas
End Class
End Namespace
Test
Se utiliza para marcar un método como método de prueba, éste no debe tener
parámetros de entrada y debe retornar void (en el caso de visual Basic ser un sub) así
mismo la clase que lo alberga debe tener marcado el atributo TextFixture.
Ejemplo:
Imports System
Imports Nunit.Framework
Namespace Pruebas
TestFixtureSetUp / TestFixtureTearDown
El primero de estos atributos se encarga de crear el ambiente de pruebas antes de
ejecutarlas y el segundo se encarga de restaurar el ambiente después de que las
pruebas han sido ejecutadas. Van de la mano con los atributos Setup y TearDown,
Setup es llamado antes de que se ejecute cualquier prueba. TearDown es llamado
después de que una prueba se ejecute.
Una clase marcada con TextFixture solo puede tener un método marcado con
TestFixtureSetUp y un solo método marcado con TestFixtureTearDown, si existe mas
de un método marcado con estos atributos el proyecto compilara pero no se
ejecutaran las pruebas. El orden de ejecución es el siguiente:
ExpectedException
Este atributo como su nombre lo sugiere, tiene como funcionalidad indicar que la
ejecución de un método prueba va a lanzar una excepción, el atributo tiene como
parámetro el tipo de excepción que se espera que lance el método, el framework
ejecuta la prueba y si se genera una excepción del tipo especificado entonces la
prueba es exitosa, si por el contrario se genera una excepción de tipo diferente al
especifico la prueba no lo es. Esto es cierto aun cuando la excepción lanzada herede
de la excepción esperada, es decir la excepción debe ser exactamente la especificada
en el parámetro del atributo
Ejemplo:
namespace Pruebas
{
using System;
using NUnit.Framework;
[TestFixture]
public class Pruebas
{
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void GeneraExcepcion()
{
//...
}
}
Imports System
Imports Nunit.Framework
Namespace Nunit.Tests
#using <Nunit.Framework.dll>
using namespace System;
using namespace NUnit::Framework;
namespace NUnitTests
{
[TestFixture]
public __gc class SuccessTests
{
[Test] [ExpectedException(__typeof(InvalidOperationException))]
void ExpectAnException();
};
}
#include "cppsample.h"
namespace NUnitTests {
// ...
}
Suite
El atributo suite es utilizado para definir subconjuntos de pruebas de acuerdo a las
preferencias del usuario, sin embargo este atributo ha sido reemplazado desde la
versión 2 debido al nuevo mecanismo dinámico de ejecución de pruebas del
framework (en modo gráfico se puede seleccionar que pruebas se desean ejecutar
utilizando el atributo category, además se pueden agrupar dentro de una estructura
marcada como TestFixture). En general es soportada para proveer compatibilidad
hacia atrás.
En las nuevas versiones del producto no se puede correr suites.
Category
El atributo category provee una alternativa a las suites para trabajar con grupos de
pruebas. Cualquiera, ya sea casos de prueba individuales o Fixtures, pueden ser
identificadas como pertenecientes a una categoría de pruebas en particular. Ya sea en
modo grafico o en modo consola se puede especificar que categorías se excluyen o
incluyen en la ejecución. Cuando se utilizan categorías, solo las pruebas de la
categoría seleccionada son ejecutadas. Las pruebas incluidas en las categorías que se
excluyen de la ejecución no son reportadas.
Para excluir o incluir determinada categoría en el modo consola incluya el parámetro
/exclude o /include seguido del nombre de la categoría. En el modo gráfico existe una
pestaña denominada categorías.
Es importante anotar que esta funcionalidad solo se encuentra presente en la versión
2.2 del producto. Este atributo puede utilizarse junto con TextFixture o Test
Ejemplos:
Imports System
Imports Nunit.Framework
Namespace Pruebas
<TestFixture(), Category("PruebasLargas")>
Public Class PruebasLargas1
'...
End Class
End Namespace
Imports System
Imports Nunit.Framework
Namespace Pruebas
<TestFixture()>
Public Class PruebasExitosas
<Test(), Category("Corta")> Public Sub PruebaCorta()
'...
End Sub
End Class
End Namespace
Explicit
Este atributo ocasiona que una prueba o un Fixture sean ignorados por el programa y
no sean ejecutados a menos que sea especificado lo contrario. La prueba o Fixture
será ejecutada si se selecciona directamente en la interfaz grafica de usuario, si su
nombre es especificado en la línea de comandos, como el Fixture a ejecutar o si es
incluido en una categoría.
Si una prueba o un Fixture con el atributo Explicit es encontrado durante la ejecución,
Ejemplos:
namespace Pruebas
{
using System;
using NUnit.Framework;
[TestFixture, Explicit]
public class PruebaX
{
//...
}
}
namespace Pruebas
{
using System;
using NUnit.Framework;
[TestFixture]
public class PruebaX
{
[Test, Explicit]
public void PruebaExp()
{
//...
}
}
}
Ignore
Utilizado para indicar que se debe ignorar determinada prueba o Fixture, El programa
ve el atributo y no ejecuta la prueba o las pruebas, el icono se coloca amarillo y la
prueba es reportada como no ejecutada. Esto es útil para inhabilitar pruebas
temporalmente, es decir, este atributo es útil para marcar una prueba o Fixture, si no
se desea ejecutarla momentáneamente, en vez de comentar el método o la clase, ya
que de todas maneras va a ser compilada junto con el resto del código pero no será
tenida encuentra a la hora de ejecutar las pruebas.
Ejemplos:
Imports System
Imports Nunit.Framework
Namespace Pruebas
Imports System
Imports Nunit.Framework
Namespace Pruebas
<TestFixture()>
Public Class PruebasX
<Test(), Ignore("Ignore esta prueba")> Public Sub Ignorada()
'...
End Sub
End Class
End Namespace
3.2 ASERCIONES
Las aserciones son métodos estáticos que la clase assert provee para realizar
comparaciones y condiciones de prueba.
Comparaciones
Las aserciones mas utilizadas son las de comparación, éstas reportan tanto el valor
esperado como el valor actual. El valor esperado es siempre el primer parámetro en
un método assert, por ejemplo:
El Segundo esta sobrecargado para trabajar con la mayoría de los tipos comunes(int,
integer, double, single, string, etc.), por ejemplo
Condiciones de Prueba
Los métodos que evalúan una condición se denominan de acuerdo a la condición. El
valor a probar es el primer argumento y opcionalmente un mensaje como segundo.
Ejemplo:
Por ultimo tenemos el método Assert.Fail que es utilizado para generar fallos que no
son considerados por los otros métodos.
4. UTILIZACIÓN
4.1 INTERFACES
NUnit provee dos interfaces de usuario, la interfaz gráfica y la interfaz de consola.
Ambas pueden ser instanciadas desde la línea de comandos y la gráfica se puede
iniciar también haciendo doble clic sobre el icono en el escritorio.
Interfaz Gráfica
Esta interface puede ejecutarse con o sin un nombre de ensamblado desde la ventana
de comandos, en el caso que no se especifique ningún ensamblado la aplicación carga
el ultimo ensamblado cargado.
· Para evitar este comportamiento se debe escribir: nunit-gui /noload, por el contrario
si desea especificar un ensamblado o nombre de proyecto desde la línea de comandos
escriba:
nunit-gui [nombreEnsamblado]
Interfaz Consola
· Si desea especificar el nombre del archivo xml resultado de las pruebas teclee:
nunit-console /xml:[nombre.xml] [ensamblado]
· Para ejecutar las pruebas de varios ensamblados separe el nombre de estos con
espacios:
nunit-console [ensamblado1] [ensamblado2 ] [ensamblado3]
Se pueden especificar multiples ensamblados pero no multiples proyectos de VS.Net o
de NUnit
Otras opciones:
/wait le indica a NUnit que espere la entrada de usuario antes de salir.
/xmlconsole despliega xml puro en la consola, esto es útil cuando se esta depurando
Ejemplo. Supongamos que hemos escrito una aplicación bancaria y que tenemos una
clase de dominio básica de Cuenta bancaria (Account). Account soporta operaciones
para depósito (deposit), retiro (withdraw) y transferencia (transfer) de fondos. La
clase sería algo similar a esto:
namespace bank
{
public class Account
{
private float balance;
public void Deposit(float amount)
{
balance+=amount;
}
Ahora vamos a escribir una prueba para esta clase - AccountTest. El primer método
que probaremos será transferencia de fondos (TransferFunds).
namespace bank
{
using NUnit.Framework;
[TestFixture]
public class AccountTest
{
[Test]
public void TransferFunds()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);
source.TransferFunds(destination, 100.00F);
Assertion.AssertEquals(250.00F, destination.Balance);
Assertion.AssertEquals(100.00F, source.Balance);
}
}
}
Lo primero que se nota en esta clase es que posee un atributo asociado a ella llamado
[TestFixture] – este es la forma de indicar que la clase contiene código de prueba.
Esta clase es pública (public) y además cuenta con un constructor (el que provee el
framework).
Compila y ejecuta este ejemplo. Asumamos que compilaste tu clase de prueba como
bank.dll. Ejecuta la GUI de NUnit (el instalador habrá creado un acceso directo en tu
escritorio y en la carpeta Archivos de Programa), luego de haber iniciado el GUI,
selecciona FileàOpen ,menu item, desplázate hacia la ubicación de tu archivo bank.dll
y selecciónalo en el cuadro de diálogo “Open”. Cuando la librería bank.dll se haya
cargado, verás una estructura de árbol para la prueba en el panel izquierdo y una
colección de paneles de estado a la derecha. Haz click en el botón Run, la barra de
estado y el nodo TransferFunds en el árbol de prueba se tornarán rojos – nuestra
prueba ha fallado. El panel “Errors and Failures” mostrará el siguiente mensaje –
“TransferFunds : expected <250> but was <150>” y el panel de stack trace justo
abajo reportará en que lugar en el código de prueba ocurrió el error- “at
bank.AccountTest.TransferFunds() in C:\nunit\BankSampleTests\AccountTest.cs:line
17”
namespace bank
{
using System;
public class InsufficientFundsException : ApplicationException
{
}
}
[Test]
[ExpectedException(typeof(InsufficientFundsException))]
public void TransferWithInsufficientFunds()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);
source.TransferFunds(destination, 300.00F);
}
Este método de prueba además del atributo [Test] posee otro llamado
[ExpectedException] - esta es la forma de indicar que el código de prueba está
esperando una excepción de cierto tipo; si tal excepción no ocurre durante la
ejecución – la prueba fallará. Compila tu código y regresa a la GUI. Mientras
compilabas tu código de prueba, la GUI se debió tornar de color gris y habrá
colapsado el árbol como si las pruebas no hubiesen sido ejecutadas aún (la GUI
espera por cambios hechos en los assemblies de prueba y se actualiza a si misma
cuando la estructura del árbol de prueba cambia – Ej.: cuando se ha agregado una
nueva prueba). Haz click en el botón Run – ahora tenemos una barra de estado roja
nuevamente. Tenemos la misma falla: “TransferWithInsufficentFunds :
InsufficientFundsException was expected”. Corrijamos nuestro código de Account
nuevamente, modifiquemos el método TransferFunds de la siguiente forma:
Compila y ejecuta las pruebas – verás una barra verde. La prueba fue exitosa! Pero
espera, mirando al código que hemos escrito nos podemos dar cuenta que el banco
podría estar perdiendo dinero en cada operación fallida de Transferencia de fondos.
Escribamos una prueba para confirmar nuestra sospecha. Añade este método de
prueba:
[Test]
public void TransferWithInsufficientFundsAtomicity()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);
try
{
source.TransferFunds(destination, 300.00F);
}
catch(InsufficientFundsException expected)
{
}
Assertion.AssertEquals(200.00F,source.Balance);
Assertion.AssertEquals(150.00F,destination.Balance);
}
[Test]
[Ignore("Se necesita decidir como implementar el manejo transaccional en
la aplicación")]
public void TransferWithInsufficientFundsAtomicity()
{ el mismo código}
Compila y ejecuta – Verás una barra amarilla. Haz click en la viñeta “Tests Not Run” y
Al ver nuestro código de prueba nos damos cuenta que se puede hacer algo de
refactorización. Todos los métodos comparten un conjunto común de objetos de
prueba. Extraigamos el código de inicialización a un método de SetUp y reusémoslo
en todas nuestras pruebas. La versión refactorizada de nuestra clase de prueba sería:
namespace bank
{
using System;
using NUnit.Framework;
[TestFixture]
public class AccountTest
{
Account source;
Account destination;
[SetUp]
public void Init()
{
source = new Account();
source.Deposit(200.00F);
destination = new Account();
destination.Deposit(150.00F);
}
[Test]
public void TransferFunds()
{
source.TransferFunds(destination, 100.00f);
Assertion.AssertEquals(250.00F, destination.Balance);
Assertion.AssertEquals(100.00F, source.Balance);
}
[Test]
[ExpectedException(typeof(InsufficientFundsException))]
public void TransferWithInsufficientFunds()
{
source.TransferFunds(destination, 300.00F);
[Test]
[Ignore("Se necesita decidir como implementar el manejo transaccional en
la aplicación ")]
public void TransferWithInsufficientFundsAtomicity()
{
try
{
source.TransferFunds(destination, 300.00F);
}
catch(InsufficientFundsException expected)
{
}
Assertion.AssertEquals(200.00F,source.Balance);
Assertion.AssertEquals(150.00F,destination.Balance);
}
}
}
Nota que el método Init tiene el código común de inicialización, posee tipo de retorno
void, sin parámetros, y está marcado con el atributo [SetUp]. Compila y ejecuta- vas
a ver la misma barra amarilla!
Es de anotar que NUnit que puedes hacer debug asociando NUnit al proceso de
depuración de Visual Studio, de esta manera es posible ejecutar las pruebas y hacer
el seguimiento al programa al mismo tiempo. Para hacer esto inicie NUnit, en
VisualStudio de clic en el menú herramientas -proceso de depuración y seleccione
NUnit, a continuación puede trabajar con todas las facilidades de debug que
proporciona el IDE. Por ultimo tenga en cuenta que cada vez que recompile el código
tendrá que volver a asociar NUnit al IDE.
Nota: En caso se este usando Visual Studio 2003, deberás usar el NUNIT para Framework 1.0 y si estas
usando VS .2005 entonces usaras el NUNIT para Framework 2.0