代码之家  ›  专栏  ›  技术社区  ›  bstpierre Edgar Aviles

为什么我的烧瓶测试客户端约60%的时间失败?

  •  0
  • bstpierre Edgar Aviles  · 技术社区  · 6 年前

    我已经将我的问题最小化到一个独立的flask应用程序+单元测试。当它和 pytest app.py 它大约有一半的时间(50次运行中的29次)有这个错误:

    E           werkzeug.routing.BuildError: Could not build url for endpoint 'thing' with values ['_sa_instance_state']. Did you forget to specify values ['id']?
    

    令人沮丧的是,在 post() 方法使其始终通过(请参阅下面的注释)。

    这感觉像是框架中某个地方的种族状况。sqlalchemy是否正在生成一个线程来执行提交和更新 t.id 是吗?

    我可以通过做一个 del t.id 在注释的位置(确认错误来自丢失的t.id)。我可以强迫它通过做一个 t.id = 999 在同一地点。

    我在这里做了什么明显的错误吗?或者这是其中一个包中的一个bug?

    我运行的是Python3.5.2,我的requirements.txt是:

    Flask==1.0.2
    Flask-RESTful==0.3.6
    Flask-SQLAlchemy==2.3.2
    Jinja2==2.10
    pytest==3.2.2
    pytest-repeat==0.4.1
    SQLAlchemy==1.2.8
    Werkzeug==0.14.1
    

    值得注意的是,这在大多数软件包的早期版本(flask 0.12、sqlalchemy 1.1.14等)中也失败了。

    值得注意的是,当与 pytest --count=20 app.py 它总是通过或不通过整个计数,即20次通过或20次失败。但大约有一半的整体运行仍会失败。

    应用程序如下:

    #!/usr/bin/env python3
    import json
    from flask import Flask
    from flask_restful import Api, Resource, fields, marshal, reqparse
    from flask_sqlalchemy import SQLAlchemy
    import pytest
    
    app = Flask(__name__)
    api = Api(app)
    db = SQLAlchemy(app)
    
    class Thing(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
    
    thing_fields = {
        'name': fields.String,
        'uri': fields.Url('thing'),
    }
    
    class ThingListAPI(Resource):
        def __init__(self):
            self.reqparse = reqparse.RequestParser()
            self.reqparse.add_argument('name', type=str, location='json')
            super().__init__()
    
        def post(self):
            args = self.reqparse.parse_args()
            t = Thing(name=args['name'])
            db.session.add(t)
            db.session.commit()
            ### <<< at this point inserting pretty much any statement
            ###     will make the test pass >>>
            return {'thing': marshal(t, thing_fields)}, 201
    
    class ThingAPI(Resource):
        def get(self, id):
            pass
    
    api.add_resource(ThingListAPI, '/things', endpoint='things')
    api.add_resource(ThingAPI, '/things/<int:id>', endpoint='thing')
    
    @pytest.fixture
    def stub_app():
        app.config['TESTING'] = True
        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
        client = app.test_client()
        db.create_all()
        yield client
        db.drop_all()
    
    def test_thing_post(stub_app):
        resp = stub_app.post('/things', data=json.dumps({'name': 'stuff'}),
                             content_type='application/json')
        assert(resp.status_code == 201)
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Fine    6 年前

    如果你加上 db.session.refresh(t) 在里面 post() 提交后,它将解决问题。我不知道这样做是否正确(sqlalchemy相当复杂,我有一点经验),但它表明 t -对象状态有时不刷新( 有时 因为可能有一个种族条件,有时还有sqlalchemy 也许吧 获取更多的机器时间并及时提取id,但有时不是 id -属性仍然不知何故(我的意思是说) ,因为SQLite确实存在,但是一个新的状态不是从DB中拔出的。