通过前几章的学习,我们完成了 Todo List 程序的 todo 管理部分,实现了对 todo 的增、删、改、查基本操作,这也是几乎所有 Web 程序都具备的功能。我们当然可以按照目前的思路继续来实现用户管理部分,在 models.py 中编写用户相关的模型,在 templates/ 目录下新建用户相关 HTML,在 controllers.py 中编写用户相关的视图函数。但是,随着新功能的加入,把不同功能的代码都写在相同的文件中必然会引起代码的混乱。为实现易维护、易扩展的代码,我们需要对项目的目录结构进行重构。
同样的,将 models.py 文件换成 models/ 包,将原来的 Todo 模型类放到 models/todo.py 中。不过这里不只是简单的将原来的 Todo 模型代码迁移过来,还对其进行了重构,抽象出一个模型基类 Model 将其放到 models/__init__.py 中,然后 Todo 继承自 Model 模型基类。这样做的好处是等我们编写用户模型时,查找、保存等方法就不需要在用户模型中再写一遍了,只需要让用户模型也继承 Model 模型基类即可。
@classmethod def_load_db(cls): """加载 JSON 文件中所有模型对象数据""" path = cls._db_path() with open(path, 'r', encoding='utf-8') as f: return json.load(f)
@classmethod def_save_db(cls, data): """将模型对象数据保存到 JSON 文件""" path = cls._db_path() with open(path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=4)
@classmethod defall(cls, sort=False, reverse=False): """查询全部模型对象""" # 这一步用来将所有从 JSON 文件中读取的 model 数据转换为 Model 实例化对象,方便后续操作 models = [cls(**model) for model in cls._load_db()] # 对数据按照 id 排序 if sort: models = sorted(models, key=lambda x: x.id, reverse=reverse) return models
@classmethod deffind_by(cls, limit=-1, ensure_one=False, sort=False, reverse=False, **kwargs): """根据传入条件查询模型对象""" result = [] models = [model.__dict__ for model in cls.all(sort=sort, reverse=reverse)] for model in models: # 根据关键字参数查询 model for k, v in kwargs.items(): if model.get(k) != v: break else: result.append(cls(**model))
# 查询给定条数的数据 if0 < limit < len(result): result = result[:limit] # 查询结果集中的第一条数据 if ensure_one: result = result[0] if len(result) > 0elseNone
return result
@classmethod defget(cls, id): """通过 id 查询模型对象""" result = cls.find_by(id=id, ensure_one=True) return result
defsave(self): """保存模型对象""" # 查找出除 self 以外所有 model # model.__dict__ 是保存了所有实例属性的字典 models = [model.__dict__ for model in self.all(sort=True) if model.id != self.id]
# 自增 id if self.id isNone: # 如果 model_list 大于 0 说明不是第一条 model,取最后一条 model 的 id 加 1 if len(models) > 0: self.id = models[-1]['id'] + 1 # 否则说明是第一条 model,id 为 1 else: self.id = 1
# 将当前 model 追加到 model_list models.append(self.__dict__) # 将所有 model 保存到文件 self._save_db(models)
defdelete(self): """删除模型对象""" model_list = [model.__dict__ for model in self.all() if model.id != self.id] self._save_db(model_list)
test_index 函数用来测试首页视图函数,为了简化测试代码,测试函数中并没有通过请求 Web Server 来获取响应。首先将请求消息报文 request_message 传递给 Resquest 类构造了一个请求对象,然后根据请求路径 request.path 获取处理该请求的视图函数,接着调用视图函数来获取响应报文。这样做的好处是不需要编写发起请求的客户端程序,但测试覆盖率肯定会有所下降。这是一个选择性的问题,需要考虑时间成本、投入产出比等。测试函数的最后通过断言语句,来断言响应报文中必然包含的内容。
test_new 函数用来测试新增 todo 视图函数,大体逻辑与 test_index 差不多。在生成测试 todo 内容时使用了 UUID,目的是为了生成足够随机的字符串避免与 db/todo.json 中已存在的 todo 内存重复,这样通过 Todo.find_by() 方法查找 todo 时能够确保查询结果正确。还需要注意的一点是在新增 todo 成功后又将其删除了,这样做的目的是为了让测试代码不对原有数据产生影响。理论上,测试代码每次执行的结果都应该相同,并且不应该破坏程序原有的数据。
使用 Python 运行测试文件 python3 test_controllers.py,如果测试代码执行完成后没有任何输出,就说明全部测试通过。测试代码遵循 Linux 设计哲学,没有消息就是最好的消息。如果测试代码执行过程中抛出 AssertionError 异常,则说明测试未通过,要么是被测代码有问题,要么是测试代码本身有问题。
由于篇幅所限,对 Todo List 程序的测试部分讲解就到这里,其他部分的测试代码可以访问本章节源码进行查看。