Flask+ Dependency-injecter+pytest 写测试类

最近在使用这几个在做项目,因为第一次用这个,所以不免有些问题。总结下踩的坑

1.测试类位置

首先测试类约定会放在tests里面,不然有可能发生引入包的问题,会报错某些包找不到。

2. 测试类依赖注入

这里我就用的真实的数据库操作,但是我用了一个专门为测试写的事务管理,所有操作都不会commit而是会rollback,相当于一个内存数据库了,只会在内存里面,不会提交,你也可以直接用内存数据库或者mock,

python 复制代码
# tests/test_app.py
import pytest
from main.application import create_app
from main.config.database_config import DatabaseConfig
from main.containers import Container
from dependency_injector.wiring import inject, Provide
from main.services.common_service.db_access.domain.user import User
from main.services.common_service.db_access.service.user_service import UserService
from dependency_injector.providers import Factory
from main.services.common_service.db_access.repository.impl.user_repository_impl import UserRepositoryImpl

#获取app
@pytest.fixture
def app():
    app = create_app()
    with app.app_context():
        yield app
#获取user_service
@pytest.fixture
def user_service(app):
    with app.app_context():
        user_repo=UserRepositoryImpl()
        db=app.container.db()
        yield UserService(user_repository=user_repo,session_factory=db.force_rowback_session)
        
#测试方法,需要传入user_serivice,会从上面加了@pytest.fixture注解获取同名方法进行注入
def test_create_user(user_service):
    user=user_service.create_user()
    assert user is not None

我的userservice需要传入两个参数,一个是repo的实现类,一个是sqla的session

python 复制代码
"""Containers module."""

import os
from dependency_injector import containers, providers

from main.config.database_config import DatabaseConfig



class Container(containers.DeclarativeContainer):
  
    # wiring_config = containers.WiringConfiguration(auto_wire=True)
    wiring_config = containers.WiringConfiguration(packages=[
        "main.config", 
        "main.web.controller",
        "main"
    ])
    config_path = os.path.join(os.path.dirname(__file__), "../config.yml")
    config = providers.Configuration(yaml_files=[config_path])
    
    db=providers.Singleton(DatabaseConfig,db_url=config.db.url)

有个很重要的一点就是

这里如果写成这样过的话,启动项目是没有什么问题。但是测试类的时候就会加载不到,就会导致需要config的类加载不到你需要的配置。

python 复制代码
config = providers.Configuration(yaml_files=['config.yml'])

所以必须写成这样

python 复制代码
config_path = os.path.join(os.path.dirname(__file__), "../config.yml")
config = providers.Configuration(yaml_files=[config_path])

事务控制

python 复制代码
"""Database module."""

from contextlib import contextmanager, AbstractContextManager
from typing import Callable

from sqlalchemy import create_engine, orm,event
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from datetime import datetime

from main.services.common_service.db_access.domain.common_field_entity import CommonEntity
Base = declarative_base()

class DatabaseConfig:

    def __init__(self, db_url: str) -> None:
        self._engine = create_engine(db_url, echo=True)
        self._session_factory = orm.scoped_session(
            orm.sessionmaker(
                autocommit=False,
                autoflush=False,
                expire_on_commit=False,
                bind=self._engine,
            ),
        )

    def create_database(self) -> None:
        Base.metadata.create_all(self._engine)


    @contextmanager
    def session(self) -> Callable[..., AbstractContextManager[Session]]:
        session: Session = self._session_factory()
        try:
            yield session
        except Exception:
            session.rollback()
            raise
        else:
            if session._transaction.is_active:
                session.commit()
            session.close()
            
    @contextmanager
    #专门负责测试类的回滚操作,不论任何情况,都进行回滚操作
    def force_rowback_session(self) -> Callable[..., AbstractContextManager[Session]]:
        session: Session = self._session_factory()
        try:
            yield session
        except Exception:
            session.rollback()
            raise
        else:
            session.rollback()
            session.close()

    @event.listens_for(CommonEntity, 'before_insert', propagate=True)
    def before_insert_listener(self, mapper, target):
        # 在创建时自动更新 created_dt,version
        target.created_dt = datetime.now()
        target.created_by = 'MAAS'
        target.version = 1
        
    @event.listens_for(CommonEntity, 'before_update', propagate=True)
    def before_update_listener(self, mapper, target):
        # 在更新时自动更新 updated_dt,version
        target.updated_dt = datetime.now()
        target.updated_by = 'MAAS'
        target.version += 1

运行测试

就直接到文件目录,执行pytest命令就可以了,没有pytest就pip install 一下就行了

python 复制代码
pytest xxx.py
相关推荐
qq_433716951 天前
Selenium+Pytest自动化测试框架 ------ 禅道实战
自动化测试·软件测试·selenium·单元测试·pytest·接口测试·压力测试
blues_C2 天前
Pytest-Bdd-Playwright 系列教程(11):场景快捷方式
自动化测试·pytest·bdd·playwright
幸运的星竹2 天前
pytest结合allure做接口自动化
自动化·pytest
blues_C2 天前
Pytest-Bdd-Playwright 系列教程(12):步骤参数 & parsers参数解析
自动化测试·pytest·bdd·playwright
胜天半月子4 天前
pytest | 框架的简单使用
运维·pytest
blues_C4 天前
Pytest-Bdd-Playwright 系列教程(10):配置功能文件路径 & 优化场景定义
自动化测试·软件测试·pytest·bdd·playwright
bigbig猩猩5 天前
pytest中的断言:深入解析与实践
pytest
CSXB996 天前
三十八、Python(pytest框架-上)
python·功能测试·测试工具·单元测试·pytest
漫漫人生路^_^6 天前
unittest和pytest
pytest
_可乐无糖7 天前
使用--log-file保存pytest的运行日志
pytest