Google CloudEndpointsの単体テストを設定するためのサポートが必要です。 WebTestを使用すると、すべてのリクエストがAppErrorで応答します:不正な応答:404が見つかりません。エンドポイントがWebTestと互換性があるかどうかはよくわかりません。
これは、アプリケーションが生成される方法です。
application = endpoints.api_server([TestEndpoint], restricted=False)
次に、WebTestを次のように使用します。
client = webtest.TestApp(application)
client.post('/_ah/api/test/v1/test', params)
Curlを使用したテストは正常に機能します。
エンドポイントのテストを別のものに書く必要がありますか? GAEエンドポイントチームからの提案は何ですか?
多くの実験とSDKコードを調べた後、Python内でエンドポイントをテストする2つの方法を思いつきました。
あなたはwebtestで正しい方向に進んでいますが、SPIエンドポイントに対するリクエストを正しく変換することを確認する必要があります。
Cloud EndpointsAPIフロントエンドと_dev_appserver
_のEndpointsDispatcher
は、_/_ah/api/*
_への呼び出しを_/_ah/spi/*
_への対応する「バックエンド」呼び出しに変換します。変換は次のように思われます:
application/json
_ HTTP POSTです(RESTエンドポイントが別のものであっても)。POST /_ah/spi/TestEndpoint.insert_message
_はコードでTestEndpoint.insert_message()
を呼び出します。これは、次の設定でエンドポイントをテストできることを意味します。
_from google.appengine.ext import testbed
import webtest
# ...
def setUp(self):
tb = testbed.Testbed()
tb.setup_env(current_version_id='testbed.version') #needed because endpoints expects a . in this value
tb.activate()
tb.init_all_stubs()
self.testbed = tb
def tearDown(self):
self.testbed.deactivate()
def test_endpoint_insert(self):
app = endpoints.api_server([TestEndpoint], restricted=False)
testapp = webtest.TestApp(app)
msg = {...} # a dict representing the message object expected by insert
# To be serialised to JSON by webtest
resp = testapp.post_json('/_ah/spi/TestEndpoint.insert', msg)
self.assertEqual(resp.json, {'expected': 'json response msg as dict'})
_
ここで重要なのは、エンドポイントを呼び出す前に、データストアまたは他のGAEサービスで適切なフィクスチャを簡単にセットアップできるため、呼び出しの予想される副作用をより完全に主張できることです。
次のようなものを使用して、同じpython環境内で開発サーバーを起動できます。
_import sys
import os
import dev_appserver
sys.path[1:1] = dev_appserver._DEVAPPSERVER2_PATHS
from google.appengine.tools.devappserver2 import devappserver2
from google.appengine.tools.devappserver2 import python_runtime
# ...
def setUp(self):
APP_CONFIGS = ['/path/to/app.yaml']
python_runtime._RUNTIME_ARGS = [
sys.executable,
os.path.join(os.path.dirname(dev_appserver.__file__),
'_python_runtime.py')
]
options = devappserver2.PARSER.parse_args([
'--admin_port', '0',
'--port', '8123',
'--datastore_path', ':memory:',
'--logs_path', ':memory:',
'--skip_sdk_update_check',
'--',
] + APP_CONFIGS)
server = devappserver2.DevelopmentServer()
server.start(options)
self.server = server
def tearDown(self):
self.server.stop()
_
ここで、APIに対してテストを実行するためにlocalhost:8123にactualHTTPリクエストを発行する必要がありますが、GAEAPIと対話してフィクスチャなどを設定することもできます。 。テストを実行するたびに新しい開発サーバーを作成および破棄するため、これは明らかに低速です。
この時点で、HTTPリクエストを自分で作成する代わりに、 Google API Python client を使用してAPIを使用します。
_import apiclient.discovery
# ...
def test_something(self):
apiurl = 'http://%s/_ah/api/discovery/v1/apis/{api}/{apiVersion}/rest' \
% self.server.module_to_address('default')
service = apiclient.discovery.build('testendpoint', 'v1', apiurl)
res = service.testresource().insert({... message ... }).execute()
self.assertEquals(res, { ... expected reponse as dict ... })
_
これは、GAE APIに直接アクセスしてフィクスチャを簡単にセットアップし、内部状態を検査できるため、CURLを使用したテストよりも改善されています。エンドポイントディスパッチメカニズムを実装する開発サーバーの最小限のコンポーネントをつなぎ合わせることでHTTPをバイパスする統合テストを行うさらに良い方法があると思いますが、これには現在よりも多くの調査時間が必要です。
webtest名前のバグを減らすために簡略化できます
次の場合TestApi
import endpoints
import protorpc
import logging
class ResponseMessageClass(protorpc.messages.Message):
message = protorpc.messages.StringField(1)
class RequestMessageClass(protorpc.messages.Message):
message = protorpc.messages.StringField(1)
@endpoints.api(name='testApi',version='v1',
description='Test API',
allowed_client_ids=[endpoints.API_Explorer_CLIENT_ID])
class TestApi(protorpc.remote.Service):
@endpoints.method(RequestMessageClass,
ResponseMessageClass,
name='test',
path='test',
http_method='POST')
def test(self, request):
logging.info(request.message)
return ResponseMessageClass(message="response message")
tests.pyは次のようになります
import webtest
import logging
import unittest
from google.appengine.ext import testbed
from protorpc.remote import protojson
import endpoints
from api.test_api import TestApi, RequestMessageClass, ResponseMessageClass
class AppTest(unittest.TestCase):
def setUp(self):
logging.getLogger().setLevel(logging.DEBUG)
tb = testbed.Testbed()
tb.setup_env(current_version_id='testbed.version')
tb.activate()
tb.init_all_stubs()
self.testbed = tb
def tearDown(self):
self.testbed.deactivate()
def test_endpoint_testApi(self):
application = endpoints.api_server([TestApi], restricted=False)
testapp = webtest.TestApp(application)
req = RequestMessageClass(message="request message")
response = testapp.post('/_ah/spi/' + TestApi.__name__ + '.' + TestApi.test.__name__, protojson.encode_message(req),content_type='application/json')
res = protojson.decode_message(ResponseMessageClass,response.body)
self.assertEqual(res.message, 'response message')
if __name__ == '__main__':
unittest.main()
これらを通常の方法でテストできるように、考えられるすべてのことを試しました。/_ah/spiメソッドを直接ヒットするだけでなく、service_mappingsを使用して新しいprotorpcアプリを作成しようとしても役に立ちませんでした。私はエンドポイントチームのGoogle社員ではないので、これを機能させるための賢い方法があるかもしれませんが、Webtestを使用するだけでは機能しないようです(明らかな何かを見逃していない限り)。
それまでの間、分離された環境でApp Engineテストサーバーを起動し、httpリクエストを発行するテストスクリプトを作成できます。
分離された環境でサーバーを実行する例(bashですが、Pythonから簡単に実行できます):
DATA_PATH=/tmp/appengine_data
if [ ! -d "$DATA_PATH" ]; then
mkdir -p $DATA_PATH
fi
dev_appserver.py --storage_path=$DATA_PATH/storage --blobstore_path=$DATA_PATH/blobstore --datastore_path=$DATA_PATH/datastore --search_indexes_path=$DATA_PATH/searchindexes --show_mail_body=yes --clear_search_indexes --clear_datastore .
次に、リクエストを使用してalacurlをテストできます。
requests.get('http://localhost:8080/_ah/...')
私のソリューションでは、テストモジュール全体に1つのdev_appserverインスタンスを使用します。これは、テストメソッドごとにdev_appserverを再起動するよりも高速です。
GoogleのPython APIクライアントライブラリを使用することで、APIと対話するための最も簡単で同時に最も強力な方法も得られます。
import unittest
import sys
import os
from apiclient.discovery import build
import dev_appserver
sys.path[1:1] = dev_appserver.EXTRA_PATHS
from google.appengine.tools.devappserver2 import devappserver2
from google.appengine.tools.devappserver2 import python_runtime
server = None
def setUpModule():
# starting a dev_appserver instance for testing
path_to_app_yaml = os.path.normpath('path_to_app_yaml')
app_configs = [path_to_app_yaml]
python_runtime._RUNTIME_ARGS = [
sys.executable,
os.path.join(os.path.dirname(dev_appserver.__file__),
'_python_runtime.py')
]
options = devappserver2.PARSER.parse_args(['--port', '8080',
'--datastore_path', ':memory:',
'--logs_path', ':memory:',
'--skip_sdk_update_check',
'--',
] + app_configs)
global server
server = devappserver2.DevelopmentServer()
server.start(options)
def tearDownModule():
# shutting down dev_appserver instance after testing
server.stop()
class MyTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
# build a service object for interacting with the api
# dev_appserver must be running and listening on port 8080
api_root = 'http://127.0.0.1:8080/_ah/api'
api = 'my_api'
version = 'v0.1'
discovery_url = '%s/discovery/v1/apis/%s/%s/rest' % (api_root, api,
version)
cls.service = build(api, version, discoveryServiceUrl=discovery_url)
def setUp(self):
# create a parent entity and store its key for each test run
body = {'name': 'test parent'}
response = self.service.parent().post(body=body).execute()
self.parent_key = response['parent_key']
def test_post(self):
# test my post method
# the tested method also requires a path argument "parent_key"
# .../_ah/api/my_api/sub_api/post/{parent_key}
body = {'SomeProjectEntity': {'SomeId': 'abcdefgh'}}
parent_key = self.parent_key
req = self.service.sub_api().post(body=body,parent_key=parent_key)
response = req.execute()
etc..
Ezequiel Munsによって説明されているように完全なHTTPスタックをテストしたくない場合は、endpoints.methodをモックアウトして、API定義を直接テストすることもできます。
def null_decorator(*args, **kwargs):
def decorator(method):
def wrapper(*args, **kwargs):
return method(*args, **kwargs)
return wrapper
return decorator
from google.appengine.api.users import User
import endpoints
endpoints.method = null_decorator
# decorator needs to be mocked out before you load you endpoint api definitions
from mymodule import api
class FooTest(unittest.TestCase):
def setUp(self):
self.api = api.FooService()
def test_bar(self):
# pass protorpc messages directly
self.api.foo_bar(api.MyRequestMessage(some='field'))
ソースを掘り下げた後、2014年のEzequiel Munsの(優れた)回答以降、エンドポイントの状況が変わったと思います。方法1の場合、/ _ ah/api/*から直接リクエストし、/を使用する代わりに正しいHTTPメソッドを使用する必要があります。 _ah/spi/*変換。これにより、テストファイルは次のようになります。
from google.appengine.ext import testbed
import webtest
# ...
def setUp(self):
tb = testbed.Testbed()
# Setting current_version_id doesn't seem necessary anymore
tb.activate()
tb.init_all_stubs()
self.testbed = tb
def tearDown(self):
self.testbed.deactivate()
def test_endpoint_insert(self):
app = endpoints.api_server([TestEndpoint]) # restricted is no longer required
testapp = webtest.TestApp(app)
msg = {...} # a dict representing the message object expected by insert
# To be serialised to JSON by webtest
resp = testapp.post_json('/_ah/api/test/v1/insert', msg)
self.assertEqual(resp.json, {'expected': 'json response msg as dict'})
検索のために、古い方法を使用した場合の症状は、エンドポイントがValueError
をInvalid request path: /_ah/spi/whatever
で発生させることです。それが誰かの時間を節約することを願っています!