单元测试可能令人生畏,但是这些 Python 模块会使你的生活变得更容易。
在这个教程中,你将学到如何对执行 HTTP 请求代码的进行单元测试。也就是说,你将看到用 Python 对 API 进行单元测试的艺术。
单元测试是指对单个行为的测试。在测试中,一个众所周知的经验法则就是隔离那些需要外部依赖的代码。
比如,当测试一段执行 HTTP 请求的代码时,建议在测试过程中,把真正的调用替换成一个假的的调用。这种情况下,每次运行测试的时候,就可以对它进行单元测试,而不需要执行一个真正的 HTTP 请求。
问题就是,怎样才能隔离这些代码?
这就是我希望在这篇博文中回答的问题!我不仅会向你展示如果去做,而且也会权衡不同方法之间的优点和缺点。
要求:
- Python 3.8
- pytest-mock
- requests
- flask
- responses
- VCR.py
使用一个天气状况 REST API 的演示程序
为了更好的解决这个问题,假设你正在创建一个天气状况的应用。这个应用使用第三方天气状况 REST API 来检索一个城市的天气信息。其中一个需求是生成一个简单的 HTML 页面,像下面这个图片:
伦敦的天气,OpenWeatherMap。图片是作者自己制作的。
为了获得天气的信息,必须得去某个地方找。幸运的是,通过 OpenWeatherMap的 REST API 服务,可以获得一切需要的信息。
好的,很棒,但是我该怎么用呢?
通过发送一个 GET
请求到:https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}&units=metric
,就可以获得你所需要的所有东西。在这个教程中,我会把城市名字设置成一个参数,并确定使用公制单位。
检索数据
使用 requests
模块来检索天气数据。你可以创建一个接收城市名字作为参数的函数,然后返回一个 JSON。JSON 包含温度、天气状况的描述、日出和日落时间等数据。
下面的例子演示了这样一个函数:
def find_weather_for(city: str) -> dict:
"""Queries the weather API and returns the weather data for a particular city."""
url = API.format(city_name=city, api_key=API_KEY)
resp = requests.get(url)
return resp.json
这个 URL 是由两个全局变量构成:
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
API = BASE_URL + "?q={city_name}&appid={api_key}&units=metric"
API 以这个格式返回了一个 JSON:
{
"coord": {
"lon": -0.13,
"lat": 51.51
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"base": "stations",
"main": {
"temp": 16.53,
"feels_like": 15.52,
"temp_min": 15,
"temp_max": 17.78,
"pressure": 1023,
"humidity": 72
},
"visibility": 10000,
"wind": {
"speed": 2.1,
"deg": 40
},
"clouds": {
"all": 0
},
"dt": 1600420164,
"sys": {
"type": 1,
"id": 1414,
"country": "GB",
"sunrise": 1600407646,
"sunset": 1600452509
},
"timezone": 3600,
"id": 2643743,
"name": "London",
"cod": 200
当调用 resp.json
的时候,数据是以 Python 字典的形式返回的。为了封装所有细节,可以用dataclass
来表示它们。这个类有一个工厂方法,可以获得这个字典并且返回一个Weat