2022年 11月 5日

python编写测试用例

开发一个系统,在开发代码时,测试是必不可少的,这里简单总结一下该怎么编写测试用例

例子:开发一个某系统,前端想要查询某个模块的数据,或着想对某个模块的数据进行增删改查,需要将参数信息传给后端,后段会有一个验证模块,验证传输过来的参数信息是否符合规范,不符合则抛异常,符合则按照前端的需求,进入相应的模块函数完成查询或者增删改查,之后将结果返回给前端。

大致流程:设置表格规范–>编写测试用例–>通过测试用例的报错,编写逻辑代码

1、设置规范

该部分的意义是,对数据库中的表设置规范,该部分就是上述所说的验证,对前端传来的参数进行验证,不符合规范则抛异常

schemas.py

  1. from pydantic import BaseModel
  2. class MemuAdd(BaseModel):   #对MemuAdd表里的字段进行设置
  3. directory_id: PositiveInt = Field(title="目录ID")        #设置directory_id字段为正整数
  4. memu_id: PositiveInt = Field(title="菜单ID")    #设置memu_id字段为正整数
  5. display_index: int = Field(title="显示序号")    #设置display_index字段为整数

这样一个规范就设置好了,前端传来的参数,必须先经过这里进行验证。

2、测试1

我们是写后端的,没有前端传来参数,自己该怎么测试代码是否正确,python有专门的模块用来进行单元测试,就是unittest模块

TestCase类:一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码 (run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。

test_schema_menu.py

  1. import unittest,schemas
  2. class TestCase(unittest.TestCase):
  3. def setUp(self):
  4. self.param={
  5. "directory_id":1,
  6. "display_index":1,
  7. "memu_id":1
  8. }
  9. def test_add(self):
  10. schemas.MemuAdd(**self.param) #将上述参数传递给schemas.MemuAdd,来验证传递的参数的规格符不符合要求
  11. def test_add_directory_id(self):
  12. self.param["directory_id"]=["1","2"]
  13. with self.assertRaises(ValueError): #故意将一个参数修改不规范,然后传递给schemas.MemuAdd,然后看是否能将异常捕获到
  14. schemas.MemuAdd(**self.param)
  15. def test_add_display_index(self):
  16. self.param["display_index"]=["1","2"]
  17. with self.assertRaises(ValueError):
  18. schemas.MemuAdd(**self.param)
  19. def test_add_memu_id(self):
  20. self.param["memu_id"]=["1","2"]
  21. with self.assertRaises(ValueError):
  22. schemas.MemuAdd(**self.param)

上述代码咱们看test_add_directory_id(self)函数,里面故意将一个参数修改成不规范的,然后通过with self.assertRaises(ValueError)捕获异常,假如咱们不添加捕获异常代码,然后进行测试,会直接报错:

  1. ERROR: test_add_directory_id (tests.test_schema_menu_test.TestCase)
  2. ----------------------------------------------------------------------
  3. Traceback (most recent call last):
  4. File "/app/tests/test_schema_menu_test.py", line 17, in test_add_directory_id
  5. schemas.MemuAdd(**self.param)
  6. File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
  7. pydantic.error_wrappers.ValidationError: 1 validation error for MemuAdd
  8. directory_id
  9. 参数类型错误 (type=value_error)

有些人就想,这不挺好吗,报错信息一目了然。虽然报错信息一目了然,但是要记住,报错是要给前端用户去看,告诉他输入参数有误,而不是给后端开发人员看的,咱们需要将报错信息捕获到然后传给前端。所以需要加入with self.assertRaises(ValueError)捕获异常。

这样一个简单的测试用例就写完了,上述只是对一些输入参数的类型进行一个判断,下面写一个对数据库进行操作的测试用例

3、对数据库操作的测试用例

首先我们先写测试用例,然后根据测试用例的报错,来完善逻辑代码,测试用例的代码,在下方,均以作出解释。

  1. decorator = tests.decorator_create_object(class_name=BaseMenu) #封装一个类,这个不用太在意,对测试影响不大,所以就不说这个了,decorator_create_object是我本地的一个函数,不是模块。
  2. class TestCase(TestCase):
  3. def setUp(self):
  4. tests.clear_table(models.MenuModel) #初始化表格,将表格数据清空
  5. records = [
  6. models.MenuModel(id=8, display_index=1, menu_id=1,directory_id=1,create_time=datetime.datetime.now(),
  7. update_time=datetime.datetime.now() + datetime.timedelta(days=1)),
  8. models.MenuModel(id=9, display_index=2, menu_id=2,directory_id=2,create_time=datetime.datetime.now(),
  9. update_time=datetime.datetime.now() + datetime.timedelta(days=1)),
  10. models.MenuModel(id=10, display_index=3, menu_id=3,directory_id=3,create_time=datetime.datetime.now(),
  11. update_time=datetime.datetime.now() + datetime.timedelta(days=1)),
  12. ]
  13. tests.insert(records) #将上述的参数信息写入表格
  14. self.param = { #自定义一个参数,方便一会测试增加数据
  15. "directory_id":4,
  16. "display_index":4,
  17. "memu_id":4
  18. }
  19. def tearDown(self) -> None: #这个上面介绍TestCase说过,这里就不说了
  20. ...

上述是一部分代码,主要是建立一个测试环境,下面我们来测试增加数据这个用例。

  1. #增加
  2. @decorator
  3. def test_add(self, *, object):
  4. param=schemas.MemuAdd(**self.param) #这个就是上述第一部分说的,判断传来的参数类型是否符合要求
  5. result=object.add(param) #这个就是调用逻辑代码里的add函数,object跟上面的decorator = tests.decorator_create_object(class_name=BaseMenu)有关系,所以这里不做过多解释,只要知道这里是调用逻辑代码里的add函数就行
  6. self.assertEqual(0,result["code"],"添加失败") #assertEqual 用来判断 参数1和参数2是否相等,不相等则输出参数3,相等不做操作,这里主要看是否添加成功,如果添加成功,逻辑代码就会返回code=0,如果不成功,就会返回其他值
  7. verify_db=tests.fetch_by_id(models.MenuModel,result["data"]["id"]) #这行代码主要是去数据库查数,看数据是否真正插入成功,这里fetch_by_id也是我本地代码,不是一个模块,这个代码我将放到下方
  8. self.assertEqual(4,verify_db.directory_id,"目录ID添加失败") #这里就是判断查出来的数据是否和刚刚自己想要插入的数据是否相同
  9. self.assertEqual(4,verify_db.display_index,"显示序号添加失败")
  10. self.assertEqual(4,verify_db.menu_id,"菜单ID添加失败")
  11. #测试输入一个不允许重复的字段的数值,看是否会报出“不允许重复”
  12. @decorator
  13. def test_add_memu_id(self,*,object):
  14. self.param["memu_id"]=1
  15. param=schemas.MemuAdd(**self.param)
  16. result=object.add(param)
  17. self.assertEqual(201002,result["code"],"菜单ID已存在,不能重复添加")
  18. --------------------------------------------------------------------------
  19. def fetch_by_id(model, id):
  20. with models.session_maker() as session:
  21. return session.query(model).filter_by(id=id).first()

上述的代码执行一遍后,输出如下,因为逻辑代码没写,所以def test_add()这个函数最后code默认返回0,所以没报错。主要是看def test_add_memu_id(self,*,object),看下面报错显示出来了,报“AssertionError: 201002 != 0 : 菜单ID已存在,不能重复添加“,我们需要在逻辑代码里把这个异常捕获,然后传递给前端。下面就按照测试用例的报错去写逻辑代码

  1. FAIL: test_add_memu_id (tests.test_class_menu_test.TestCase)
  2. ----------------------------------------------------------------------
  3. Traceback (most recent call last):
  4. File "/app/tests/__init__.py", line 59, in wrapper
  5. result = function(*args, **kwargs)
  6. File "/app/tests/test_class_menu_test.py", line 73, in test_add_memu_id
  7. self.assertEqual(201002,result["code"],"菜单ID已存在,不能重复添加")
  8. AssertionError: 201002 != 0 : 菜单ID已存在,不能重复添加

逻辑代码: 

  1. from .ssobaseclass import SSOBaseClass
  2. model = MenuModel #表名称
  3. class BaseMenu(SSOBaseClass):
  4. @catch_exception
  5. def add(self, param:schemas.MemuAdd):
  6. directory_id=param.directory_id
  7. memu_id=param.memu_id
  8. display_index=param.display_index
  9. session=self.context.session #这个是创建会话,用于数据库操作,具体可以看我文章里有一个专门介绍python对数据库操作
  10. row_memu_id=session.query(model).filter(model.menu_id==memu_id).first() #这个就是在数据库中进行搜索
  11. if row_memu_id: #如果搜索出来有值,说明数据库里有这个菜单ID,所以本次不能去添加,添加的话就重复了,所以我们通过下面的代码进行抛出异常。
  12. raise SSOException(201002,"菜单ID不能重复")
  13. ##下面这一堆就是写入数据库操作,具体可以看我文章里有一个专门介绍python对数据库操作 Menu=model(directory_id=directory_id,menu_id=memu_id,display_index=display_index)
  14. Menu.create_time = datetime.datetime.now()
  15. Menu.update_time = datetime.datetime.now()
  16. session.add(Menu)
  17. session.commit()
  18. # 记录操作日志
  19. self.context.add_oplog(event_type="添加菜单", content=f"添加了菜单:{Menu.menu_id}", target_id=Menu.id)
  20. return{
  21. "id":Menu.id
  22. }

逻辑代码写完以后,我们在执行一遍测试用例,就会发现没有报错,说明报错被我们捕获了,这就说明我们成功了,捕获到就可以返回给前端了

  1. root@3f6fe561aa49:/app# ./unittest.sh
  2. ...............
  3. ----------------------------------------------------------------------
  4. Ran 15 tests in 0.294s
  5. OK

这样一个大致的测试用例就完结了,也不仅仅只有测试用例,也写到怎么写逻辑代码,可能内容比较多,写的不是很明确,之后还得需要读者慢慢消化,慢慢理解,其实理解了流程,其他都好说,流程不知道,看代码就是一塌糊涂。