Documentos de Académico
Documentos de Profesional
Documentos de Cultura
5 Implementación de Pruebas de
Integración con Spring y JUnit
Índice
Código de pruebas en Maven
JUnit
JUnit 4
Inicialización y borrado de datos
Spring TestContext Framework
Configuración
Inyección de dependencias
Transacciones
Integración con JUnit 4
Spring + Junit
Inicialización y borrado de datos
Transacciones
Código de Pruebas en Maven
Con Maven el código de pruebas se ubica en el
directorio src/test (que tiene una estructura de
directorios similar a la del directorio src/main)
En el módulo pojo-minibank el código de las
clases de prueba está contenido en el paquete
es.udc.pojo.minibank.test.model (y
subpaquetes)
Las pruebas se pueden ejecutar automáticamente
desde Maven (a través del plugin surefire)
haciendo que se ejecute la fase test (e.g. mvn
test)
Maven genera un informe detallado en
target/surefire-reports
Por defecto, se consideran clases de pruebas aquellas clases
de src/test/java cuyo nombre empieza o termina por
“Test”
JUnit
JUnit es un framework para escribir pruebas de unidad o de
integración automatizadas en Java
http://www.junit.org/
Open Source
Programado por Erich Gamma y Kent Beck
Utiliza aserciones para comprobar resultados esperados
Tras la ejecución de las pruebas genera un informe indicando el
número de pruebas ejecutadas y cuales no se han ejecutado
satisfactoriamente
Existen dos tipos de fallos diferentes para una prueba
Failure: Indica que ha fallado una aserción, es decir, el código que
se está probando no está devolviendo los resultados esperados,
por lo que falla la comparación de los resultados
Error: Indica que ha ocurrido una excepción no esperada y que por
tanto no se está capturando (e.g. NullPointerException o
ArrayIndexOutOfBoundsException)
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (1)
...
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
...
public class AccountServiceTest {
...
private AccountService accountService;
...
@BeforeClass
public static void populateDb() {
DbUtil.populateDb();
}
@AfterClass
public static void cleanDb() throws Exception {
DbUtil.cleanDb();
}
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (1)
@Test
public void testCreateAccount() throws InstanceNotFoundException {
Account account = accountService.createAccount(new Account(1, 10));
Account account2 = accountService.findAccount(account.getAccountId());
assertEquals(account, account2);
}
@Test
public void testFindAccount() throws InstanceNotFoundException {
Account account = accountService.findAccount(DbUtil.getTestAccountId());
Account account2 = accountDao.find(DbUtil.getTestAccountId());
assertEquals(account2, account);
}
@Test(expected = InstanceNotFoundException.class)
public void testFindNonExistentAccount() throws InstanceNotFoundException {
accountService.findAccount(NON_EXISTENT_ACCOUNT_ID);
}
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (2)
@Test
public void testWithdrawFromAccount()
throws InstanceNotFoundException, InsufficientBalanceException {
testAddWithdraw(false);
}
/* Perform operation. */
double amount = 5;
double newBalance;
Calendar startDate;
Calendar endDate;
Account testAccount = accountService.findAccount(DbUtil
.getTestAccountId());
if (add) {
newBalance = testAccount.getBalance() + amount;
} else {
newBalance = testAccount.getBalance() - amount;
}
startDate = Calendar.getInstance();
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (3)
if (add) {
accountService.addToAccount(
testAccount.getAccountId(), amount);
} else {
accountService.withdrawFromAccount(
testAccount.getAccountId(), amount);
}
endDate = Calendar.getInstance();
assertTrue(newBalance == testAccount.getBalance());
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (4)
assertTrue(accountOperations.size() == 1);
if (add) {
assertEquals(AccountOperation.Type.ADD,
accountOperation.getType());
} else {
assertEquals(AccountOperation.Type.WITHDRAW,
accountOperation.getType());
}
assertTrue(amount == accountOperation.getAmount());
}
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (5)
@Test(expected = InstanceNotFoundException.class)
public void testWithdrawFromNonExistentAccountAccount()
throws InstanceNotFoundException, InsufficientBalanceException {
accountService.withdrawFromAccount(NON_EXISTENT_ACCOUNT_ID, 10);
}
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (6)
@Test
public void testWithdrawWithInsufficientBalance()
throws InstanceNotFoundException, InsufficientBalanceException {
/* Try to withdraw. */
startDate = Calendar.getInstance();
Account testAccount = accountService.findAccount(
DbUtil.getTestAccountId());
double initialBalance = testAccount.getBalance();
try {
accountService.withdrawFromAccount(
testAccount.getAccountId(), testAccount.getBalance() + 1);
} catch (InsufficientBalanceException e) {
exceptionCatched = true;
}
endDate = Calendar.getInstance();
assertTrue(exceptionCatched);
es.udc.pojo.minibank.test.model.accountservice.AccountServiceTest (y 7)
assertTrue(testAccount.getBalance() == initialBalance);
assertTrue(accountOperations.size() == 0);
...
}
JUnit 4 (1)
Requiere Java SE 5.0 o superior
Utiliza anotaciones @Test para marcar un método
como un caso de prueba
El parámetro timeout permite especificar que el test falle si
su ejecución no ha finalizado después de cierto tiempo
El parámetro expected permite especificar el tipo de
excepción que debe lanzar el test para que sea exitoso
Comprobaciones
Pueden realizarse varias comprobaciones (aserciones) por
método
La clase Assert proporciona un conjunto de métodos
estáticos para realizar comprobaciones
Para que una prueba se considere correcta tienen que
cumplirse todas las aserciones especificadas
JUnit 4 (2)
Comprobaciones (cont):
Ej: Assert.assertTrue(boolean)
Ej: Assert.assertEquals(Object, Object)
Compara utilizando el método equals (definido en Object)
Si no se redefine, el método equals de la clase Object
realiza una comparación por referencia
Si para alguna clase se desease que la comparación fuese por
contenido sería necesario redefinir el método equals y el
método hashCode
La clase String y las correspondientes a los tipos básicos los
tienen redefinidos para comparar por contenido
Cuando se trabaja con entidades manejadas por Hibernate, lo más
sencillo es buscar una solución arquitectónica que no obligue a
redefinir estos métodos (es decir, utilizar la igualdad referencial)
En el caso de los ejemplos de la asignatura, el código garantiza
que durante la ejecución de un caso de uso nunca hay instancias
duplicadas de entidades en memoria (el código escrito nunca crea
instancias duplicadas, e Hibernate, una vez que carga una
instancia en la sesión, siempre devuelva esa misma cada vez que
se le pide)
JUnit 4 (y 3)
En general, cada método @Test se corresponde con un caso de
prueba de un caso de uso, aunque a veces puede tener sentido
implementar varios casos de prueba dentro de un mismo
método @Test (si con ello se evita repetir código)
Para el caso de uso findAccount se han implementado dos
casos de prueba
Buscar una cuenta existente
testFindAccount
Buscar una cuenta inexistente
testFindNonExistentAccount
Para el caso de uso withdrawFromAccount se han
implementado tres casos de prueba
Retirar dinero de una cuenta con balance suficiente
testWithdrawFromAccount
Retirar dinero de una cuenta inexistente
testWithdrawFromNonExistentAccountAccount
Retirar dinero de una cuenta con balance insuficiente
testWithdrawWithInsufficientBalance
JUnit: Inicialización y Borrado de Datos (1)
@BeforeClass
public static void populateDb() {
testAccount = << Create "testAccount" in DB >>
}
@Test
public void testFindAccount() throws InstanceNotFoundException {
Account account = accountService.find(testAccount.getAccountId());
assertEquals(testAccount, account);
}
...
}
JUnit: Inicialización y Borrado de Datos (4)
@BeforeClass
public static void populateDb() {
<< Create "testAccount" in DB >>
testAccountId = ...;
}
@Test
public void testFindAccount() throws InstanceNotFoundException {
Account account =
accountService.findAccount(DbUtil.getTestAccountId()); // (1)
Account account2 = accountDao.find(DbUtil.getTestAccountId()); // (2)
assertEquals(account2, account);
}
...
}
JUnit: Inicialización y Borrado de Datos (y 6)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { SPRING_CONFIG_FILE,
SPRING_CONFIG_TEST_FILE })
@Transactional
public class AccountServiceTest {
...
@Autowired
private AccountService accountService;
...
}
GlobalNames.java
package es.udc.pojo.minibank.model.util;
private GlobalNames () {}
package es.udc.pojo.minibank.test.util;
private GlobalNames () {}
}
pojo-minibank-spring-config.xml
<!-- Enable component scanning for defining beans with annotations. -->
<context:component-scan base-package="es.udc.pojo.minibank.model"/>
@BeforeClass
public static void populateDb() {
DbUtil.populateDb();
}
@AfterClass
public static void cleanDb() throws Exception {
DbUtil.cleanDb();
}
...
}
DbUtil.java (1)
public class DbUtil {
static {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {SPRING_CONFIG_FILE, SPRING_CONFIG_TEST_FILE});
try {
accountDao.save(testAccount);
testAccountId = testAccount.getAccountId();
transactionManager.commit(transactionStatus);
} catch (Throwable e) {
transactionManager.rollback(transactionStatus);
throw e;
}
}
DbUtil.java (y 3)
public static void cleanDb() throws Throwable {
/*
* For the same reason as "populateDb" (with regard to @AfterClass
* methods), this method works directly with "TransactionManager".
*/
try {
accountDao.remove(testAccountId);
testAccountId = null;
transactionManager.commit(transactionStatus);
} catch (Throwable e) {
transactionManager.rollback(transactionStatus);
throw e;
}
}
Spring + JUnit: Inicialización y Borrado de Datos