集成測試框架集成sqlalchemy操作已存在表實戰

sqlalchemy是很強大的orm框架,一般的教程都是從新建一個表開始教,這種情況下我們能直接拿到表對象然后方便的操作,但是對于已經存在的表的操作,網上教程并不多,而實際上,對于測試同學來說,這種情況才是最普遍的。我在集成測試框架中數據庫操作便使用了sqlalchemy,這里分享一下,順便記錄踩過的一些不那么明顯卻很致命的坑。


首先,我們可以創建一個自定義對象,它可以去組合sqlalchemy提供的種種功能。

from sqlalchemy.orm.exc import UnmappedClassError
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import sessionmaker, class_mapper, Query

class DbRoot(object):

    def __init__(self, **kwargs):
        """
        orm基礎db對象,通過實例化該對象得到db實例,然后創建類對象繼承自db.Model,便可以對相應表進行操作
        :param kwargs: dialect 數據庫類型
                        driver 數據庫驅動
                        user 用戶名
                        password 用戶密碼
                        host 數據庫地址
                        port 端口
                        database 數據庫名
        """
        url = '{dialect}+{driver}://{user}:{password}@{host}:{port}/{database}?charset=utf8'.format(**kwargs)
        engine = create_engine(url, echo=False)

        class Base(object):

            @declared_attr
            def __table__(cls):
                return Table(cls.__tablename__, MetaData(), autoload=True, autoload_with=engine)

        self._base = Base
        self.Model = self.make_declarative_base()
        self.session = sessionmaker(bind=engine)

    def make_declarative_base(self):
        """

        :return:
        """
        base = declarative_base(cls=self._base)
        base.query = _QueryProperty(self)
        base.query_class = Query
        return base

如代碼所示,DbRoot對象接收數據庫url的各項參數,然后創建引擎,組合declarative_base方法生成的Model對象(Model對象是Base對象的子類),以及通過sessionmaker方法生成的session數據庫會話對象(實例化它便可以產生一個數據庫會話,相當于操作句柄)。
然后在make_declarative_base方法中,Model對象還組合了_QueryProperty,也就是query屬性,用于查詢操作,_QueryProperty是一個描述符對象,見下面代碼。這樣,我們在做比如query.filter_by等操作時,都是轉給了get方法去執行,該方法最終調用了t也就是Model對象query_class方法(其實就是sqlalchemy.orm提供的Query方法)做查詢,這里尤其注意,session參數傳的是DbRoot對象的session屬性,該屬性一定要在這里實例化,這樣每次操作才能重新生成一個session會話。

class _QueryProperty(object):

    def __init__(self, sa):
        self.sa = sa

    def __get__(self, obj, t):
        """
        這里一定要注意,session要每次重新生成,不然session會話會自動關閉,導致下一次操作句柄為空
        :param obj:
        :param t:
        :return:
        """
        try:
            mapper = class_mapper(t)
            if mapper:
                return t.query_class(mapper, session=self.sa.session())
        except UnmappedClassError:
            return None

現在,我們等于有了db.Model對象,然后我們要去映射表生成表對象。其實就是創建一個類繼承自DbRoot.Model,然后將__tablename__屬性設置為想要生成的表對象表名,這樣便大功告成了。我這里采用了動態生成類的方式。

def gen_orm_class( db=None, table_name=None):
    """
    動態生成數據庫表映射Model類
    :param db: db對象
    :param table_name: 表名稱
    :return:
    """
    return type(
        table_name.title(),
        (db.Model,),
        {
            '__tablename__': table_name
        }
    )

有了這個基礎,如果結合pytest使用的話會更方便,pytest有個很牛的功能叫fixture,我們可以把初始化數據庫的操作做出一個scope為module的fixture

@pytest.fixture(scope='module')
def mysql(request, config_init):
    """
    mysql數據庫操作實例
    :param request:
    :param config_init:
    :return:
    """
    db_roots = {}
    mysql_conf = config_init.get('mysql')
    databases = mysql_conf.pop('databases')
    dbs = request.module.config.get('mysql_dbs', {})

    for db, table in dbs.items():
        db_conf = databases.get(db)
        mysql_conf.update(db_conf)
        db_roots.update({table: gen_orm_class(db_name=db, db=DbRoot(**mysql_conf), table_name=table)})

    return db_roots

然后在測試用例中配置數據庫連接信息。

# test_case.py
config = {
    'mysql_dbs': {
        'dev': 'user'
    }
}

這樣在進入該模塊(test_case.py)開始測試之前,pytest會根據配置自動初始化數據庫連接生成{‘表名’: ‘DbRoot表對象’}結構的字典,之后在測試代碼中,就可以通過mysql.get('user')拿到User表對象,從而對dev.User表進行查詢操作。

    def test_user(self, mysql, user_id):
        user_tbl = mysql.get('user')
        # id是主鍵,所以可以通過get方法直接查詢
        assert user_tbl.query.get(user_id),'該用戶{}不存在'.format(user_id)

有緣瀏覽到的人,希望可以幫助到你

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容