私はフラスコでwebappに取り組んでおり、サービスレイヤーを使用してデータベースのクエリと操作をビューとAPIルートから抽象化しています。これにより、サービス層をモック化できるため、テストが容易になることが示唆されていますが、これを行うための良い方法を見つけるのに苦労しています. 簡単な例として、3 つの SQLAlchemy モデルがあるとします。
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String)
class Group(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column
class Transaction(db.Model):
id = db.Column(db.Integer, primary_key = True)
from_id = db.Column(db.Integer, db.ForeignKey('user.id'))
to_id = db.Column(db.Integer, db.ForeignKey('user.id'))
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
amount = db.Column(db.Numeric(precision = 2))
ユーザーとグループ、およびユーザー間のトランザクション (お金のやり取りを表す) があります。これで、特定のユーザーまたはグループが存在するかどうかを確認したり、ユーザーが特定のグループのメンバーであるかどうかを確認したりするなど、一連の機能を備えたservices.pyができました。JSON で送信される API ルートでこれらのサービスを使用します。リクエストで、それを使用してデータベースにトランザクションを追加します。これは次のようになります。
ルート.py
import services
@app.route("/addtrans")
def addtrans():
# get the values out of the json in the request
args = request.get_json()
group_id = args['group_id']
from_id = args['from']
to_id = args['to']
amount = args['amount']
# check that both users exist
if not services.user_exists(to_id) or not services.user_exists(from_id):
return "no such users"
# check that the group exists
if not services.group_exists(to_id):
return "no such group"
# add the transaction to the db
services.add_transaction(from_id,to_id,group_id,amount)
return "success"
テストのためにこれらのサービスをモックアウトしようとすると、問題が発生します。私はモック ライブラリを使用してきましたが、サービス モジュールから関数にパッチを適用して、関数をモックにリダイレクトする必要があります。たとえば、次のようになります。
mock = Mock()
mock.user_exists.return_value = True
mock.group_exists.return_value = True
@patch("services.user_exists",mock.user_exists)
@patch("services.group_exists",mock.group_exists)
def test_addtrans_route(self):
assert "success" in routes.addtrans()
これは、さまざまな理由で気分が悪くなります。1 つ目は、パッチが汚いと感じることです。2 つ目は、使用しているすべてのサービス メソッドに個別にパッチを適用する必要がないことです (モジュール全体にパッチを適用する方法はないと私が知る限り)。
私はこれを回避するいくつかの方法を考えました。
- routes.services を再割り当てして、実際のサービス モジュールではなくモックを参照するようにします。次のようになります。
routes.services = mymock
- サービスを各ルートにキーワード引数として渡されるクラスのメソッドにし、テストでモックを渡すだけです。
- (2) と同じですが、シングルトン オブジェクトを使用します。
これらのオプションを評価したり、他のオプションを考えたりするのに苦労しています。Python Web 開発を行っている人は、通常、サービスを利用するルートをテストするときに、サービスをどのようにモックしますか?