My favorites | Português | Sign in

Teste de unidade com implementações de serviços locais

É natural e saudável criar testes de unidade que usam as implementações de serviços locais presentes no SDK. Este capítulo descreve como cumprir essa tarefa com o JUnit 3. Vamos seguir cada etapa individualmente e, em seguida, juntá-las em uma classe base para possibilitar a expansão dos seus testes.

Como estabelecer o ambiente de execução

A primeira coisa a fazer é se certificar de que você possui appengine-api-stubs.jar e appengine-local-runtime.jar no caminho de classe do teste da sua unidade (esses jars são enviados como parte do SDK). Em seguida, precisamos criar uma instância de ApiProxy.Environment e registrá-la com ApiProxy. Quando o aplicativo está sendo executado localmente, o recipiente cria isso em seu nome usando o conteúdo do arquivo de configuração appengine-web.xml. Nesse caso, porém, o JUnit é o nosso recipiente, e ele não sabe nada sobre o Google App Engine ou o appengine-web.xml. Portanto, fica por conta do teste garantir que um ApiProxy.Environment seja criado e registrado adequadamente:

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());

Depois de estabelecermos um ambiente para executar as implementações de serviços locais, precisamos configurar as próprias implementações de serviços locais. Os serviços do Google App Engine utilizados pelo seu aplicativo invocarão ApiProxy.makeSyncCall(), que por sua vez delegará a uma instância ApiProxy que, dependendo de como estiver sendo executada - localmente ou durante a produção - irá se comunicar com uma implementação local ou remota do servidor do serviço. Ao executar o aplicativo localmente, o recipiente instala uma implementação de ApiProxy chamada ApiProxyLocalImpl. Nesse caso, no entanto, o nosso recipiente é o JUnit, que não sabe que isso precisa ser configurado. Por isso, fica por sua conta fazer essa configuração:

import java.io.File;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;

ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});

E só!

Como criar um caso de teste básico

Como queremos facilitar a criação de testes que utilizam as implementações de serviços locais, vamos concluir isso em um caso de teste básico que pode ser estendido facilmente pelos testes:

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();
    }
}

Agora que temos um caso de teste básico, fica simples criar testes que dependam das implementações de serviços locais. Veja abaixo um exemplo que testa se os e-mails são enviados corretamente após a criação de um bug:

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
    }
}

Como criar testes para o armazenamento de dados

Por padrão, a implementação local do serviço de armazenamento de dados esvazia os conteúdos do disco em intervalos regulares. Esse é um recurso vantajoso quando o aplicativo está sendo executado localmente, pois ele mantém o seu estado depois que você desliga o servidor. Durante a execução dos testes, no entanto, é muito mais fácil criar testes determinísticos se todos os testes começarem com um armazenamento de dados limpo, pois você pode ter problemas se o estado for removido do disco e, em seguida, lido novamente. Vamos estender LocalServiceTestCase para demonstrar como conseguir isso:

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();
    }

}

A partir de agora, qualquer teste pertencente a um caso de teste que estende essa classe com certeza estará começando com um armazenamento de dados limpo:

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());
    }
}