# 哈佛免费 Python 课，FDE 最该精读第 5 周

> CS50P 第 5 周讲 assert、pytest 和打包：代码要跑在客户数据上，还要交给别人接手，这是底线。

原文: https://fdetimes.net/zh/books-courses/harvard-cs50p-python-unit-testing/

CS50P 的课程大纲从第 0 周排到第 9 周。对想成为前线部署工程师（FDE）的人来说，最值得投入精力的不是 OOP，也不是库，而是第 5 周：David J. Malan 在这一周讲 `assert`、`AssertionError`、`pytest`，以及如何用 `__init__.py` 把代码打包成 package。

原因在于这份工作本身。你在客户现场写的代码，要跑在不是你生成的数据上；你离开项目后，还得有人接手。

所以，带测试、错误处理得当的 Python 基础，对 FDE 来说是最低要求，而不是加分项。CS50P 教的正是这套基础。

## 谁来讲，课程怎么上？

课程全称是 CS50's Introduction to Programming with Python，官网是 cs50.harvard.edu/python。哈佛对 Malan 的介绍是 Gordon McKay 计算机科学实践教授（Gordon McKay Professor of the Practice of Computer Science）。他从 2007 年起讲授 CS50。

据 Harvard PLL 介绍，课程为期 9 周，自定进度，每周需投入 3–9 小时。共十讲，编号 0 到 9。课程资料可在 OpenCourseWare 上免费观看；如果想拿证书，无论免费还是付费，都需要注册 edX 账号。

课程不要求编程经验，也不需要证明你学过任何前置课程。本轮课程于 2026 年 1 月 1 日开始，只有一个最终截止日期——2027 年 6 月 30 日，单项作业没有截止时间。

有一点要注意：新版本发布后，为旧版本提交的作业可能不再被认可。别学到一半拖到明年。

## 十讲内容，拼起来就是一项熟悉的 FDE 任务

十讲依次是函数与变量、条件语句、循环、异常、库、单元测试、File I/O、正则、OOP 和 Et Cetera，最后是期末项目。逐周看，这是一份很常见的清单；拼在一起，恰好对应一项再普通不过的 FDE 任务。

假设客户发来一份从老旧系统导出的 CSV。日期列有的地方写成 "2026-03-01"，有的地方写成 "01/03/2026"，还有几行填的是文字而不是日期。

处理这份文件，你需要用 File I/O（第 6 周）读取文件，用正则（第 7 周）识别两种格式，用异常（第 3 周）处理坏行，同时不让程序崩溃。

```python
import re

def parse_date(s):
    if m := re.fullmatch(r"(\d{4})-(\d{2})-(\d{2})", s):
        y, mo, d = m.groups()
    elif m := re.fullmatch(r"(\d{2})/(\d{2})/(\d{4})", s):
        d, mo, y = m.groups()
    else:
        raise ValueError(f"无效日期：{s}")
    return f"{y}-{mo}-{d}"
```

剩下的一步就是第 5 周：在拿真实数据跑之前，先写测试，把这个函数该有的行为固定下来。

```python
import pytest
from parse import parse_date

def test_two_formats():
    assert parse_date("2026-03-01") == "2026-03-01"
    assert parse_date("01/03/2026") == "2026-03-01"

def test_bad_input():
    with pytest.raises(ValueError):
        parse_date("yesterday")
```

## 常见错误：正则匹配上了，日期却还是错的

两个测试都通过了，但函数仍有漏洞。输入 "99/99/2026"，正则照样能匹配，因为 `\d{2}` 只检查是不是两位数字，函数会把 "2026-99-99" 当成真实日期返回。

这是初学者写正则的典型错误：正则检查的是字符串的形状，而不是含义。修复办法是交给标准库去校验日期，再针对漏掉的这个情况补一条测试。

```python
import re
from datetime import date

def parse_date(s):
    if m := re.fullmatch(r"(\d{4})-(\d{2})-(\d{2})", s):
        y, mo, d = m.groups()
    elif m := re.fullmatch(r"(\d{2})/(\d{2})/(\d{4})", s):
        d, mo, y = m.groups()
    else:
        raise ValueError(f"无效日期：{s}")
    date(int(y), int(mo), int(d))  # 新增一行：日期不存在时抛出 ValueError
    return f"{y}-{mo}-{d}"
```

新增的这一行会在日期不存在时抛出 `ValueError`。下面是针对漏网情况的新测试：

```python
def test_nonexistent_date():
    with pytest.raises(ValueError):
        parse_date("99/99/2026")
```

练习：客户又发来一批 "1/3/2026" 这样不带前导零的数据。先写测试，期望结果为 "2026-03-01"；再加一条测试，确保 "31/02/2026" 会被拒绝；然后才去改正则和输出格式，直到所有测试通过。

**要点:** 在你机器上能跑的代码，不一定能处理客户的数据。

## 从课程中带走的三个观念

第一，出错是常态。课程第 0 周的讲义说，bug 是编程的自然组成部分，是等你去解决的问题，并鼓励学习者不要因此气馁。

对 FDE 来说，这句话的含义更具体：坏数据一定会出现，所以上面的 `raise ValueError` 是一个设计决策，而不是临时补丁。

第二，测试是你和自己、和接手代码的人之间的约定。上面的测试准确描述了函数接受什么、拒绝什么。客户发来新格式时，先加一行 `assert` 再改代码，就能确定没有破坏原有格式。

第三，留给别人的代码必须打包。第 4 周讲如何使用库，第 5 周讲如何用 `__init__.py` 构建自己的 package。躺在 Downloads 文件夹里的一个脚本，和一个客户工程师在你离开项目后还能 import、能测试的 package，差别很大。

## 适合谁，按什么顺序学？

如果你有 2-8 年 Java、C# 或 JavaScript 经验，第 0–2 周用一两个晚上快速过一遍即可。

把时间留给第 3、5、6、7 周，这几周的每一套习题都要做，包括看起来很简单的。

零基础的学习者按原有顺序学，这门课本来就是为从没编过程的人设计的。

最值得投入的是期末项目。选一个接近真实客户工作的问题，比如一个清洗导出文件的工具，就像上面的例子，配上测试目录和说明运行方法的 README。写进简历时，按项目来描述（"支持 N 种数据格式，使用 pytest 测试"），而不是只写课程名。

证书方面，CS50 免费颁发自己的证书。edX 上另有付费认证证书，可以作为向雇主证明的材料。

无论选哪种，申请 FDE 岗位时，都应把项目仓库当作主要证据，证书只是辅助。让 `tests/` 目录替你说话。

CS50P 不会把你变成 FDE。它能做到的是：客户第一次把一份杂乱的数据文件交给你时，你的第一反应是写测试，而不是直接跑、然后祈祷。

**本周动手试试:**

- 打开 cs50.harvard.edu/python，如果想要证书就注册一个 edX 账号，用一个晚上快速浏览第 0–2 周。
- 写一个支持两种日期格式的 parse_date 函数，配两个 pytest 测试：一个测有效输入，一个测坏数据。
- 参照真实的客户问题选定期末项目主题，比如一个清洗老旧系统导出文件的工具。

## 信源

- [CS50's Introduction to Programming with Python | Harvard PLL](https://pll.harvard.edu/course/cs50s-introduction-programming-python)

- [CS50's Introduction to Programming with Python (CS50P)](https://cs50.harvard.edu/python/)

- [CS50's Introduction to Programming with Python (CS50P)](https://cs50.harvard.edu/python/2022/)

- [CS50P FAQs](https://cs50.harvard.edu/python/faqs/)

- [CS50P Lecture 0 notes](https://cs50.harvard.edu/python/notes/0/)

- [CS50P Week 5](https://cs50.harvard.edu/python/2022/weeks/5/)

- [CS50 - Wikipedia](https://en.wikipedia.org/wiki/CS50)
