先澄清一个很多测试新手都会有的误区:学软件测试,不是只要会“点点点”就行。尤其是想拿高薪、想进大厂、想做自动化测试的朋友,代码基础是绕不开的一道坎。而在代码基础里,数据类型和运算符又是最底层的两块基石。你写测试用例时用到的边界值、等价类,你在自动化脚本里写的断言,你调接口时对返回参数的校验,本质上都是在跟数据类型和运算符打交道。
这篇文章是“软件测试入门到精通”系列的第二课,我会结合软件测试的真实场景,把 Python 的数据类型与运算符讲透。内容会覆盖基础概念、代码示例、完整实战案例、高频面试题,以及测试工作中的最佳实践。无论是零基础转行,还是已经在职想补强代码能力,这篇教程都值得你跟着敲一遍。
1. 为什么软件测试要先搞定运算符与数据类型
1.1 测试人员学代码,不是为了当开发
先看三个常见场景。
场景一:需求文档里写“用户名长度 6-12 位”。你作为测试,要设计正常用例和异常用例。6 位、12 位是边界值,5 位、13 位是越界值。这些数据的判断逻辑,写在代码里就是len(username) >= 6 and len(username) <= 12。如果你看不懂这个表达式,就很难真正理解开发同学写出来的功能边界在哪里。
场景二:接口测试返回了一段 JSON,你需要断言“登录成功”。底层逻辑其实就是判断code == 200并且msg里包含“成功”。这里既用到了比较运算符==,也用到了逻辑运算符and。不理解运算符,就写不出有效的断言。
场景三:自动化脚本要读取一批测试账号,每个账号依次登录并执行操作。这个过程中需要用到列表存储数据、字典保存用户信息、循环遍历,以及条件判断。数据结构选得好不好,直接决定脚本好不好维护。
这三个场景说明一件事:学习运算符和数据类型,是测试工程师从“手工用例思维”走向“测试开发思维”的关键一步。
1.2 本文涉及的核心范围
本文以 Python 3 为语言基础,重点拆解五块内容:
- Python 常见数据类型与相互转换;
- 算术、赋值、比较、逻辑、成员、身份等运算符;
- 运算符优先级和易错点;
- 用边界值和等价类思想设计测试数据;
- 基于注册功能编写完整的自动化断言脚本。
读完本文,你可以独立看懂简单的 Python 测试脚本,也能自己动手设计测试用例并写成可执行的断言代码。
2. 环境准备:搭建 Python 学习环境
2.1 安装 Python 解释器
我建议所有测试初学者使用 Python 3.8 以上的版本。不需要纠结最新的 3.13 还是 3.12,选一个稳定版本即可。安装步骤按操作系统区分:
- Windows:前往 Python 官网下载
.exe安装包,勾选Add Python to PATH,然后一路 Next。 - macOS:下载
.pkg文件,按向导安装。 - Linux:使用包管理器,例如
sudo apt install python3。
安装完成后,打开终端(Windows 用 CMD 或 PowerShell),输入:
python --version如果能输出类似Python 3.12.x的内容,说明安装成功。
2.2 推荐 IDE 工具
测试人员写脚本,常用的有三类工具:
- PyCharm:专业 Python IDE,社区版免费,适合写自动化测试工程。
- VS Code:轻量编辑器,安装 Python 插件后体验很好。
- Jupyter Notebook:适合逐行验证代码,学习阶段非常好用。
如果你是零基础,我更推荐 PyCharm 或 VS Code。第一个项目不建议在编辑器选择上花太多时间,能跑起来就行。
2.3 示例项目结构
本文实战部分会创建一个非常简单的项目目录:
lesson2_demo/ ├── register.py # 被测试的注册函数 ├── test_register.py # 自动化断言脚本 └── notes.py # 数据类型与运算符练习脚本全部基于 Python 标准库,不需要安装任何第三方包。这样你可以把精力完全放在理解代码逻辑上,不用被环境问题干扰。
3. 数据类型进阶:从“知道”到“会测”
3.1 Python 基本数据类型
Python 中最基础的数据类型有五种,每种类型在测试中的关注点完全不同。
| 类型 | 说明 | 示例 | 测试关注点 |
|---|---|---|---|
int | 整数 | 100、-3、0 | 边界值、正负数、0 |
float | 浮点数 | 3.14、-0.5 | 精度问题、小数位数 |
bool | 布尔值 | True、False | 真假的等价转换 |
str | 字符串 | "admin"、"123" | 空串、长度、特殊字符 |
NoneType | 空值 | None | 字段为空与字段不存在 |
list | 列表 | [1, 2, 3] | 有序、可变、下标访问 |
tuple | 元组 | (1, 2, 3) | 有序、不可变 |
dict | 字典 | {"name": "Tom"} | 键值对、接口响应体 |
set | 集合 | {1, 2, 3} | 无序、去重 |
新建notes.py文件,输入以下代码:
# notes.py age = 25 price = 99.9 is_vip = True name = "Tom" nothing = None print(type(age)) # <class 'int'> print(type(price)) # <class 'float'> print(type(is_vip)) # <class 'bool'> print(type(name)) # <class 'str'> print(type(nothing)) # <class 'NoneType'>type()返回变量的类型。在测试脚本中,这个函数经常用来校验接口返回数据的类型是否符合预期。
3.2 复合数据类型与测试场景
复合类型在测试脚本中几乎每天都会用到,尤其是list和dict。
# notes.py names = ["Alice", "Bob", "Charlie"] # list position = (10, 20) # tuple user = {"username": "admin", "age": 20} # dict tags = {"test", "automation", "test"} # set,重复的 test 会被去重 print(names[0]) # Alice print(position[1]) # 20 print(user["username"]) # admin print(tags) # {'test', 'automation'}接口测试中,Python 用requests库发送请求后,响应的 JSON 数据会被解析为dict或list。比如登录接口返回:
{ "code": 200, "message": "success", "data": { "token": "abc123", "user": { "id": 1001, "name": "Tom" } } }在 Python 中就可以这样取值:
response = { "code": 200, "message": "success", "data": { "token": "abc123", "user": {"id": 1001, "name": "Tom"} } } print(response["data"]["user"]["name"]) # Tom print(response["data"]["token"]) # abc123这就是字典的嵌套取值。测试人员必须熟练这种操作,而且还要学会判断response["data"]是否为None,避免空指针异常。
3.3 类型判断与类型转换
测试脚本中经常需要判断类型和做类型转换。
判断类型有两种写法:
print(type("123") == str) # True print(isinstance("123", str)) # True print(isinstance(123, (int, float))) # True推荐isinstance(),因为它支持一次判断多个类型,代码更清晰。
类型转换更常用。接口返回的字段可能是字符串"18",而断言要和数字 18 比较,这时必须转换:
a = "18" b = int(a) print(b + 2) # 20 c = 3.99 d = int(c) print(d) # 3,直接丢弃小数部分 e = str(100) print(e + "元") # 100元这里有一个测试人员特别容易踩的坑:
print(bool("false")) # True print(bool("")) # FalsePython 中只有空字符串、0、None、空列表、空字典等会被转换为 False,其他值都是 True。所以当接口返回字符串"false"时,不能直接用bool()去转换,否则判断会出错。正确做法是:
status = "false" print(status == "false") # True3.4 用数据类型思维设计测试数据
等价类划分和边界值分析是软件测试最核心的两个方法,而它们都和数据类型强相关。
假设需求规定“用户年龄字段为整数,范围 18~60”,测试数据可以这样设计:
| 测试类型 | 测试数据 | 预期结果 |
|---|---|---|
| 有效等价类 | 25 | 成功 |
| 边界下界 | 18 | 成功 |
| 边界上界 | 60 | 成功 |
| 下边界减一 | 17 | 失败 |
| 上边界加一 | 61 | 失败 |
| 类型错误 | "abc"、18.5、None | 失败 |
这个表格的每一行,背后都对应了代码中的比较运算符和类型判断。边界值分析本质上就是在寻找比较运算符两边的临界点。
4. 运算符详解:测试逻辑的基石
4.1 算术运算符
算术运算符用于数学计算。Python 中的常见写法如下:
a = 10 b = 3 print(a + b) # 13 print(a - b) # 7 print(a * b) # 30 print(a / b) # 3.3333333333333335 print(a // b) # 3 print(a % b) # 1 print(a ** b) # 1000这里要特别注意/和//的区别。/是普通除法,结果是浮点数;//是整除,结果向下取整。比如判断 10 个苹果分给 3 个人,每人能分几个,用10 // 3得到 3;判断还剩几个,用10 % 3得到 1。
在测试脚本中,取余运算常用于判断奇偶、判断是否能被整除:
number = 10 print(number % 2 == 0) # True,说明是偶数4.2 赋值运算符
赋值运算符用来给变量赋值。最常见的写法是复合赋值:
x = 10 x += 5 # x = x + 5,结果是 15 print(x) x -= 3 # x = 15 - 3,结果是 12 print(x) x *= 2 # x = 12 * 2,结果是 24 print(x) x /= 4 # x = 24 / 4,结果是 6.0 print(x)赋值运算符在测试脚本中的典型场景是统计失败用例数量:
fail_count = 0 fail_count += 1这里必须提醒一个新手高频率错误:Python 没有++和--运算符。如果你在 Java 或 C 语言里写习惯了i++,到 Python 里会直接报语法错误。正确的写法是i += 1。
4.3 比较运算符
比较运算符用于比较两个值的大小或相等关系,结果永远是布尔值True或False。
a = 5 b = 3 print(a == b) # False,等于 print(a != b) # True,不等于 print(a > b) # True,大于 print(a < b) # False,小于 print(a >= b) # True,大于等于 print(a <= b) # False,小于等于比较运算符是断言的核心。自动化测试中断言接口返回码,本质就是比较运算:
response_code = 200 print(response_code == 200) # True print(response_code != 500) # True print(response_code >= 200) # True新手特别容易混淆=和==。=是赋值,==是判断相等。这是软测面试笔试中的高频考点,几乎每一次基础题测试都会出现。
字符串比较时要注意大小写:
print("abc" == "abc") # True print("abc" == "ABC") # False如果需求中不区分大小写,测试脚本中需要先统一转为小写再比较:
username = "Admin" print(username.lower() == "admin") # True4.4 逻辑运算符
逻辑运算符有三种:and、or、not,分别对应“与”“或”“非”。
age = 25 has_id = True print(age >= 18 and has_id) # True,两个条件都成立 print(age < 18 or has_id) # True,至少一个成立 print(not has_id) # False,取反在自动化测试中,and和or经常用于组合多个校验条件:
username = "test001" password = "abc123" is_valid_username = len(username) >= 6 and len(username) <= 12 is_valid_password = len(password) >= 8 and len(password) <= 16 if is_valid_username and is_valid_password: print("参数校验通过") else: print("参数校验失败")这里有一个 Python 区别于其他语言的特性:短路运算。and和or并不总是返回True或False,它们会返回参与运算的具体值。
print(0 and "hello") # 0,第一个为假,直接返回 0 print(1 and "hello") # hello print(0 or "hello") # hello print("" or "default") # default这个特性在实际工作中有一个非常实用的场景:给变量设置默认值。
name = "" username = name or "guest" print(username) # guest测试数据配置文件中经常用这种方式做兜底。
4.5 成员运算符、身份运算符与位运算符
成员运算符in和not in,用于判断一个值是否存在于列表、字典、字符串等容器中。
fruit_list = ["apple", "banana", "orange"] print("apple" in fruit_list) # True print("grape" in fruit_list) # False print("a" in "banana") # True user_dict = {"username": "admin", "role": "tester"} print("username" in user_dict) # True,判断键是否存在在接口测试中,in常用于判断响应体是否包含某个字段。比如断言响应中包含token字段:
response_keys = ["code", "message", "data", "token"] print("token" in response_keys) # True身份运算符is和is not比较的是两个对象是否是同一个对象,也就是内存地址是否相同。
a = [1, 2, 3] b = [1, 2, 3] print(a == b) # True,值相等 print(a is b) # False,不是同一个对象在测试脚本中,判断None时推荐使用is None,而不要写== None:
result = None print(result is None) # True,推荐写法位运算符直接操作整数的二进制位,包括&、|、^、~、<<、>>。日常功能测试中很少用到,但在权限系统、嵌入式测试和配置项校验中偶尔会碰到。
READ = 1 # 二进制 001 WRITE = 2 # 二进制 010 EXECUTE = 4 # 二进制 100 # 用户拥有读和写权限 user_permission = READ | WRITE # 结果是 3 # 判断是否有读权限 print(user_permission & READ) # 1,说明有读权限 print(user_permission & EXECUTE) # 0,说明没有执行权限这类写法在测试底层权限模型时可能会遇到,建议了解即可。
4.6 运算符优先级
当一个表达式里有多个运算符时,优先级决定了运算顺序。常见优先级从高到低如下:
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 高 | ** | 幂运算 |
*、/、//、% | 乘除取余 | |
+、- | 加减 | |
==、!=、>、<、>=、<= | 比较 | |
not | 逻辑非 | |
and | 逻辑与 | |
| 低 | or | 逻辑或 |
尽量用括号来明确运算顺序。加括号不是为了显得谨慎,而是为了让阅读代码的人一眼看懂逻辑。
# 不推荐的写法 if a + b > 10 and c == 2 or d != 0: pass # 推荐的写法 if ((a + b) > 10 and (c == 2)) or (d != 0): pass在测试用例设计阶段,如果被测代码里同时存在and和or,一定要认真分析它的优先级是否正确,因为这是开发最容易出 bug 的地方,也正是测试人员发挥价值的地方。
5. 完整实战案例:注册功能从需求到自动化断言
理论知识讲完,现在进入正题。我们以一个注册功能为例,完整演示从需求分析、测试用例设计,到编写自动化断言脚本的全过程。
5.1 需求描述
被测注册功能规则如下:
- 用户名:6~12 位,只能包含字母、数字和下划线,不能以数字开头。
- 密码:8~16 位,必须同时包含字母和数字。
- 确认密码:必须与密码一致。
- 邮箱:必须包含
@和.,@不能在开头,.不能在末尾。
5.2 编写被测函数 register.py
文件路径:register.py
# register.py def register(username, password, confirm_password, email): """ 模拟用户注册校验函数 :param username: 用户名 :param password: 密码 :param confirm_password: 确认密码 :param email: 邮箱 :return: 成功返回 (True, "注册成功"),失败返回 (False, 错误信息) """ # 1. 用户名格式校验 if not isinstance(username, str): return False, "用户名必须为字符串" if len(username) < 6 or len(username) > 12: return False, "用户名长度必须在6到12位之间" if not (username.isalnum() or "_" in username): return False, "用户名只能包含字母、数字和下划线" if username[0].isdigit(): return False, "用户名不能以数字开头" # 2. 密码格式校验 if not isinstance(password, str): return False, "密码必须为字符串" if len(password) < 8 or len(password) > 16: return False, "密码长度必须在8到16位之间" if not (any(c.isdigit() for c in password) and any(c.isalpha() for c in password)): return False, "密码必须同时包含字母和数字" # 3. 两次密码一致性 if password != confirm_password: return False, "两次输入的密码不一致" # 4. 邮箱格式校验 if not isinstance(email, str) or "@" not in email or "." not in email: return False, "邮箱格式不正确" if email.startswith("@") or email.endswith("."): return False, "邮箱格式不正确" return True, "注册成功"这个函数里用到了很多字符串内建方法:
isalnum()判断字符串是否由字母或数字组成;isdigit()判断字符串是否全为数字;startswith()判断字符串是否以某个前缀开头;endswith()判断字符串是否以某个后缀结尾;any()搭配生成器,判断密码中是否存在数字或字母。
这些都是测试脚本中非常实用的工具函数。
5.3 设计测试用例
按照边界值分析和等价类划分思想,设计以下测试用例:
| 用例编号 | 用户名 | 密码 | 确认密码 | 邮箱 | 预期结果 |
|---|---|---|---|---|---|
| TC01 | test01 | abc12345 | abc12345 | user@test.com | 成功 |
| TC02 | test0 | abc12345 | abc12345 | user@test.com | 用户名短于6位,失败 |
| TC03 | test0123456789 | abc12345 | abc12345 | user@test.com | 用户名长于12位,失败 |
| TC04 | 1test01 | abc12345 | abc12345 | user@test.com | 用户名以数字开头,失败 |
| TC05 | test@01 | abc12345 | abc12345 | user@test.com | 用户名含特殊字符,失败 |
| TC06 | test001 | abc123 | abc123 | user@test.com | 密码短于8位,失败 |
| TC07 | test001 | abc123456789012345 | abc123456789012345 | user@test.com | 密码长于16位,失败 |
| TC08 | test001 | abcdefgh | abcdefgh | user@test.com | 密码全字母,失败 |
| TC09 | test001 | 12345678 | 12345678 | user@test.com | 密码全数字,失败 |
| TC10 | test001 | abc12345 | abc12344 | user@test.com | 两次密码不一致,失败 |
| TC11 | test001 | abc12345 | abc12345 | usertest.com | 邮箱缺少@,失败 |
| TC12 | test001 | abc12345 | abc12345 | @test.com | 邮箱@开头,失败 |
| TC13 | test001 | abc12345 | abc12345 | user@test.com. | 邮箱点号结尾,失败 |
| TC14 | 123456 | abc12345 | abc12345 | user@test.com | 用户名以数字开头,失败 |
这 14 条用例覆盖了合法路径、长度边界、格式错误、逻辑错误等主要场景。
5.4 编写自动化断言脚本
文件路径:test_register.py
# test_register.py from register import register def run_case(username, password, confirm_password, email, expected_result): """ 执行单个测试用例并断言结果 :param expected_result: 预期结果,True 表示成功,False 表示失败 """ result, message = register(username, password, confirm_password, email) if result != expected_result: print(f"[FAILED] username={username}, password={password}, email={email}") print(f" 预期: {'成功' if expected_result else '失败'}, 实际: {message}") return False else: print(f"[PASSED] username={username}, 结果={message}") return True if __name__ == "__main__": test_cases = [ # (用户名, 密码, 确认密码, 邮箱, 预期结果) ("test01", "abc12345", "abc12345", "user@test.com", True), # TC01 ("test0", "abc12345", "abc12345", "user@test.com", False), # TC02 ("test0123456789", "abc12345", "abc12345", "user@test.com", False), # TC03 ("1test01", "abc12345", "abc12345", "user@test.com", False), # TC04 ("test@01", "abc12345", "abc12345", "user@test.com", False), # TC05 ("test001", "abc123", "abc123", "user@test.com", False), # TC06 ("test001", "abc123456789012345", "abc123456789012345", "user@test.com", False), # TC07 ("test001", "abcdefgh", "abcdefgh", "user@test.com", False), # TC08 ("test001", "12345678", "12345678", "user@test.com", False), # TC09 ("test001", "abc12345", "abc12344", "user@test.com", False), # TC10 ("test001", "abc12345", "abc12345", "usertest.com", False), # TC11 ("test001", "abc12345", "abc12345", "@test.com", False), # TC12 ("test001", "abc12345", "abc12345", "user@test.com.", False), # TC13 ("123456", "abc12345", "abc12345", "user@test.com", False), # TC14 ] total = len(test_cases) passed = 0 for case in test_cases: username, password, confirm_password, email, expected = case if run_case(username, password, confirm_password, email, expected): passed += 1 print(f"\n共执行 {total} 条用例,通过 {passed} 条,失败 {total - passed} 条") if passed == total: print("测试全部通过") else: print("存在失败用例,请检查被测函数逻辑")5.5 运行与验证
在项目目录lesson2_demo下执行:
python test_register.py预期输出结果(摘录):
[PASSED] username=test01, 结果=注册成功 [PASSED] username=test0, 结果=用户名长度必须在6到12位之间 [PASSED] username=test0123456789, 结果=用户名长度必须在6到12位之间 ... 共执行 14 条用例,通过 14 条,失败 0 条 测试全部通过这个脚本不依赖任何第三方测试框架,直接运行就能看到结果。如果你后续学习了pytest,可以把这套用例改写成pytest参数化形式,报告会更规范,执行效率也更高。
6. 常见问题与排查思路
在学习和使用数据类型与运算符的过程中,有几个高频问题几乎每个人都会遇到。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
10 / 3结果不是预期 3 | /是浮点除法,返回浮点数 | 改用//整除或round()取整 |
assert "0" == 0报错 | 字符串和整数类型不匹配 | 先确认类型,再用int()或str()转换 |
bool("false")返回 True | 非空字符串都会被转为 True | 不要用bool(),使用== "false"比较 |
用is判断整数相等失败 | is比较内存地址,小整数池外不保证 | 比较值用==,判断 None 用is |
"abc"和"ABC"不相等 | 字符串比较区分大小写 | 忽略大小写时先调用lower() |
and/or的结果不是 True/False | Python 逻辑运算符返回实际值 | 了解短路特性,避免误判 |
字典用in判断值失败 | in对 dict 只判断键 | 判断值使用"value" in dict.values() |
这里再补充一个网上经常讨论的兼容性问题:非 h5 平台:key不支持逻辑运算符的兼容性问题。
这个问题常见于部分低代码平台或特殊前端运行时。不同平台对运算符的支持范围不一样,有些平台不支持&&、||,有些平台不支持三元运算符,导致同样的表达式在不同环境运行结果不同。作为测试人员,在做兼容性测试时,不能默认所有平台都具备相同的运算符支持能力,要结合目标平台的官方文档,设计最小验证用例做冒烟测试。
7. 最佳实践与工程建议
7.1 测试脚本中不使用魔法数字
不要把6、12、8、16这些校验条件直接散落在代码里。建议定义成常量,或者放在配置文件里:
USERNAME