自动创建用于测试的数据库

Automating database creation for testing

为了加快测试速度,使用基于内存的 sqlite 会更好,但仍然有必要偶尔使用 MySQL 进行更接近生产的测试。 为了避免枯燥的 discussion/abstract 问题,下面的代码插入了几个词并确认它们在数据库中,对于刚刚提到的两种类型的 SQL 数据库。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import unittest


db = SQLAlchemy()
TEST_DATABASE_URL_MEMORY = 'sqlite:///:memory:'
TEST_DATABASE_URL_MYSQL = 'mysql+pymysql://root:@127.0.0.1:3306/somewords'


def create_app(db_url):
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = db_url
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db.init_app(app)
    return app


class Word(db.Model):
    __tablename__ = 'words'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    word = db.Column(db.String(32), index=True)


class TestInsertion(unittest.TestCase):
    def manual_set_up(self, db_url):
        self.app = create_app(db_url)
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.drop_all()
        db.create_all()

    def insert(self):
        words = ['hello', 'world']
        for word in words:
            w = Word(word=word)
            db.session.add(w)
        db.session.commit()
        for word in words:
            assert Word.query.filter_by(word=word).first() is not None

    def test_dbs(self):
        for db_url in [TEST_DATABASE_URL_MEMORY,
                       TEST_DATABASE_URL_MYSQL]:
            self.manual_set_up(db_url)
            self.insert()

第一个(sqlite)通过。第二次失败:

E       sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1049, "Unknown database 'somewords'")

我们可以,每次我们 运行 测试时,创建数据库

> mysql -u root -p
Welcome to the MySQL monitor.  Commands end with ; or \g.
mysql> create database somewords;
Query OK, 1 row affected (0.02 sec)
mysql> quit;

但这排除了自动化测试,所以我猜我缺少一些基本的东西。

如何通过自动创建数据库来运行无人值守的测试?

更新

1.0, 1.1 的测试示例和教程 (flask/examples/tutorial/tests/conftest.py) 使用 tempfile.mkstemp(),这似乎是一个不错的选择。无需担心设置名称和创建数据库,甚至无需关心(随机且可丢弃的)数据库名称。 How/where数据库创建部分完成了吗?

import tempfile
db_fd, db_path = tempfile.mkstemp()

sqlalchemy 似乎不直接支持创建和删除数据库,但 sqlalchemy-utils 支持:https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/functions/database.html#create_database.

这将要求您已经有一个 mysql 服务器 运行ning,因此它不允许您使用 tempfile.mkstemp() 来控制文件放置。

另一种方法是 运行 mysql 命令作为自动化测试的一部分。

通过对 manual_set_uptest_dbs 的一些修改,我能够 运行 编码。

对于 mysql 数据库,我从 db_url 中删除了数据库名称。而且 db.drop_all() 也失败了,因为数据库不存在所以我输入 try/except 并在此处传递异常。然后在 db.create_all() 之前我创建了一个绕过 db_url 的 sqlachemy 引擎,它没有数据库名称 db.create_engine(db_url).

# your imports ...

import sqlalchemy.exc

#...
TEST_DATABASE_URL_MYSQL = 'mysql+pymysql://root:@127.0.0.1:3306/'

def manual_set_up(self, db_url, db_kind, db_name=None):
    if db_kind == "mysql":
        self.app = create_app(db_url + db_name)
    else:
        self.app = create_app(db_url)
    self.app_context = self.app.app_context()
    self.app_context.push()
    try:
        db.drop_all()
    except sqlalchemy.exc.InternalError as e:
        if "unknown database" in str(e.args[0]).lower():
            pass
    try:
        db.create_all()
    except sqlalchemy.exc.InternalError as e:
        if "unknown database" in str(e.args[0]).lower():
            db.create_engine(db_url, {}).execute(f"CREATE DATABASE IF NOT EXISTS {db_name};")
            db.create_all()

def test_dbs(self):
    for args in [(TEST_DATABASE_URL_MEMORY, "sqlite"),
                 (TEST_DATABASE_URL_MYSQL, "mysql", "somewords")]:
        self.manual_set_up(*args)
        self.insert()