Google Code disponible en: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
La escritura de pruebas de la unidad que utiliza las implementaciones del servicio local, agrupadas en el SDK, es algo natural y recomendado. Este capítulo describe cómo realizar esta tarea con JUnit 3. Analizaremos cada paso y, a continuación, los uniremos en una clase base que pueden ampliar las pruebas que realices.
Lo primero que debes hacer es asegurarte de que tienes appengine-api-stubs.jar y appengine-local-runtime.jar en la ruta de clase de la prueba de la unidad (estos JAR se envían como parte del SDK). A continuación necesitas crear una instancia de ApiProxy.Environment y registrarla con ApiProxy. Si la aplicación se ejecuta de forma local, el contenedor la creará en tu nombre mediante el contenido del archivo de configuración appengine-web.xml, aunque en este caso JUnit es el contenedor y no sabe nada en absoluto de App Engine o appengine-web.xml. Así pues, depende de la prueba asegurarse de que ApiProxy.Environment se ha creado y registrado correctamente:
import com.google.apphosting.api.ApiProxy;
class TestEnvironment implements ApiProxy.Environment {
public String getAppId() {
return "Unit Tests";
}
public String getVersionId() {
return "1.0";
}
public void setDefaultNamespace(String s) { }
public String getRequestNamespace() {
return null;
}
public String getDefaultNamespace() {
return null;
}
public String getAuthDomain() {
return null;
}
public boolean isLoggedIn() {
return false;
}
public String getEmail() {
return null;
}
public boolean isAdmin() {
return false;
}
}
// ...
ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());
Una vez que se haya establecido un entorno para que las implementaciones de servicio local se ejecuten en él, se deberán configurar las propias implementaciones del servicio local. Los servicios de App Engine que utiliza la aplicación ejecutan ApiProxy.makeSyncCall() en última instancia que, a su vez delega en una instancia ApiProxy que, en función de si se ejecuta de forma local o durante la producción, se comunica con una implementación del servicio local o remota de servidor. Si ejecutas la aplicación de forma local, el contenedor instalará una implementación de ApiProxy llamada ApiProxyLocalImpl. Sin embargo, en este caso el contenedor es JUnit, que no sabe que esto debe configurarse. Así pues, depende de ti hacerlo:
import java.io.File;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;
ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});
Y eso es todo.
Nuestra intención es facilitar la escritura de pruebas que utilizan implementaciones del servicio local, así que hagamos esto en un caso de prueba base que las pruebas pueden ampliar fácilmente:
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;
public class LocalServiceTestCase extends TestCase {
@Override
public void setUp() throws Exception {
super.setUp();
ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());
ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});
}
@Override
public void tearDown() throws Exception {
// not strictly necessary to null these out but there's no harm either
ApiProxy.setDelegate(null);
ApiProxy.setEnvironmentForCurrentThread(null);
super.tearDown();
}
}
Ahora que disponemos de un caso de prueba base, es muy sencillo escribir pruebas en implementaciones del servicio local. A continuación se muestra un ejemplo que comprueba que los mensajes de correo electrónico se envían correctamente si se produce un error:
import com.google.appengine.api.mail.dev.LocalMailService;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
public class BugNotificationTest extends TestCase {
public void testEmailGetsSent() {
ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
LocalMailService mailService = (LocalMailService) proxy.getService("mail");
mailService.clearSentMessages();
Bug b = new Bug();
b.setSeverity(Severity.LOW);
b.setText("NullPointerException when trying to update phone number.");
b.setOwner("max");
new BugDAO().createBug(b);
assertEquals(1, mailService.getSentMessages().size());
// ... tests the content and recipient of the email
}
}
De forma predeterminada, la implementación local del servicio de almacén de datos borra el contenido en disco a intervalos regulares. Si ejecutas la aplicación de forma local, se trata de una función práctica, porque mantiene el estado tras cerrar el servidor. Sin embargo, si ejecutas pruebas, es mucho más fácil escribir pruebas deterministas si cada prueba se inicia con un almacén de datos limpio, aunque el hecho de haber borrado el estado en disco y haberlo vuelto a leer puede resultar un obstáculo. Ampliemos LocalServiceTestCase para demostrar cómo se puede conseguir esto:
import com.google.appengine.api.datastore.dev.LocalDatastoreService;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
public class LocalDatastoreTestCase extends LocalServiceTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY, Boolean.TRUE.toString());
}
@Override
public void tearDown() throws Exception {
ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
LocalDatastoreService datastoreService = (LocalDatastoreService) proxy.getService("datastore_v3");
datastoreService.clearProfiles();
super.tearDown();
}
}
Ahora, cualquier prueba que pertenezca a un caso de prueba que amplíe esta clase puede estar segura de que se inicia con un almacén de datos limpio:
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Query;
public class ContactInfoDAOTestCase extends LocalDatastoreTestCase {
public void testBatchInsert() {
ContactInfoDAO dao = new ContactInfoDAO();
ContactInfo info1 = new ContactInfo("John", "Doe", "650-555-5555");
ContactInfo info2 = new ContactInfo("Jane", "Doe", "415-555-5555");
dao.addContacts(info1, info2);
Query query = new Query(ContactInfo.class.getSimpleName());
assertEquals(2, DatastoreServiceFactory.getDatastoreService().prepare(query).countEntities());
}
public void testDelete() {
ContactInfoDAO dao = new ContactInfoDAO();
ContactInfo info1 = new ContactInfo("John", "Doe", "650-555-5555");
dao.addContact(info1);
dao.deleteContact(info1);
Query query = new Query(ContactInfo.class.getSimpleName());
assertEquals(0, DatastoreServiceFactory.getDatastoreService().prepare(query).countEntities());
}
}