前言
这是一次从创建项目到测试打包上传Pypi的过程,记录一下。
创建项目
这里我是用的是pdm这个包管理工具,创建项目的命令是pdm init,跟着引导一步步走,之后会生成一个pyproject.toml文件,里面有项目的一些基本信息,比如作者、版本号等。下面是一个示例:
mkdir project
cd project
pdm init
之后生成的目录大概如下:
.
├── pyproject.toml
├── pdm.lock
└── src
├── project
| └── __init__.py
└── tests
└── __init__.py
安装依赖这些就不赘述了。
注意将源码放在src/project目录下,这样打包的时候会自动将src/project目录下的文件打包,而不是将整个项目打包。而且要加上build信息
[tool.pdm.build]
includes = ["src/project"]
[tool.pdm.version]
source = "file"
path = "src/project/__init__.py"
测试
用例测试
这里我是用的是pytest框架,用pdm add -d pytest来将pytest安装在dev依赖上,这样不会影响到用户pdm install project的安装。
测试代码放在tests目录下,测试用例文件名以test_开头,用例方法也以test_开头,比如
# tests/test_add.py
def test_add():
assert add(1, 2) == 3
使用pytest命令来进行测试
mock测试
当测试数据依赖于某些API,且不方便测试时获得,那么可以使用mock来模拟数据。pytest的mock框架是pytest-mock,用pdm add -d pytest-mock来安装。
# tests/test_api.py
import requests
def api():
return requests.get("https://example.com").json()
def test_request(mocker):
mock_response = mocker.Mock()
mock_response.json.return_value = {"code": 0, "data": "success"}
mocker.patch("requests.get", return_value=mock_response)
assert api() == {"code": 0, "data": "success"}
Coverage覆盖率测试
pytest的coverage测试框架是pytest-cov,用pdm add -d pytest-cov来安装。测试用例不需要改变,但需要加上--cov=src指定覆盖率测试的目录;需要分支覆盖率,需要加上--cov-branch;如果还要测试报告,命令就变成了pytest --cov=src --cov-branch --cov-report=html:tests/report/coverage。注意这里要加上pytest-html依赖才能生成html报告。
Tox多Py版本兼容性测试
如果你要测试你的项目在不同Python版本下是否能正常运行,那么在PDM的环境下,正好可以使用tox-pdm来进行测试,pdm add -d tox-pdm,然后创建tox.ini文件,内容如下:
[tox]
env_list = py{38, 39, 310, 311}
[testenv]
groups = dev
commands =
your test commands here
env_list是你要测试的Python版本,groups是测试组,commands是测试命令,比如pytest。
持续集成测试
其实就是运用Github Actions,下面有案例。
测试报告
不同版本不同的测试报告生成起来会挤在一堆,我自己的做法是写一个测试脚本:
# tests/__init__.py
import sys
from pathlib import Path
import pytest
import toml
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
version = toml.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"][
"version"
]
def test():
path = f"v{version}/{py_version}"
args = [
"--cov=src",
"--cov-branch",
f"--cov-report=html:tests/report/{path}/coverage",
f"--html=tests/report/{path}/report.html",
]
args.extend(sys.argv[1:]) # Pass additional arguments to pytest if any
pytest.main(args)
然后在pyproject.toml上添加
[tool.pdm.scripts]
test = {call = "tests:test"}
这样就可以使用pdm test来运行测试了。
最后在tox.ini上把commands改成pdm test,报告就分别存放了。
打包上传Pypi
pdm build是打包,pdm publish是上传。但这里我用一个更复杂的例子来说明,做到持续集成。
name: Upload Python Package
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: Version Number
required: true
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ env.GITHUB_REF }}
token: ${{ secrets.GH_TOKEN }}
- name: Config Git Username
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.9.x"
- name: Install dependencies
run: |
pip install pdm
- name: Change version
run: |
python ./bump-version.py ${{ github.event.inputs.version }}
git add .
git diff-index --quiet HEAD || git commit -m ":sparkles: ${{ github.event.inputs.version }}"
git push
- name: Build and Publish
run: |
pdm publish --username __token__ --password ${{ secrets.PYPI_API_TOKEN }}
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: v${{ github.event.inputs.version }}
run: |
gh release create "$tag" \
--repo="$GITHUB_REPOSITORY" \
--title="${GITHUB_REPOSITORY#*/} ${tag#v}" \
--generate-notes
流程是,输入version值后,他会提取项目设置里设置的secrets.GH_TOKEN和secrets.PYPI_API_TOKEN来进行打包、发布和创建release。其中还使用bump-version.py处理了__init__.py上的__version__信息。其他更多的命令,请自行搜索。