My favorites | 中文(繁體) | Sign in
英文版或許有比此中譯版新的內容

使用本機服務實作執行單元測試

撰寫單元測試時,使其採用隨附於 SDK 的本機服務實作,這是既正常又良好的操作。本章將說明如何使用 JUnit 3 來完成這項工作。我們將逐一介紹每個步驟,然後在一個基礎類別中組合所有步驟,而您的測試可以擴充此基礎類別。

建立執行環境

首先,您必須確認單元測試的類別路徑上有 appengine-api-stubs.jarappengine-local-runtime.jar (這些 jar 會隨附於 SDK)。接著,我們需要建立 ApiProxy.Environment 的實例,並向 ApiProxy 註冊。當您在本機執行應用程式時,容器會使用您的 appengine-web.xml 設定檔內容代表您建立這個實例,但在這個案例中,JUnit 是我們的容器,而 JUnit 完全沒有「應用服務引擎」或 appengine-web.xml 的相關資訊。因此,必須經由測試來確定 ApiProxy.Environment 已正確建構與註冊:

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

在建立可供本機服務實作執行的環境之後,我們需要設定本機服務實作本身。應用程式最終要使用的「應用服務引擎」服務會叫用 ApiProxy.makeSyncCall(),而這個方法會委派給 ApiProxy 實例,該實例將根據您的執行方式 (本機執行或實際執行),與本機或遠端伺服器的服務實作通訊。當您在本機執行應用程式時,容器會安裝 ApiProxy 的實作,實作名稱為 ApiProxyLocalImpl。不過,在這個案例中,我們的容器是 JUnit,而 JUnit 不知道需要安裝這個實作。因此,您必須自己決定是否安裝:

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

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

大功告成!

建立基礎測試案例

對於採用本機服務實作的測試,我們希望簡化測試的撰寫程序,因此我們使用基礎測試案例將它包裝,以便您的測試能夠輕鬆擴充:

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

現在我們有了基礎測試案例,要撰寫依賴本機服務實作的測試就容易許多。下列範例會測試當發生錯誤時能否正確傳送電子郵件:

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

撰寫資料存放區測試

按照預設,資料存放區服務的本機實作會定期將內容排清至磁碟。當您在本機執行應用程式時,這是很有用的功能,因為在您關閉伺服器之後,該功能會維護您的狀態。不過,當您執行測試時,如果要使定型測試的撰寫更加容易,每個測試一開始的資料存放區必須是乾淨的,而排清狀態至磁碟再讀回的功能可能會造成干擾。 讓我們擴充 LocalServiceTestCase 來示範如何完成這個步驟:

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

}

現在我們可以確保,在擴充這個類別的測試案例中,所有測試一開始的資料存放區都是乾淨的:

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