引言
在读一本书《Learn Python Programming》1的第8章,按照书中的讲解先后安装了marshmallow
和pytest
第三方库,j进而按照书中的代码结构和代码在ch08文件夹下运行pytest tests
,出现如下错误:
ch08中的api.py的代码为:
# ch08/api.py
'''
This program needs: pip install marshmallow; pip install pytest
'''
import os
import csv
from copy import deepcopy
from marshmallow import Schema, fields, pre_load
from marshmallow.validate import Length, Range
class UserSchema(Schema):
''' Represent a *valid* user. '''
email = fields.Email(required=True)
name = fields.String(required=True, validate=Length(min=1))
age = fields.Integer(required=True, validate=Range(min=18, max=65))
role = fields.String()
@pre_load(pass_many=False)
def strip_name(self, data):
data_copy = deepcopy(data)
try:
data_copy['name'] = data_copy['name'].strip()
except (AttributeError, KeyError, TypeError):
pass
return data_copy
schema = UserSchema()
def export(filename, users, overwrite=True):
'''Export a CSV file.
Create a CSV file and fill with valid users. if "overwrite"
is False and file already exists, raise IOError.
'''
if not overwrite and os.path.isfile(filename):
raise IOError(f"'{filename}' already exists")
valid_users = get_valid_users(users)
write_csv(filename, valid_users)
def get_valid_users(users):
'''Yield one valid user at a time from users.'''
yield from filter(is_valid, users)
def is_valid(user):
'''Return whether or not the user is valid.'''
return not schema.validate(user)
def write_csv(filename, users):
'''Write a CSV given a filename and a list of users.
The users are assumed to be valide for the given CSV structure.
'''
fieldnames = ['email', 'name', 'age', 'role']
with open(filename, 'x', newline='') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for user in users:
writer.writerow(user)
ch08/tests/test_api.py中代码为:
# tests/test_api.py
import os
from unittest.mock import patch, mock_open, call
import pytest
from ..api import is_valid, export, write_csv
@pytest.fixture
def min_user():
""" Represent a valid user with minimal data. """
return {
'email': 'minimal@example.com',
'name': 'Primus Minimus',
'age': 18,
}
@pytest.fixture
def full_user():
"""Represent valid user with full data."""
return {
'email': 'full@example.com',
'name': 'Minimus Primus',
'age': 65,
'role': 'emperor',
}
@pytest.fixture
def users(min_user, full_user):
"""List of users, two valid and one invalid. """
bad_user = {
'email': 'invalid@example.com',
'name': 'Horribilis',
}
return [min_user, bad_user, full_user]
class TestIsValid:
"""Test how code verifies whether a user is valid or not. """
def test_minimal(self, min_user):
assert is_valid(min_user)
从上面错误截图并结合代码可以分析出,该错误与我们所给出的测试数据无关,并且指向了api.py文件中的strip_name()
方法。
问题解决
从事后来看,我昨晚一遇到该问题,比较慌乱和焦虑。混乱想,认为我电脑上安装的Python版本老,或者说准备在另一台台式机上重新试试代码。今早到办公室解决该问题后,冷静下来,很容易能分析出该问题的症结所在:因为方法strip_name()被装饰器@pre_load(pass_many=False)
所装饰,那么问题的症结只能是第三方库marshmallow
。这是马后炮,当时遇到问题时可没这么想。
看了下自己电脑上安装的的marshmallow
为:marshmallow 3.19.0
。又在网络上搜索了该问题,看到如下:
于是将api.py代码中的strip_name()方法签名改为:
@pre_load(pass_many=False)
def strip_name(self, data, **kwargs):
仅仅增加了参数**kwargs
,其他代码保持不变。运行成功,如下图所示:
结束语
在读相对比较老的书籍时,书中的代码可能是针对老版本库的代码。而自己电脑上安装的一般是比较新的第三方包。这时,可能会出现代码错误。解决该错误的过程能提高自己的debug能力。比较蹊跷的是,上面test_api.py是单元测试代码,也就是说是测试api.py的代码,而我现在是在找出和解决test_api.py代码出现的问题,所有应该被称为元测试。如果我们把相关问题解决了,实际上是将书中的老代码更新到了新版本。
遇到问题不要慌,要淡定。冷静的分析错误根源。
Python语言很好入门,但是,应用好Python将是一个非常广阔的话题,所耗费的时间将是很多的。如果从事Python相关职业,可能长期在用。在此引用书《Learning Python》2中话与大家共勉:
Applying Python is actually a larger topic than learning Python.
Fabrizio Romano. Learn Python Programming. 2ed. Packt, 2018. ↩︎
Mark Lutz. Learning Python. 5ed. O’Reilly, 2013. ↩︎