重庆公司网站seo小广告
在当今的软件开发中,测试的必要性不言而喻。为了确保代码的质量和稳定性,开发者需要一种高效的方式去编写和运行单元测试。Python 提供了一个强大的工具——unittest
。这是一个标准库模块,专为编写和运行测试而设计,帮助开发者减少错误并提高代码的可维护性。
为什么选择 unittest?
- 内置支持: 作为标准库的一部分,
unittest
与 Python 语言的兼容性极高,无需额外安装。 - 功能强大: 提供丰富的断言方法、测试夹具和错误报告,方便调试。
- 易于使用: 结构清晰,便于组织和运行测试。
如何使用unittest
使用 unittest
进行单元测试的基本步骤如下:
- 导入
unittest
模块。 - 创建测试类,并让其继承自
unittest.TestCase
。 - 编写测试方法,方法名需以
test_
开头。 - 使用断言方法,确保输出结果与预期一致。
- 运行测试。
安装和导入
unittest
是 Python 标准库的一部分,所以不需要额外安装,可以直接导入并使用它。
import unittest
示例代码
假设我们有一个简单的函数 add
,我们需要编写测试用例来验证它的正确性。文件存盘为my_math.py:
# my_math.py
def add(a, b):return a + b
现在我们编写测试用例,文件存盘为test_my_math.py:
# test_my_math.py
import unittest
from my_math import addclass TestAddFunction(unittest.TestCase):def test_add_positive_numbers(self):self.assertEqual(add(1, 2), 3)def test_add_negative_numbers(self):self.assertEqual(add(-1, -2), -3)def test_add_mixed_numbers(self):self.assertEqual(add(-1, 2), 1)def test_add_zero(self):self.assertEqual(add(0, 0), 0)def test_add_floats(self):self.assertEqual(add(1.5, 2.5), 4.0)if __name__ == '__main__':unittest.main()
解释
- 导入模块: 导入
unittest
模块和需要测试的函数add
。 - 定义测试类:
TestAddFunction
继承自unittest.TestCase
。 - 编写测试方法: 每个方法名以
test_
开头,用于标识这是一个测试方法。 - 使用断言方法: 使用
assertEqual
方法来验证add
函数的输出是否符合预期。 - 运行测试: 使用
unittest.main()
来运行测试。
运行测试
可以通过以下命令运行测试:
python -m unittest test_my_math.py
或者直接运行包含测试代码的文件:
python test_my_math.py
输出结果
运行测试后,会看到类似以下的输出:
....
----------------------------------------------------------------------
Ran 5 tests in 0.001sOK
这表示所有测试都通过了。如果有测试失败,会看到具体的错误信息。
其它常用断言方法
assertTrue(expr)
: 确保表达式为True
。assertFalse(expr)
: 确保表达式为False
。assertIsNone(expr)
: 确保表达式为None
。assertIsNotNone(expr)
: 确保表达式不为None
。assertIs(expr1, expr2)
: 确保两个表达式相等。assertIsNot(expr1, expr2)
: 确保两个表达式不相等。assertIn(member, container)
: 确保成员在容器中。assertNotIn(member, container)
: 确保成员不在容器中。assertIsInstance(obj, cls)
: 确保对象是某个类的实例。assertNotIsInstance(obj, cls)
: 确保对象不是某个类的实例。
通过这些基本的步骤和方法,我们就可以编写和运行高效的单元测试来确保自己的代码质量。
CNPL解释器的测试:
编辑文件cnpl_interpreter.py
"""
中文同像性编程语言(CNPL)解释器基于主谓宾结构和同像性数据结构的编程语言解释器
"""
import re
import math
import jsonclass CNPLParser:def __init__(self):self.ast = {}def parse(self, tokens):"""将词法单元转换为抽象语法树"""if not tokens:return None# 简单的主谓宾结构解析if len(tokens) >= 3 and tokens[1]['type'] == 'operator':return {'type': 'operation','subject': tokens[0]['value'],'predicate': tokens[1]['value'],'object': ' '.join([t['value'] for t in tokens[2:]]) if len(tokens) > 2 else None}# 处理单操作数函数if len(tokens) == 2 and tokens[1]['type'] == 'operator' and tokens[1]['value'] in ["开方", "正弦", "余弦", "正切"]:return {'type': 'unary_operation','operator': tokens[1]['value'],'operand': tokens[0]['value']}# 默认返回原始token列表return tokensclass CNPLInterpreter:def __init__(self):# 词法单元类型定义self.token_types = {"keywords": ["如果", "否则", "循环", "返回", "抛出", "导入", "从", "函数", "变量", "当", "每次"],"operators": ["加", "减", "乘", "除", "幂", "模", "开方", "正弦", "余弦", "正切"],"delimiters": ["(", ")", "{", "}", "[", "]", ",", ";", ":"],"identifier": r'^[\u4e00-\u9fa5a-zA-Z_][\u4e00-\u9fa5a-zA-Z0-9_]*$',"number": r'^[0-9]+(\.[0-9]+)?$'}# 变量表self.variables = {}# 语法分析器self.parser = CNPLParser()def lexer(self, input_str):"""词法分析器"""tokens = []pos = 0while pos < len(input_str):# 跳过空白字符match = re.match(r'\s+', input_str[pos:])if match:pos += len(match.group())continueif pos >= len(input_str):break# 检查关键字、操作符和分隔符found = Falsefor token_type in ["keywords", "operators", "delimiters"]:for token in self.token_types[token_type]:if input_str[pos:].startswith(token):tokens.append({"type": token_type[:-1], "value": token})pos += len(token)found = Truebreakif found:breakif found:continue# 检查数字(优先检查数字,因为数学函数可能包含数字)match = re.match(self.token_types["number"], input_str[pos:])if match:token = match.group()tokens.append({"type": "number", "value": token})pos += len(token)continue# 检查数学函数(如'开方'等)for op in self.token_types["operators"]:if input_str[pos:].startswith(op):tokens.append({"type": "operator", "value": op})pos += len(op)found = Truebreakif found:continue# 检查标识符match = re.match(self.token_types["identifier"], input_str[pos:])if match:token = match.group()tokens.append({"type": "identifier", "value": token})pos += len(token)continueraise Exception(f"无法识别的字符: {input_str[pos]}")return tokensdef parse_svo(self, code):"""解析主谓宾结构"""parts = code.split(" ")# 处理单操作数的数学函数if len(parts) == 2 and parts[1] in ["开方", "正弦", "余弦", "正切"]:# 先检查操作数是否为数字try:operand = float(parts[0])result = self.execute_operation(parts[0], parts[1], "")if isinstance(result, (int, float)):return float(result)elif isinstance(result, str):try:return float(result)except ValueError:raise Exception("数学函数结果必须是数字")elif isinstance(result, bool):return float(result)elif result is None:return 0.0else:raise Exception(f"无效的数学函数结果类型: {type(result)}")except ValueError:raise Exception("数学函数的操作数必须是数字")if len(parts) < 2:raise Exception("无效的主谓宾结构")subject = parts[0]predicate = parts[1]object_ = " ".join(parts[2:])return self.execute_operation(subject, predicate, object_)def execute_operation(self, subject, predicate, object_):"""执行基本操作"""# 处理条件判断if predicate == "如果":try:condition = self.interpret(subject)if isinstance(condition, bool):if condition:return self.interpret(object_)return Noneelif isinstance(condition, (int, float)):if condition != 0:return self.interpret(object_)return Noneelse:raise Exception(f"条件表达式必须返回布尔值或数字,得到: {type(condition)}")except Exception as e:raise Exception(f"条件判断执行错误: {str(e)}")# 处理循环结构if predicate == "循环" or predicate == "当":try:result = Nonewhile True:condition = self.interpret(subject)if isinstance(condition, bool):if not condition:breakelif isinstance(condition, (int, float)):if condition == 0:breakelse:raise Exception(f"循环条件必须返回布尔值或数字,得到: {type(condition)}")result = self.interpret(object_)return resultexcept Exception as e:raise Exception(f"循环执行错误: {str(e)}")# 处理单操作数函数if predicate in ["开方", "正弦", "余弦", "正切"]:print(f"====执行单操作数函数: {predicate}({subject})")try:subj_num = float(subject)print(f"====操作数转换成功: {subject} -> {subj_num}")result = Noneif predicate == "开方":result = math.sqrt(subj_num)print(f"====开方结果: {result}")elif predicate == "正弦":result = math.sin(subj_num)print(f"====正弦结果: {result}")elif predicate == "余弦":result = math.cos(subj_num)print(f"====余弦结果: {result}")elif predicate == "正切":result = math.tan(subj_num)print(f"====正切结果: {result}")# 确保返回数值类型if isinstance(result, (int, float)):return float(result)elif isinstance(result, str):try:return float(result)except ValueError:raise Exception("数学函数结果必须是数字")elif isinstance(result, bool):return float(result)elif result is None:return 0.0else:raise Exception(f"无效的数学函数结果类型: {type(result)}")except ValueError as e:print(f"====操作数转换失败: {e}")raise Exception("操作数必须是数字")# 转换为数字try:subj_num = float(subject)obj_num = float(object_)except ValueError:raise Exception("操作数必须是数字")# 执行操作if predicate == "加":return subj_num + obj_numelif predicate == "减":return subj_num - obj_numelif predicate == "乘":return subj_num * obj_numelif predicate == "除":return subj_num / obj_numelif predicate == "幂":return subj_num ** obj_numelif predicate == "模":return subj_num % obj_numelse:raise Exception(f"未实现的操作: {predicate}")def execute_homiconic_code(self, code_obj):"""执行同像性代码"""if code_obj["type"] == "代码块":return self.interpret(code_obj["content"])elif code_obj["type"] == "变量":return self.get_variable(code_obj["name"])else:raise Exception(f"未知的代码类型: {code_obj['type']}")def set_variable(self, name, value):"""设置变量"""self.variables[name] = valuedef get_variable(self, name):"""获取变量"""if name in self.variables:return self.variables[name]raise Exception(f"未定义的变量: {name}")def interpret(self, code_block):"""主解释函数"""try:if isinstance(code_block, str):# 处理数学运算表达式if re.match(r'^\d+\s+[加减乘除幂模开方正弦余弦正切]\s+\d+$', code_block) or \re.match(r'^\d+\s+[开方正弦余弦正切]$', code_block):result = self.parse_svo(code_block)print(f"==处理数学运算表达式 code_block:{code_block} 解析结果result: {result} type is:{type(result)}")if isinstance(result, (int, float)):return resulttry:return float(result)except ValueError:if isinstance(result, str) and result.replace('.', '', 1).isdigit():return float(result)raise Exception(f"无法转换为数字的结果: {result}")# 确保数学运算结果返回数值类型if isinstance(result, str) and result.replace('.', '', 1).isdigit():return float(result)elif isinstance(result, (int, float)):return resultelif isinstance(result, bool):return float(result)elif result is None:return 0.0else:raise Exception(f"无法转换为数字的结果: {result}")# 处理纯数字if re.match(r'^\d+$', code_block):return float(code_block)# 先进行词法分析tokens = self.lexer(code_block)# 生成ASTast = self.parser.parse(tokens)# 如果是操作表达式if isinstance(ast, dict) and ast.get('type') in ['operation', 'unary_operation']:return self.execute_ast(ast)# 否则回退到原始解析方式return self.parse_svo(code_block)elif isinstance(code_block, dict):return self.execute_homiconic_code(code_block)else:raise Exception("不支持的代码块类型")except Exception as e:return f"错误: {str(e)}"def execute_ast(self, ast):"""执行AST节点"""if ast['type'] == 'operation':# 处理变量引用subject = self.get_variable(ast['subject']) if ast['subject'] in self.variables else ast['subject']object_ = self.get_variable(ast['object']) if ast['object'] in self.variables else ast['object']return self.execute_operation(subject, ast['predicate'], object_)elif ast['type'] == 'unary_operation':operand = self.get_variable(ast['operand']) if ast['operand'] in self.variables else ast['operand']return self.execute_operation(operand, ast['operator'], "")# 测试代码
if __name__ == "__main__":interpreter = CNPLInterpreter()# 测试基本数学运算print("5加3的结果是:", interpreter.interpret("5 加 3"))print("10减2的结果是:", interpreter.interpret("10 减 2"))print("4乘2的结果是:", interpreter.interpret("4 乘 2"))print("8除2的结果是:", interpreter.interpret("8 除 2"))# 测试同像性数据结构code_obj = {"type": "代码块","content": "5 加 3"}print("同像性代码执行结果:", interpreter.interpret(code_obj))
编辑文件测试文件test_parser.py
"""
CNPL解释器测试文件测试词法分析、语法解析和执行功能
"""
import unittest
from cnpl_interpreter import CNPLInterpreterclass TestCNPLInterpreter(unittest.TestCase):def setUp(self):self.interpreter = CNPLInterpreter()def test_basic_math_operations(self):"""测试基本数学运算"""# self.assertEqual(self.interpreter.interpret("5 加 3"), 8)# self.assertEqual(self.interpreter.interpret("10 减 2"), 8)# self.assertEqual(self.interpreter.interpret("4 乘 2"), 8)# self.assertEqual(self.interpreter.interpret("8 除 2"), 4)# self.assertEqual(self.interpreter.interpret("2 幂 3"), 8)# self.assertEqual(self.interpreter.interpret("10 模 3"), 1)def test_math_functions(self):"""测试数学函数"""x = self.interpreter.interpret("4 开方")print(f"====x is {x}, x.type:{type(x)}")self.assertAlmostEqual(self.interpreter.interpret("4 开方"), 2.0)self.assertAlmostEqual(self.interpreter.interpret("0 正弦"), 0.0)self.assertAlmostEqual(self.interpreter.interpret("0 余弦"), 1.0)self.assertAlmostEqual(self.interpreter.interpret("0 正切"), 0.0)def test_variables(self):"""测试变量操作"""self.interpreter.set_variable("变量一", 5)self.assertEqual(self.interpreter.get_variable("变量一"), 5)code_obj = {"type": "变量", "name": "变量一"}self.assertEqual(self.interpreter.interpret(code_obj), 5)def test_homiconic_structures(self):"""测试同像性数据结构"""code_obj = {"type": "代码块","content": "5 加 3"}self.assertEqual(self.interpreter.interpret(code_obj), 8)if __name__ == "__main__":unittest.main()
执行测试:
python test_parser.py
当然现在还没完全调试好,所以测试有报错:
python test_parser.py
.==处理数学运算表达式 code_block:5 加 3 解析结果result: 8.0 type is:<class 'float'>
.====x is 错误: 无法识别的字符: 4, x.type:<class 'str'>
E.
======================================================================
ERROR: test_math_functions (__main__.TestCNPLInterpreter)
测试数学函数
----------------------------------------------------------------------
Traceback (most recent call last):File "E:\work\cntrae\test_parser.py", line 26, in test_math_functionsself.assertAlmostEqual(self.interpreter.interpret("4 开方"), 2.0)File "e:\py310\lib\unittest\case.py", line 876, in assertAlmostEqualdiff = abs(first - second)
TypeError: unsupported operand type(s) for -: 'str' and 'float'----------------------------------------------------------------------
Ran 4 tests in 0.009sFAILED (errors=1)
总结:
通过使用 unittest
模块,Python 开发者可以有效地为他们的代码编写测试,确保功能实现的正确性和代码的健壮性。无论是初学者还是经验丰富的开发者,掌握 unittest
都能为软件开发工作带来极大的帮助。希望本文能够激发你使用 unittest
来提升代码质量的兴趣!
如果你还有其他主题或具体方向想要探讨,随时告诉我!