昨天学到博客用户增加重设密码的功能,给User表加了两个方法后,按照往常一样进行数据库迁移:运行两个指令flask migrate,flask upgrade。然后美滋滋地输入测试指令:flask test,却报错说找不到User表。
我一看数据库,确实表都没了,部分报错信息:
(venv) E:\program\Python\Web\MySecondFlaskApp>flask test
test_app_exists (test_basics.BasicsTestCase) ... ok
test_app_is_testing (test_basics.BasicsTestCase) ... ok
test_expired_confirmation_token (test_user_model.UserModelTestCase) ... ERROR
test_invalid_confirmation_token (test_user_model.UserModelTestCase) ... ERROR
test_invalid_reset_token (test_user_model.UserModelTestCase) ... ERROR
test_no_password_getter (test_user_model.UserModelTestCase) ... ok
test_password_salts_are_random (test_user_model.UserModelTestCase) ... ok
test_password_setter (test_user_model.UserModelTestCase) ... ok
test_password_verification (test_user_model.UserModelTestCase) ... ok
test_valid_confirmation_token (test_user_model.UserModelTestCase) ... ERROR
test_valid_reset_token (test_user_model.UserModelTestCase) ... ERROR
======================================================================
ERROR: test_expired_confirmation_token (test_user_model.UserModelTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "e:\program\python\web\mysecondflaskapp\venv\lib\site-packages\sqlalchemy\engine\base.py", line 1965, in _exec_single_context
cursor, str_statement, effective_parameters, context
File "e:\program\python\web\mysecondflaskapp\venv\lib\site-packages\sqlalchemy\engine\default.py", line 748, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: no such table: users
我看了一下ERROR的这些测试函数,都是在db.session.commit()处开始出错。
我的表怎么会被删了?
在Stack Overflow上一番搜寻,很快找到了答案:
flask migrate seemed to delete all my database data
迁移脚本中还没有同步得到任何表,迁移脚本是空的,用空的脚本去flask upgrade 数据库,自然就相当于把数据库的表给删了。
解决方法:在flasky.py内加上app上下文
# Import database models with app context
with app.app_context():
from app.models import User, Role
migrate = Migrate(app, db)
然后迁移数据库(flask migrate+ flask upgrade),出现了新的迁移脚本并且不是空的。(之前几次迁移数据库,都没产生新的迁移脚本,我竟然没怀疑有问题!!!)
查看数据库data.sqlite,里面有User等表的结构了。
但是再flask test进行单元测试,还是报同样的错???我一看数据库,又空了
仔细看一遍书上《使用Flask-Migrate实现数据库迁移》这章节,发现了一个之前漏掉的知识点:每次flask upgrade后会生成的新的data.sqlite。也就是说我可以随便删除它,只要迁移脚本还在就行。用flask upgrade指令,根据最新的迁移脚本来生成数据库。
删除data.sqlite后,用flask upgrade,确实生成了一个完整表结构的数据库。但是每次单元测试后,数据库里面的User等表又没了。单元测试怎么会删我的表呢?
最后一番搜索,在Stack Overflow上找到了跟我遇到一样问题的人,但是导致问题的原因不一样:
How to setup testing script in Flask with SQLite?
他是SQLALCHEMY_DATABASE_URI这个参数名写错了。
那我是因为啥?为什么flask test 会把迁移过来的数据库弄空?
flask test 单元测试自己的数据库呢?我并没有给单元测试分配数据库,它会跟数据迁移一样自动生成吗?每次flask upgrade后会生成的新的数据库,这时的表结构是没问题的,但是单元测试后,直接没表了。
想了半天,找到了答案。就是我因为偷懒,在config.py 里面把 DevelopmentConfig和TestingConfig的数据库设置成同一个了!我以为只要我不手动更改,就一直会运行默认的 DevelopmentConfig
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig # 默认设置的这个!
}
然而事实上,运行单元测试,是用的TestingConfig的配置!!!
正确的应该是用两个不同的名字,比如data-dev.sqlite
和data-test.sqlite
,而不是都用data.sqlite!!!
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(Config):
TESTING = True # True默认不发送邮件
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
运行数据库迁移指令(flask migrate+ flask upgrade),生成了data-dev.sqlite
然后运行单元测试指令flask test,生成了data-test.sqlite
现在就能解释得通一切了。
数据库迁移后生成的data.sqlite没错,里面的表也是完整存在的。单元测试运行后,新生成的数据库也叫data.sqlite,把原来有表的数据库给覆盖了。由于是在测试脚本中,生成表后会再删除(测试脚本中setUp,tearDown函数),所以直观上来看好像是“同一个数据库,但是表被删了”。
现在再来运行,都测试成功!
(venv) E:\program\Python\Web\MySecondFlaskApp>flask test
test_app_exists (test_basics.BasicsTestCase) ... ok
test_app_is_testing (test_basics.BasicsTestCase) ... ok
test_expired_confirmation_token (test_user_model.UserModelTestCase) ... ok
test_invalid_confirmation_token (test_user_model.UserModelTestCase) ... ok
test_invalid_reset_token (test_user_model.UserModelTestCase) ... ok
test_no_password_getter (test_user_model.UserModelTestCase) ... ok
test_password_salts_are_random (test_user_model.UserModelTestCase) ... ok
test_password_setter (test_user_model.UserModelTestCase) ... ok
test_password_verification (test_user_model.UserModelTestCase) ... ok
test_valid_confirmation_token (test_user_model.UserModelTestCase) ... ok
test_valid_reset_token (test_user_model.UserModelTestCase) ... ok
----------------------------------------------------------------------
Ran 11 tests in 9.176s
OK
总结:flask migrate 指令“删了”我的数据库的表,flask test 也 “删了”我的表
对config.py的结构 和 flask_migrate的原理有了更深的认知。
每次flask migrate之后,一定要检查新生成的迁移脚本内有没有问题,没有问题再flask upgrade更新,否则会出现数据丢失。