Unittest单元测试

单元测试的定义

什么是单元测试

单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数或者类,一般是开发来做的,按照测试阶段来分,就是单元测试、集成测试、系统测试以及验收测试。

为什么要做单元测试

  • 单元测试之后,才是集成测试,单个单个的功能模块测试通过之后,才能把单个功能模块集成起来做集成测试,为了从底层发现bug,单元测试时可以减少合成后出现的问题。
  • 越早发现bug越好,这样可以早点发现问题,不然问题累计到后面,很可能会因为一个做错了而导致整个模块甚至更大范围的推倒重来,对于时间和经费来说,是非常浪费的!
  • 对于测试来说,单元测试就是为了执行用例,输入测试数据–>输出测试结果

unittest框架及原理

unittest单元测试框架是python自带的一套测试框架,不需要下载。

unittest框架最核心的四个概念:

  • test case:就是我们的测试用例,unittest中提供了一个基本类TestCase,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例;unittest中测试用例方法都是以test开头的,且执行顺序会按照方法名的ASCII值排序。
  • test fixure:测试夹具,用于测试用例环境的搭建和销毁。即用例测试前准备环境的搭建(SetUp前置条件),测试后环境的还原(TearDown后置条件),比如测试前需要登录获取token等就是测试用例需要的环境,运行完后执行下一个用例前需要还原环境,以免影响下一条用例的测试结果。
  • test suite:测试套件,用来把需要一起执行的测试用例集中放到一块执行,相当于一个篮子。我们可以使用TestLoader来加载测试用例到测试套件中。
  • test runner:用来执行测试用例的,并返回测试用例的执行结果。它还可以用图形或者文本接口,把返回的测试结果更形象的展现出来,如:HTMLTestRunner。

unittest的断言

在python基础中,我们有讲过一个assert断言,使用方法比较简单,即assert 表达式, 提示信息,而unittest框架中也提供了一个自带的断言方式,主要有以下几种:

unittest断言
方法 检查
assertEqual(a, b,msg=None) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) Bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a,b)
assertNotIsInstance(a, b) not isinstance(a,b)

如果断言失败即不通过就会抛出一个AssertionError断言错误,成功则标识为通过,以上几种方式都有一个共同点,就是都有一个msg参数(表中只列了一个,其实都有),默认是None,即msg = None,如果指定msg参数的值,则将该信息作为失败的错误信息返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
self.assertEqual('自动化测试_百度搜索', self.driver.title, msg='错误提示信息') # 等于
self.assertIn('自动化测试', self.driver.title, msg='错误提示信息') # 包含
self.assertNotEqual('自动化', self.driver.title) # 不等于

self.assertAlmostEqual() # 约等于
self.assertGreater() # 大于
self.assertGreaterEqual() # 大于等于
self.assertLess() # 小于
self.assertLessEqual() # 小于等于

self.assertDictEqual() # 字典
self.assertListEqual() # 列表
self.assertTupleEqual() # 元组
self.assertSetEqual() # 集合

TestCase测试用例

编写测试用例前,我们需要建一个测试类继承unittest里面的TestCase类,继承这个类之后我们才是真正的使用unittest框架去写测试用例,编写测试用例的步骤如下:

  • 导入unittest模块
  • 创建一个测试类,并继承unittest.TestCase()
  • 定义测试方法,方法名必须以test开头
  • 调用unittest.main()方法来运行测试用例,unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行
  • 执行顺序会按照方法名的ASCII值排序,即 0-9,A-Z,a-z

TestFixure测试夹具

unittest的测试夹具有两种使用方式,一种是以测试方法为维度的setUp()tearDown(),一种是以测试类为维度的setUpClass()tearDownClass()。以注册功能为例,但这个注册代码比较简单,没有真正需要用到测试夹具的地方,因此这只是个用法演示。

  • setUp前置条件:在每一条测试用例执行前执行
  • tearDown后置条件:在每一条测试用例执行后执行
  • setUpClass前置条件:在测试类执行前执行
  • tearDownClass后置条件:在测试类执行后执行

注意:

  • setupClass和tearDownClass需要@classmethod修饰
  • @classmethod指明这是个类方法以类为维度去执行的

TestSuite测试套件

unittest.TestSuite()类来表示一个测试用例集,把需要执行的用例类或模块存到一起,常用的方法如下:

  • unittest.TestSuite()

  • addTest():添加单个测试用例方法

  • addTest([..]):添加多个测试用例方法,方法名存在一个列表

  • unittest.TestLoader() 或unittest.defaultTestLoader

  • loadTestsFromTestCase(测试类名):添加一个测试类

  • loadTestsFromModule(模块名):添加一个模块

  • discover(测试用例的所在目录):指定目录去加载,会自动寻找这个目录下所有符合命名规则的测试用例

TestRunner执行用例

test runner顾名思义就是用来执行测试用例的,并且可以生成相应的测试报告。测试报告有两种展示形式,一种是text文本,一种是html格式。

html格式的就是HTMLTestRunner了,HTMLTestRunner是 Python 标准库的 unittest 框架的一个扩展,它可以生成一个直观清晰的 HTML 测试报告。使用的前提就是要下载 HTMLTestRunner.py,下载完后放在python的安装目录下的scripts目录下即可。

  • unittest.TextTestRunner()

  • run(测试套件)

  • HTMLTestRunner.HTMLTestRunner(stream=sys.stdout, verbosity=1, title=None, description=None)

    • run(测试套件)
  • 参数:

    • stream:指定输出的方式
    • title:测试报告的标题
    • description:报告中要显示的面熟信息
    • verbosity :表示测试报告信息的详细程度,一共三个值,默认是2
    • 0 (静默模式):你只能获得总的测试用例数和总的结果,如:总共100个 失败10 成功90
    • 1 (默认模式):类似静默模式,只是在每个成功的用例前面有个. 每个失败的用例前面有个F
    • 2 (详细模式):测试结果会显示每个测试用例的所有相关的信息

skip规则设定

在执行测试用例时,有时候有些用例是不需要执行的,那我们怎么办呢?难道删除这些用例?那下次执行时如果又需要执行这些用例时,又把它补回来?这样操作就太麻烦了。

unittest提供了一些跳过指定用例的方法。

  • @unittest.skip(reason):强制跳转。reason是跳转原因
  • @unittest.skipIf(condition, reason):condition为True的时候跳转
  • @unittest.skipUnless(condition, reason):condition为False的时候跳转
  • @unittest.expectedFailure:如果test失败了,这个test不计入失败的case数目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import unittest

class TestCase_Skip(unittest.TestCase):

def test_01(self):
print('test01')

@unittest.skip('不执行') # 无条件跳过测试
def test_02(self):
print('test02')

@unittest.skipIf(1 < 2, '成立不执行') # 条件为true的时候跳过测试
def test_03(self):
print('test03')

@unittest.skipUnless(1 > 2, '不成立不执行') # 条件为false的时候跳过测试
def test_04(self):
print('test04')

@unittest.expectedFailure # 测试标记为失败
def test_05(self):
print('test05')


if __name__ == '__main__':
unittest.main()

unittest结合ddt数据驱动实战

1、创建Python Package命名为data

login_data.yml:

1
2
3
4
5
6
7
8
9
-
username: sysadmin
password: SysAdmin123
-
username: zcadmin
password: ZcAdmin456
-
username: zcleader
password: ZcLeader789

ceshi_data.yml:

1
2
3
4
5
6
7
8
9
10
11
12
-
- 小明
- 18
- 打篮球
-
- 小王
- 19
- 打游戏
-
- 小刘
- 21
- 敲代码

2、创建Python Package命名为test_case

test_login.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from selenium import webdriver
import unittest
from ddt import ddt, file_data
from time import sleep

@ddt()
class Test_login(unittest.TestCase):

def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get("http://192.168.68.52/asset/logon")

@file_data("../data/login_data.yml")
def test01(self, username, password):
self.driver.find_element_by_id("loginName").send_keys(username)
self.driver.find_element_by_id("password").send_keys(password)
self.driver.find_element_by_class_name("btn").click()
sleep(2)

def tearDown(self) -> None:
self.driver.quit()


if __name__ == '__main__':
unittest.main()

test_ceshi.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import unittest
from ddt import ddt, file_data

@ddt()
class Test_ceshi(unittest.TestCase):

def setUpClass(cls) -> None:
print("setUpClass")

@file_data("../data/ceshi_data.yml")
def test_01(self, value):
print(value)

def tearDownClass(cls) -> None:
print("setUpClass")


if __name__ == '__main__':
unittest.main()

3、创建Python Package命名为run_case

run_case.py:

1
2
3
4
5
6
7
8
9
10
import unittest
# 确定测试用例文件路径
test_dir = "../test_case";
# 将用例添加到测试套件中
# discover = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')
discover = unittest.TestLoader().discover(test_dir, pattern='test*.py')
# 使用unittest自带执行方式
runner = unittest.TextTestRunner()
# 执行测试套件中的测试用例
runner.run(discover)

4、创建Python Package命名为report

report包用于存放HTML测试报告

在run_case:run_case_html.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import unittest
import time
import HTMLTestRunner
# 确定测试用例文件路径
test_dir = "../test_case";
# 将用例添加到测试套件中
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')
# 指定测试报告存放位置
report_dir = "../report"
# 给测试报告命名
now_time = time.strftime("%Y-%m-%d %H-%M-%S")
report_filename = report_dir + "/" + now_time + "_report.html";
# 创建并读写文件
stream = open(report_filename, "wb")
# 使用第三方插件执行测试用例生成测试报告
runner = HTMLTestRunner.HTMLTestRunner(stream=stream, verbosity=2, title="测试报告", description="登录测试");
# 执行测试套件中的测试用例
runner.run(discover)
# 关闭文件
stream.close()

5、HTML测试报告结果