4、Python基础回顾(二):函数定义与调用、模块与包管理(import)、文件操作(open/read/write)、异常处理(try/except)
好,咱们接着聊。上一章我们把Python的基本数据类型和流程控制捋了一遍,算是把地基打牢了。这一章,我们得开始搭“脚手架”了——也就是函数、模块、文件操作和异常处理。
说实话,在自动化测试框架里,这四样东西是天天都要用的。你写一个测试用例,本质上就是在定义一个函数;你要复用代码,就得用模块和包;你要读配置文件、写日志,离不开文件操作;而异常处理,嗯,它是你代码的“安全气囊”。
我个人习惯,在开始写任何框架之前,都会先把这几块基本功练扎实。不然写到后面,代码会变得又臭又长,自己都不想看。
本章核心脉络: 从“把代码封装成函数”开始,到“把函数组织成模块”,再到“处理外部文件和运行时错误”。这是从写脚本到写框架的必经之路。
4.1 函数:代码的“积木块”
函数是什么?说白了,就是把一段逻辑打包起来,给它起个名字。以后你想用这段逻辑,直接喊名字就行。
在自动化测试里,函数太重要了。比如你每次都要登录系统,难道每次都写一遍登录代码?当然不。你写一个 login() 函数,到处调用就行。
4.1.1 定义与调用
Python 定义函数用 def 关键字,很简单:
def login(username, password):
# 模拟登录逻辑
if username == "admin" and password == "123456":
return True
else:
return False
# 调用
result = login("admin", "123456")
print(result) # 输出: True
这里有个细节,我刚开始写代码时经常搞混——函数定义不会执行代码,只有调用时才会执行。你想想看,如果你定义了一个函数,它自己就跑了,那不乱套了?
4.1.2 参数与返回值
Python 的参数机制很灵活。我个人最喜欢的是默认参数和关键字参数:
def read_config(file_path, encoding="utf-8"):
# 默认编码是 utf-8
print(f"读取文件: {file_path}, 编码: {encoding}")
# 实际读取逻辑...
# 两种调用方式
read_config("config.ini") # 使用默认编码
read_config("config.ini", encoding="gbk") # 指定编码
避坑指南: 我曾经在项目里踩过一个坑——默认参数如果是可变对象(比如列表、字典),会有大问题。因为默认参数只在函数定义时被创建一次,多次调用会共享同一个对象。所以,默认参数尽量用不可变类型(None、字符串、数字)。
4.1.3 作用域与 global
函数内部的变量是局部变量,函数外部访问不到。反过来,函数内部可以读取全局变量,但不能直接修改。想修改?用 global 声明一下:
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 输出: 1
嗯,这里要注意,global 能不用就不用。它会让代码变得难以调试。我一般只在写计数器或者全局配置时才会用到。
4.2 模块与包管理:别重复造轮子
你写的函数越来越多,总不能全塞在一个文件里吧?这时候就需要模块和包了。
模块就是一个 .py 文件。包就是一个包含 __init__.py 的文件夹。
4.2.1 import 的几种姿势
| 写法 | 说明 | 示例 |
|---|---|---|
import 模块名 |
导入整个模块,使用时需加前缀 | import os; os.getcwd() |
from 模块名 import 函数名 |
导入特定函数,直接使用 | from os import getcwd; getcwd() |
from 模块名 import * |
导入所有(不推荐,容易命名冲突) | from os import * |
import 模块名 as 别名 |
起个短名字,方便调用 | import pandas as pd |
我个人习惯用 import 模块名 as 别名 这种方式。比如 import numpy as np,写起来顺手,别人一看也知道是哪个库。
4.2.2 包的结构
假设你的测试框架结构是这样的:
test_framework/
├── __init__.py
├── core/
│ ├── __init__.py
│ └── runner.py
└── utils/
├── __init__.py
└── helpers.py
那么,在 runner.py 里导入 helpers.py 中的函数:
from utils.helpers import read_csv
注意那个 __init__.py 文件。在 Python 3.3 之前,它必须存在才能让文件夹被视为包。现在虽然可以没有,但我建议还是保留,里面可以写一些包的初始化代码或者 __all__ 变量。
注意: 导入模块时,Python 会搜索 sys.path 中的路径。如果你自己写的模块导入失败,八成是路径没加对。可以用 sys.path.append("你的路径") 临时解决,但更好的做法是用相对导入或设置 PYTHONPATH 环境变量。
4.3 文件操作:读写是基本功
自动化测试离不开文件操作。读配置文件、写测试报告、处理日志……样样都要跟文件打交道。
4.3.1 打开文件:open()
open() 函数返回一个文件对象。最常用的模式:
| 模式 | 说明 |
|---|---|
'r' |
只读(默认) |
'w' |
写入(会覆盖原有内容) |
'a' |
追加(在文件末尾写入) |
'rb' |
二进制读取(如图片) |
4.3.2 读取文件
最推荐的方式是使用 with 语句,它会自动关闭文件:
with open("test_data.txt", "r", encoding="utf-8") as f:
content = f.read() # 读取全部内容
# 或者逐行读取
# for line in f:
# print(line.strip())
为什么用 with?因为我曾经犯过一个低级错误——打开文件后忘记 close(),导致文件一直被占用,后续操作报错。用 with 就永远不会忘。
4.3.3 写入文件
with open("report.html", "w", encoding="utf-8") as f:
f.write("<h1>测试报告</h1>\n")
f.write("<p>通过用例: 10</p>\n")
写入时注意两点:一是 'w' 模式会清空原文件,二是记得加换行符 \n。
小技巧: 如果你要写多行内容,可以用 writelines() 方法,传入一个字符串列表。但注意它不会自动加换行,你得自己在每个字符串后面加 \n。
4.4 异常处理:让代码更健壮
程序跑着跑着突然崩溃,这种事谁都遇到过。异常处理就是用来兜底的。
4.4.1 try/except 的基本结构
try:
# 可能会出错的代码
result = 10 / 0
except ZeroDivisionError:
print("除数不能为0!")
except Exception as e:
print(f"发生了未知错误: {e}")
else:
print("没有异常时执行")
finally:
print("无论如何都会执行")
这里有个顺序问题:except 子句会从上到下匹配,所以具体的异常要写在前面,通用的 Exception 写在后面。
4.4.2 实战中的异常处理
在自动化测试里,最常见的场景是读取配置文件:
def load_config(file_path):
try:
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
print(f"配置文件 {file_path} 不存在,使用默认配置")
return "default_config"
except PermissionError:
print(f"没有权限读取 {file_path}")
return None
except Exception as e:
print(f"读取配置时发生未知错误: {e}")
return None
你看,这样处理之后,即使文件不存在,程序也不会崩溃,而是优雅地降级。
4.4.3 自定义异常
有时候,Python 内置的异常不够用。比如你想在测试用例失败时抛出一个特定的异常:
class TestCaseFailedError(Exception):
"""自定义测试用例失败异常"""
def __init__(self, case_name, message):
self.case_name = case_name
self.message = message
super().__init__(f"用例 [{case_name}] 失败: {message}")
# 使用
raise TestCaseFailedError("login_test", "用户名或密码错误")
注意: 不要滥用异常。异常处理是为了处理“异常情况”,而不是用来控制正常流程。比如,不要用 try/except 来代替 if/else 做条件判断,那样性能会差很多。
好了,这一章的内容就到这里。函数、模块、文件、异常——这四个东西组合起来,你已经能写出结构清晰、健壮可靠的 Python 代码了。下一章我们会把这些知识串起来,开始搭建真正的自动化测试框架。