Google Code 提供下列語言介面: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
撰寫單元測試時,使其採用隨附於 SDK 的本機服務實作,這是既正常又良好的操作。本章將說明如何使用 JUnit 3 來完成這項工作。我們將逐一介紹每個步驟,然後在一個基礎類別中組合所有步驟,而您的測試可以擴充此基礎類別。
首先,您必須確認單元測試的類別路徑上有 appengine-api-stubs.jar 和 appengine-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());
}
}