5

私はフラスコで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 つ目は、使用しているすべてのサービス メソッドに個別にパッチを適用する必要がないことです (モジュール全体にパッチを適用する方法はないと私が知る限り)。

私はこれを回避するいくつかの方法を考えました。

  1. routes.services を再割り当てして、実際のサービス モジュールではなくモックを参照するようにします。次のようになります。routes.services = mymock
  2. サービスを各ルートにキーワード引数として渡されるクラスのメソッドにし、テストでモックを渡すだけです。
  3. (2) と同じですが、シングルトン オブジェクトを使用します。

これらのオプションを評価したり、他のオプションを考えたりするのに苦労しています。Python Web 開発を行っている人は、通常、サービスを利用するルートをテストするときに、サービスをどのようにモックしますか?

4

4 に答える 4

7

依存性注入または制御の反転を使用して、テストがはるかに簡単なコードを実現できます。

これを置き換えます:

def addtrans():
    ...
    # check that both users exist
    if not services.user_exists(to_id) or not services.user_exists(from_id):
        return "no such users"
    ...

と:

def addtrans(services=services):
    ...
    # check that both users exist
    if not services.user_exists(to_id) or not services.user_exists(from_id):
        return "no such users"
    ...

何が起こっていますか:

  • グローバルをローカルとしてエイリアスしています(それは重要なポイントではありません)
  • services同じインターフェイスを期待しながら、コードを切り離しています。
  • 必要なものをモックする方がはるかに簡単です

例えば:

class MockServices:
    def user_exists(id):
        return True

いくつかのリソース:

于 2013-07-16T22:43:32.423 に答える
0
    @patch("dao.qualcomm_transaction_service.QualcommTransactionService.get_max_qualcomm_id",20) 
def test_lambda_handler(): 
lambda_handler(event, None)

私はあなたの例を見てモックを使用しましたが、ローカルで get_max_qualcomm_id をテストしたラムダ関数でメソッドが 20 を返すことを期待しています。ここで何が問題なのか教えてください。

これは、私がモックしようとしている呼び出し中の実際のメソッドです:

 last_max_id = QualcommTransactionService().get_max_qualcomm_id(self.subscriber_id)
于 2019-01-24T08:49:54.667 に答える