注意:
1、所有的测试用例,有两种方式加载成测试套件:
(1)使用TestSuite一个个加;(2)使用TestLoader,一次性全部加进去。
2、所有的测试用例,加载成测试套件后有两种方式执行:(执行测试套件)
(1)如果要生成文本样式的测试报告,就用TextTestRunner来执行。(unittest自带)
(2)如果要生成html样式的测试报告,就用HTMLTestRunner来执行。(第三方插件)
3、UnitTest是python自带的一个单元测试框架,用来做单元测试。
核心要素:
(1)TestCase——定义测试类的时候要继承的父类,然后在这个类里编写方法(一个方法就是一条测试用例)。注意:类里面的函数称呼的时候不叫函数,而是要叫方法。
(2)TestSuite——测试套件,通过addTest方法,把测试用例一条一条加到测试套件中。
(3)TextTestRunner——执行测试用例,生成的是文本格式的测试报告。它是unittest中自带的,一般测试要生成测试报告的话,就不用它,而是会用一个第三方插件,HTMLTestRunner,它也是执行测试用例,生成的是html格式的测试报告,但它不是unittest自带的,使用它的时候,要先去网上下载一个HTMLTestRunner.py文件,然后把它放到自己电脑里python安装目录下的Lib\site-packages目录下。后期项目代码中使用from...import来进行调用。
from HTMLTestRunner import HTMLTestRunner
(4)TestLoader—— 也算是一个套件,它是一次性的把所有文件中的测试用例都加到测试套件中。
(5)Fixture——测试固件,用来在测试执行前和执行后执行一些代码。有三个控制级别:方法级、类级、模块级。比如,测试前做一些环境方面的初始化工作,测试结束后做一些收尾工作。
除此之外,在进行参数化的时候,由于unittest框架本身不支持参数化,所以要进行参数化需要安装一个第三方插件parameterized。安装方法:win+r输入cmd进入dos窗口,切换到python的安装目录,输入python -m pip install parameterized即可进行下载。
小结:TestCase、TestSuite、TextTestRunner、TestLoader这四个都是unittest自带的,所以使用的时候都要写unittest. 而HTMLTestRunner、参数化是第三方插件,所以使用时候不用写unittest.。
4、参数化的使用场景:就一个功能点,我们要输入好多的数据去测试它,来找它的问题,这个时候就可以用参数化,这样能够减少冗余代码。
一、TestCase
就是测试用例的意思。只要定义的类继承自unittest.TestCase这个父类,那么这个类中每一个以test开头的方法就是一个测试用例。
TestCase的使用步骤:
第一步:导包,导入unittest模块。
第二步:定义一个类,这个类要继承unittest.TestCase类。类的命名要使用大驼峰命名法。
第三步:定义类的方法,方法名必须以test开头。类中的每一个方法就代表一个测试用例。
例如:定义一个函数,用来实现加法运算,并对该函数进行测试。新建一个py文件,命名为:testcase_01.py。
import unittest # 导包
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase): # 定义测试类。注意:【测试类必须要继承unittest.TestCase】
def test01(self): # 定义测试方法,方法名必须以test开头。
print(my_sum(1, 2)) # 注意:一个测试方法就代表一个测试用例,所以test01是一条测试用例
def test02(self): # 这也是一条测试用例
print(my_sum(3, 3))
# 运行:点击鼠标右键,选择【Unittests in 这个py文件】
结果:
Ran 2 tests in 0.000s
OK
3
6
进程已结束,退出代码 0
# 说明这两条测试用例通过了
注意:如果点击鼠标右键,没有运行【Unittests for 这个py文件】这个选项怎么办?
上面例题中是在一个.py文件里,执行了两个测试用例,那么如果有多个.py文件怎么办?或者说,测试用例放在了不同的.py文件里,那么这些测试用例又该怎么执行?使用测试套件TestSuite或TestLoader。
二、TestSuite——测试套件,用来打包测试用例
用来把多个测试用例集合到一起,整合成一个测试套件。
TestSuite使用步骤:
第一步:导包,导入unittest模块,以及导入其它包含测试用例的.py文件。这里的py文件的命名规则与变量名的命名规则相同。
第二步:为unittest.TestSuite这个类,实例化一个对象。
第三步:调用对象的addTest方法,来添加测试用例。
利用addTest方法添加用例有两种方式:
第一种:对象名.addTest(py文件名.类名("方法名"))—— 一次只能添加类里面的一条测试用例。
第二种:对象名.addTest(unittest.makeSuite(py文件名.类名))—— 一次导入一个类里面所有的测试用例。即:指定类,然后将类里面所有的以test开头的方法都添加进去。但这种方法不咋样,写的时候,pycharm也会有警告提示。
下面分别对这两种方式进行演示。
第一种:这种方式代码量比较大,比较冗余,不方便。适合添加指定的、少量的测试用例。
以刚才编写的测试加法运算的那俩用例为例。它们所在文件名为:testcase_01.py。
现在在同级目录下,新建一个py文件,命名为:study_testsuite.py,并写入以下代码。
import unittest
import testcase_01 # 导入testcase_01.py这个文件
suite = unittest.TestSuite() # 为类TestSuite实例化一个对象,名叫suite
# 根据对象名.addTest(测试用例所在的文件名.类名("方法名"))来添加测试用例
suite.addTest(testcase_01.MyTest("test01")) # 添加第一条测试用例
suite.addTest(testcase_01.MyTest("test02")) # 添加第二条测试用例
第二种:不咋用。
import unittest
import testcase_01 # 导入testcase_01.py这个文件
suite = unittest.TestSuite() # 为类TestSuite实例化一个对象,名叫suite
suite.addTest(unittest.makeSuite(testcase_01.MyTest)) # 把testcase_01.py文件的类名添加进行
至此,两种方式添加testcase_01.py中的两个测试用例(也就是类里面的方法)已经添加完毕。
三、TextTestRunner——执行测试套件中的测试用例。
上面两种方式已经将test01、test02这两个测试用例添加到测试套件中。现在开始执行。
使用步骤:
第一步:为TextTestRunner这个类实例化一个对象。
第二步:调用对象的run方法,去执行测试套件。
格式:对象名.run(套件名)
例如:接着上面的代码,把下面代码写入study_testsuite.py中。
runner = unittest.TextTestRunner() # 为TextTestRunner这个类实例化一个对象,名叫runner
runner.run(suite) # 调用对象的run方法,参数为套件名称
所以,study_testsuite.py完整代码是:
import unittest
import testcase_01 # 导入testcase_01.py这个文件
suite = unittest.TestSuite() # 为类TestSuite实例化一个对象,名叫suite
# 根据对象名.addTest(测试用例所在的文件名.类名("方法名"))来添加测试用例
suite.addTest(testcase_01.MyTest("test01")) # 添加第一条测试用例
suite.addTest(testcase_01.MyTest("test02")) # 添加第二条测试用例
# 执行测试套件
runner = unittest.TextTestRunner() # 为TextTestRunner这个类实例化一个对象,名叫runner
runner.run(suite) # 调用对象的run方法,参数为套件名称
# 鼠标右键【运行study_testsuite.py】
结果:
3
6
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
进程已结束,退出代码 0
# 或显示
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
3
6
进程已结束,退出代码 0
四、TestLoader
TestSuite是用来将测试用例进行打包,整合成一个测试套件。但是它的两种添加用例的方式都不太好,尤其是当测试用例在多个py文件中的时候,会非常繁琐、代码量大、重复的代码比较多,所以都不常用。用的比较多的是TestLoader。
TestLoader也是用来把测试用例整合到测试套件中,相比TestSuite,TestLoader会更加的方便。它可以从指定的目录中,查找指定的py文件中所有的测试用例(也就是类中以test开头的所有方法),然后自动的加载到测试套件中。也就是说,它可以把满足条件的测试用例一次性的全部封装成测试套件。
使用步骤:
用TestLoader对象的discover方法来自动查找py文件,并自动加载文件里的测试用例到测试套件中。
格式:unittest.TestLoader().discover("从哪里找py文件","指定要查找的py文件的文件名")
当然也可以使用通配符。
例如:suite=unittest.TestLoader().discover("./","test*.py")
./表示从当前目录开始查找,./也可以只写个点,即.也表示当前目录。
./或者. 都表示当前目录
/表示根目录
..表示上级目录
test*.py表示查找以test开头的所有.py文件。
总结起来就是:从当前目录下,查找所有以test开头的.py文件中所有的测试用例,然后把它们一起加载到测试套件中。
例如:在当前目录下新建study_taseloader.py文件,写入以下内容,并执行。
import unittest
suite = unittest.TestLoader().discover("./", "test*.py")
# 当前目录下查找以test开头的.py文件中所有的测试用例,只要找到就生成一个测试套件suite
runner = unittest.TextTestRunner()
runner.run(suite)
# 鼠标右键运行
结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
3
6
进程已结束,退出代码 0
从上面代码中可以看到:同样是把test01、test02这两个测试用例添加到测试套件,然后再运行,上面这种方式显得更加方便。但如果只是把一部分用例拿出来去执行,显然就不能用上面这种方式了,得用TestSuite 。
小结:TestSuite VS TestLoader
1、TestSuite
可以添加指定的测试用例。比如,一个py文件里有10个测试用例,而我们只想执行俩,那就把要执行的这俩添加进去执行即可。也就是说,你想执行哪个用例就把哪个用例加进去。
2、TestLoader
不能添加指定的测试用例,只要满足查找的条件,那么对应文件里的所有用例就会全部加进去。
当然,如果不用通配符*进行模糊匹配也可以,这时候查找的就是这一个具体文件,然后把这个文件里面所有的测试用例都加载进去,再去执行。但是,倘若这个文件里的用例我们不想全部执行,而是想有选择的性的挑几个去执行,此时,这个方法就不行了。所以,TestLoader适用于执行所有py文件中的所有测试用例,而TestSuite适用于执行指定的几个测试用例。
五、Fixture
它是unittest框架的固件,在测试开始前和测试结束后调用指定的函数或方法,来进行一些操作。
为什么叫调用指定的函数或方法?因为类级、方法级因为是要写在类里面,所以就方法,而模块级是写在类的外面所以叫函数。
setUpxxx:一般是做初始化工作,tearDownxxx是在测试结束后做一些收尾的工作。
比如,在测试网站的时候会,第一步是要先打开网站,这个就可以写到setUp里,最后一步是关闭网站,就可以写到tearDown里。
fixture的控制级别:方法级、类级、模块级。
(1)方法级:setUp、tearDown,是在每个方法执行前和执行后自动调用。
(2)类级:setUpClass、tearDownClass,是在每个类的执行前和执行后自动调用。注意要用@classmethod进行修饰。
(3)模块级:一个py文件就是一个模块。setUpModule、tearDownModule,每个py文件执行前和执行后自动调用。
1、方法级:setUp、tearDown
setUp——测试用例执行前,自动被调用。
tearDown——测试用例执行后,自动被调用。
有多少个测试用例,那么它们就会被自动调用多少次。即:每个方法执行前后都会自动调用一次。既然是方法级别,那么在定义的时候就要写到类里面去(要写到测试用例所在的class中),至于写在测试用例上面还是下面都可以,结果都一样。例如下面的代码中,就把它写到了测试用例test02的下面。
例如:新建一个study_fixture.py文件,内容如下:定义两个方法级的fixture。即:setUp、tearDown。然后执行代码。
import unittest
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase): # 注意:【测试类必须要继承unittest.TestCase】
def test01(self): # 定义测试方法,方法名必须以test开头。
print(my_sum(1, 2)) # 注意:一个测试方法就是一个测试用例,所以test01是一条测试用例
def test02(self):
print(my_sum(3, 3))
def setUp(self): # 方法级别
print("setUp被调用了")
def tearDown(self): # 方法级别
print("tearDown被调用了")
# 鼠标右键【运行Unittests in study_fixture.py】
结果:
setUp被调用了
3
tearDown被调用了
setUp被调用了
6
tearDown被调用了
要执行上面这段代码有两种方式:
(1)直接在unittest框架里运行。步骤:在文件的最后敲一个空行出来,光标点到这,然后鼠标右键【运行Unittests in study_fixture.py】。
(2)专门写个py文件来执行它。做法:新建一个py文件,然后在这个文件里添加用例再执行。学习阶段,为了方便找这俩文件,可以把这个文件建在跟上面代码同级目录上。例如:新建一个名为run_fixture.py的文件,内容如下:
import unittest
suite = unittest.TestLoader().discover(".", "study_fixture.py") # 当前目录下,把study_fixture.py里的所有测试用例加载到测试套件
runner = unittest.TextTestRunner() # 创建TextTestRunner类的实例
runner.run(suite) # 调用run方法
#运行:鼠标右键 run run_fixture.py
结果:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/one_study_fixture/run_fixture.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
setUp被调用了
3
tearDown被调用了
setUp被调用了
6
tearDown被调用了
进程已结束,退出代码 0
两种方式结果都是一样的,代码中,有两个测试用例,分别是test01、test02,定义了方法级别的fixture那就是在每个方法执行前、执行后各执行一次,setUp、tearDown。
2、类级——setUpClass、tearDownClass
不管类里面有多少方法,一个类执行前后自动调用一次。
既然是类级别,那么在定义的时候就要写到类里面去,至于写在测试用例上面还是下面都可以,结果都一样,需要注意的是,类方法需要用@classmethod来进行修饰,且第一个参数是cls。
例如:现在把study_fixture.py这个文件内容改了,把方法级的改成类级别的。
import unittest
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase): # 注意:【测试类必须要继承unittest.TestCase】
@classmethod # 类方法
def setUpClass(cls): # 这是类级别的fixture
print("setUpClass被调用了")
@classmethod
def tearDownClass(cls): # 这是类级别的fixture
print("tearDownClass被调用了")
def test01(self): # 定义测试方法,方法名必须以test开头。
print(my_sum(1, 2)) # 注意:一个测试方法就是一个测试用例,所以test01是一条测试用例
def test02(self):
print(my_sum(3, 3))
用第二种方式执行下,结果如下:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/one_study_fixture/run_fixture.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
setUpClass被调用了
3
6
tearDownClass被调用了
进程已结束,退出代码 0
3、模块级——setUpModule、tearDownModule
一个py文件就是一个模块。模块在执行前后自动调用一次。也就是说,整个模块只会运行一次setUpModule、tearDownModule。既然是模块级别那就不能写类里了,得写在类的外面,至于写类的上面还是下面都可以,结果都是一样的,但为了更直观更方面查看,通常是写在文件最上面,也就是类的上面。
例如:现在把study_fixture.py这个文件内容改了,把类级的改成模块级别的。
import unittest
def setUpModule(): # 模块级别
print("setUpModule自动调用了")
def tearDownModule(): # 模块级别
print("tearDownModule自动调用了")
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase): # 注意:【测试类必须要继承unittest.TestCase】
def test01(self): # 测试用例名必须以test开头。
print(my_sum(1, 2)) # 注意:一个测试方法就是一个测试用例,所以test01是一条测试用例
def test02(self):
print(my_sum(3, 3))
再次执行run_fixture.py,结果如下:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/one_study_fixture/run_fixture.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
setUpModule自动调用了
3
6
tearDownModule自动调用了
进程已结束,退出代码 0
4、所有级别放一起演示
为了更好的展示三者的区别,现在把三种级别都加上,进行演示。
例如:现在把study_fixture.py这个文件内容改了,把所有级别的fixture都加上。
import unittest
def setUpModule(): # 模块级别
print("setUpModule自动调用了")
def tearDownModule():
print("tearDownModule自动调用了")
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase): # 注意:【测试类必须要继承unittest.TestCase】
@classmethod
def setUpClass(cls): # 类级别
print("setUpClass被调用了")
@classmethod
def tearDownClass(cls):
print("tearDownClass被调用了")
def setUp(self): # 方法级别
print("setUp被调用了")
def tearDown(self):
print("tearDown被调用了")
def test01(self):
print(my_sum(1, 2))
def test02(self):
print(my_sum(3, 3))
执行run_fixture.py,结果如下:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/one_study_fixture/run_fixture.py
setUpModule自动调用了
setUpClass被调用了
setUp被调用了
3
tearDown被调用了
setUp被调用了
6
tearDown被调用了
tearDownClass被调用了
tearDownModule自动调用了
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
进程已结束,退出代码 0
可以发现:
测试前执行的顺序是:先调用模块级,再调用类级、最后调用方法级。
测试结束后调用的顺序是:先调用方法级、再调用类级、最后才调用模块级。
这个很好理解,首先它们是在同一个文件,那么同一个文件内,代码的执行顺序都是从上到下依次执行。所以初始化时候就会先调用模块级,然后是类级、方法级;结束的时候,相反。
六、断言
让程序来判断测试用例执行的结果是否符合预期。也就是说,让程序自动的帮我们判断代码执行的结果是否正确。
注意:使用断言的时候,这个类,必须要继承unittest.TestCase这个父类,例如:
class MyTest(unittest.TestCase):
这样才可以使用unittest里的断言方法。
unittest常用的断言方法:
(1)assertEqual(参数1,参数2)
这两个参数用来存放实际结果和预期结果。哪个写在前面,哪个写在后面,都可以。
如果参数1和参数2的值相等,则断言成功,否则断言失败。
(2)assertNotEqual(参数1,参数2)
跟上面一样,只是,如果参数1和参数2的值不相等,则断言成功,否则断言失败。
(3)assertIn(参数1,参数2)
如果参数1在参数2里面,则断言成功,否则断言失败。
(4)assertNotIn(参数1,参数2)
如果参数1不在参数2里面,则断言成功,否则断言失败。
小结:
断言方法 |
用来检查 |
assertEqual(a,b) |
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 |
例如:assertEqual(参数1,参数2)举例。新建study_assert.py文件,内容如下:
import unittest
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase):
def test01(self):
num1 = my_sum(1, 2) # 定义变量num1,用来接收my_sum函数的返回值
self.assertEqual(num1, 3) # num1存放的是实际结果,3是预期结果
# 实际结果与预期结果一致,则说明测试用例测试通过,否则,用例不通过,测试失败
def test02(self):
num1 = my_sum(3, 3)
self.assertEqual(num1, 6)
新建run_assert.py,内容如下:
# 单纯的用来执行study_assert.py这个文件里的测试用例
import unittest
# 查找当前目录下的study_assert.py,然后把这个文件里所有的测试用例加载到测试套件中
suite = unittest.TestLoader().discover(".", "study_assert.py")
runner = unittest.TextTestRunner()
runner.run(suite)
执行run_assert.py,结果如下:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
进程已结束,退出代码 0
# 说明断言成功
现在为了演示断言失败的情况,把study_assert.py文件中,第一个测试用例的self.assertEqual(num1, 3) 里的3改成4 。显然1+2=3,这里改成4肯定失败。
再次运行run_assert.py,结果如下:
F.
======================================================================
FAIL: test01 (study_assert.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\study_python\yy_daima\ceshikuangjia\two_study_assert\study_assert.py", line 12, in test01
self.assertEqual(num1, 4)
AssertionError: 3 != 4
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
进程已结束,退出代码 0
注意:用例执行成功与否看运行的结果的第一行就知道了,如果都是点,就代表成功,有几个点就代表执行了几个测试用例,F代表失败。例如:上面的结果中,第一行是F.说明,本次一共执行了两个测试用例,其中,第一个用例失败,第二个用例成功了。
例如:assertIn(参数1,参数2)举例。将study_assert.py内容进行修改,如下:
import unittest
import random
def MyNumber(): # 定义一个函数,要求返回1~4之间的一个随机数
return random.randint(1, 4)
class MyTest(unittest.TestCase):
def test1(self):
num1 = MyNumber() # 调用函数
self.assertIn(num1, [1, 2, 3, 4]) # 判断num1是否是1234中的一个数
# 第一种执行方法,直接在框架里执行,即鼠标右键运行,选择Unittests in study_assert.py
结果:
Ran 1 test in 0.002s
OK
进程已结束,退出代码 0
# 第二种执行方法:执行run_assert.py
结果:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
进程已结束,退出代码 0
注意:都有两种执行方式,一个是直接在测试用例那个文件里直接执行,即:右键【运行Unittests in这个py文件名】。另一个是再另外建个文件专门用来执行测试用例,此时就是【运行 文件名】。
把study_assert.py内容进行修改,来验证断言失败的情况。修改后内容如下:
把函数定义的范围由原来的1~4改成,10~40,断言时候依然判断这个数是否在1~4之间,显然肯定不在,所以就会断言失败。
import unittest
import random
def MyNumber(): # 定义一个函数,要求返回10~40之间的一个随机数
return random.randint(10, 40)
class MyTest(unittest.TestCase):
def test1(self):
num1 = MyNumber() # 调用函数
self.assertIn(num1, [1, 2, 3, 4]) # 判断num1是否是1234中的一个数
结果:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/two_study_assert/run_assert.py
F
======================================================================
FAIL: test1 (study_assert.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\study_python\yy_daima\ceshikuangjia\two_study_assert\study_assert.py", line 31, in test1
self.assertIn(num1, [1, 2, 3, 4]) # 判断num1是否是1234中的一个数
AssertionError: 18 not found in [1, 2, 3, 4]
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
进程已结束,退出代码 0
上面的报错信息中,清楚的写出来了18不在1~4里面。
七、参数化
从之前的代码中可以看到,测试用例test01、test02的区别就是,代码基本一样,只是数据和预期结果不同,测试的都是同一个加法函数。显然这种方式,代码看起来冗余度特别高,比如,要拿100个数据来测那个功能,就要写100次差不多一模一样的代码,这样写起来就会很麻烦,重复代码也会特别多,此时就可以使用参数化这个技术。即:通过参数化的方式来传递数据,这样不仅可以减少代码的冗余,后期还能很好的进行维护。也就是说,用一个方法通过传递不同的参数就可以实现。
1、安装第三方插件——parameterized
unittest本身是不支持参数化的,但是可以通过安装第三方插件parameterized来实现参数化。
安装方法:
win+R输入cmd进入dos窗口,切换到python的安装目录,输入:
python -m pip install parameterized 即可进行下载。效果如下:
D:\anzhuang\Python37>python -m pip install parameterized
Looking in indexes: http://pypi.douban.com/simple
Collecting parameterized
Downloading http://pypi.doubanio.com/packages/31/13/fe468c8c7400a8eca204e6e160a29bf7dcd45a76e20f1c030f3eaa690d93/parameterized-0.8.1-py2.py3-none-any.whl
Installing collected packages: parameterized
Successfully installed parameterized-0.8.1
WARNING: You are using pip version 19.3.1; however, version 22.3.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
D:\anzhuang\Python37>
踩坑1:输入pip install parameterized,报错
D:\anzhuang\Python37>pip install parameterized
Traceback (most recent call last):
File "d:\anzhuang\python37\lib\runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "d:\anzhuang\python37\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "D:\anzhuang\Python37\Scripts\pip.exe\__main__.py", line 9, in <module>
TypeError: 'module' object is not callable
检验是否安装成功:
方法1:去电脑里找到python的安装目录,然后进入Lib\site-packages目录下就可以看到多了三个文件夹,分别是:nose_parameterized、parameterized、parameterized-0.8.1.dist-info。
方法2:win+R输入cmd进入dos窗口,输入python回车,进入python编译器,输入:
from parameterized import parameterized回车,不报错说明安装成功。
C:\Users\Lenovo>python
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from parameterized import parameterized
>>> exit
Use exit() or Ctrl-Z plus Return to exit
>>> exit()
C:\Users\Lenovo>
2、测试用例中使用参数化的场景
多个测试用例的代码相同,只是测试数据和预期结果不同,这时候就可以把多个测试用例通过参数化技术合并为一个测试用例。也就是说,就一个功能点,我们要输入好多的数据去测试它,来找它的问题,这个时候就可以用参数化,这样能够减少冗余代码。
3、参数化操作步骤
第一步:导包,from parameterized import parameterized。
第二步:在需要参数化的方法上面用@parameterized.expand()进行装饰。
其中,expand( )里面是一个列表套元组。即:列表里面放多个元组,每个元组中的成员就代表调用方法时候所使用的实参,列表中有几个元组,就说明有几组测试数据,方法就会被自动调用几次。
例如:把最开始的测试加法运算的函数那个代码进行优化,加上断言、参数化,代码如下:
新建canshuhua.py文件。
import unittest
from parameterized import parameterized
def my_sum(a, b):
return a + b
class MyTest(unittest.TestCase):
@parameterized.expand([(1, 2, 3), (3, 3, 6), (5, 5, 10), (-1, 10, 9)])
def test01(self, a, b, c):
num1 = my_sum(a, b)
self.assertEqual(num1, c)
# 注释:
# 元组里面写三个数,前两个数代表调用函数时候传的实参,后一个数代表预期结果
# 有多少个测试数据,就写多少个元组,每一个元组代表一组测试数据,例如,上面一共有4组测试数据
# a、b、c的含义:
# a是调用my_sum函数的第一个参数,b是调用my_sum的第二个参数
# c是预期结果
# 定义变量num1,用来接收my_sum函数的返回值,即:num1代表实际结果
结果:
# 两种执行方式
# 法1,直接在这个测试用例文件里面用unittest框架执行,鼠标右键【运行Unittests in canshuhua.py】
结果:
Ran 4 tests in 0.000s
OK
进程已结束,退出代码 0
# 法2,新建一个py文件,另写代码,专门用来执行测试用例,也就是在框架外执行
# 新建run_canshuhua.py,写入以下内容并执行
import unittest
# 查找当前目录下的canshuhua.py,然后把这个文件里所有的测试用例加载到测试套件中
suite = unittest.TestLoader().discover(".", "canshuhua.py")
runner = unittest.TextTestRunner()
runner.run(suite)
结果:
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
进程已结束,退出代码 0
4、参数化的三种写法
第一种:就是上面那种,把参数化的那行数据代码写到类里,方法的上面。
第二种:在类的外面,定义一个全局变量,用来存放测试数据,然后在类的里面调用这个全局变量。例如:其实,本质上跟第一种一样,只是单纯把数据拿出来了而已。
# 参数化的第2种写法
import unittest
from parameterized import parameterized
def my_sum(a, b):
return a + b
# 定义一个全局变量名叫list1,用来存放测试数据
list1 = [(1, 2, 3), (3, 3, 6), (5, 5, 10), (-1, 10, 9)]
class MyTest(unittest.TestCase):
@parameterized.expand(list1) # 调用全局变量list1
def test01(self, a, b, c):
num1 = my_sum(a, b)
self.assertEqual(num1, c)
第三种:定义个函数,返回一个列表,这个列表里放的是测试数据。
import unittest
from parameterized import parameterized
def my_sum(a, b):
return a + b
def gat_data(): # 定义一个函数
return [(1, 2, 3), (3, 3, 6), (5, 5, 10), (-1, 10, 9)]
class MyTest(unittest.TestCase):
@parameterized.expand(gat_data()) # 调用函数
def test01(self, a, b, c):
num1 = my_sum(a, b)
self.assertEqual(num1, c)
小结:三种方式本质上都一样,只是写法上略有不同,推荐前两种方法。
八、测试报告
有两种格式的测试报告。常用第二种。
(1)文本格式的测试报告,它是unittest自带的一种测试报告,测试结束后生成的是一个.txt文本文件。
(2)html格式的测试报告,它是一个第三方插件,使用之前要先去网上下载【HTMLTestRunner.py 】,然后把它放到电脑里python安装目录下的Lib\site-packages目录下,后期项目代码中通过from...import来进行调用。
from HTMLTestRunner import HTMLTestRunner
测试结束后,生成的是一个.html文件,然后用浏览器就能打开了。这种用的比较多。
小结:其实两种测试报告的区别主要是在最后执行测试套件的方式上有所不同,使用TextTestRunner,得到的是.txt格式的测试报告;使用HTMLTestRunner,得到的是.html格式的测试报告。
1、生成文本格式的测试报告
操作步骤:
第一步:导包,import unittest
第二步:生成测试套件。
suite = unittest.TestLoader().discover("文件查找路径", "要查找的文件名/用通配符查找多个文件")
第三步:打开一个文件,写入测试结果,注意编码方式。这个文件就是所说的测试报告。
file = open("a.txt", "w", encoding="utf8") # 写方式打开文件,如果没有则创建,有,则覆盖里面内容。
第四步:为TextTestRunner实例化一个对象。在实例化时候要注意写两个参数:
runner = unittest.TextTestRunner(stream=file, verbosity=2)
第一个参数:stream,表示用open打开的文件流
verbosity=2是个固定写法,2表示用第二个模板 。
通常都是写简化的写法:
runner = unittest.TextTestRunner(file, verbosity=2)
第五步:执行测试套件。
第六步:关闭文件。
例如:新建study_txt_report.py,内容如下:
# 文本样式的测试报告是unittest自带的
import unittest
from parameterized import parameterized
def my_sum(a, b):
return a + b
# 定义一个全局变量名叫list1,用来存放测试数据
list1 = [(1, 2, 3), (3, 3, 6), (5, 5, 10), (-1, 10, 9)]
class MyTest(unittest.TestCase):
@parameterized.expand(list1) # 调用全局变量list1
def test01(self, a, b, c):
num1 = my_sum(a, b)
self.assertEqual(num1, c)
新建run_txt_report.py,用来执行上面代码,并生成测试报告,内容如下:
# 生成TextTestRunner的测试报告
import unittest
suite = unittest.TestLoader().discover(".", "study_txt_report.py")
file = open("a.txt", "w", encoding="utf8") # 写方式打开文件,如果没有则创建,有,则覆盖里面内容
runner = unittest.TextTestRunner(file, verbosity=2)
runner.run(suite)
file.close()
# 执行
结果:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/study_report/txt_report/run_txt_report.py
进程已结束,退出代码 0
说明结果已经写到了同目录下的a.txt。打开该文件,内容如下:
test01_0 (study_txt_report.MyTest) ... ok
test01_1 (study_txt_report.MyTest) ... ok
test01_2 (study_txt_report.MyTest) ... ok
test01_3 (study_txt_report.MyTest) ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
可以看到,其实就是把之前控制台输出的内容,以一种更具体的方式,保存到了一个文本文件里。
#以前没用测试报告时候
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/three_study_canshuhua/run_canshuhua.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
进程已结束,退出代码 0
演示断言失败时,文本样式测试报告的特点
刚才全是断言成功,现在为了更好的演示文本样式的测试报告的特点,现在专门写一个错误的数据,来进行测试。这样就会断言失败,此时就能清楚的看到文本样式的测试报告,面对用例失败时具体是什么样子的了。
例如:把list1 中的第一组测试数据的1,2,3改成1,2,4,也就是说把原来的1+2等于3改成了等于4,显然实际结果与预期结果不符,此时就会断言失败。
执行run_txt_report.py,结果a.txt内容如下:
test01_0 (study_txt_report.MyTest) ... FAIL
test01_1 (study_txt_report.MyTest) ... ok
test01_2 (study_txt_report.MyTest) ... ok
test01_3 (study_txt_report.MyTest) ... ok
======================================================================
FAIL: test01_0 (study_txt_report.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\anzhuang\Python37\lib\site-packages\parameterized\parameterized.py", line 533, in standalone_func
return func(*(a + p.args), **p.kwargs)
File "D:\study_python\yy_daima\ceshikuangjia\study_report\txt_report\study_txt_report.py", line 18, in test01
self.assertEqual(num1, c)
AssertionError: 3 != 4
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=1)
可以清楚的看到,是第一组测试数据失败,原因是3不等于4 。
2、生成html格式的测试报告
操作步骤:步骤跟用TextTestRunner差不多,只是执行套件的方法不一样。
第一步:导包。
import unittest
from HTMLTestRunner import HTMLTestRunner
第二步:生成测试套件。
第三步:以二进制只写的方式打开测试报告。file = open("a.html", "wb")
第四步:实例化HTMLTestRunner对象。实例化时候,第一个参数是open打开的文件,后面有一些可选参数,比如,加title,表示报告的标题。
第五步:执行测试套件。
第六步:关闭文件。
例如:生成网页版的测试报告
把新建一个study_html_report.py,把study_txt_report.py内容全部复制进行。再新建run_html_report.py,用来生成网页版的测试报告,内容如下:
# 生成HTMLTestRunner的测试报告
import unittest
from HTMLTestRunner import HTMLTestRunner
suite = unittest.TestLoader().discover(".", "study_html_report.py")
file = open("a.html", "wb")
runner = HTMLTestRunner(file, title="测试报告——测试加法运算")
runner.run(suite)
file.close()
# 注意:
# 文件名就要改成.html后缀
# wb用二进制写方式,就不用指定字符集utf8了
# title就是给这个测试报告起个名字
运行结果:
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/study_report/html_report/run_html_report.py
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
Time Elapsed: 0:00:00.000997
....
进程已结束,退出代码 0
四个点,说明四个测试用例全部通过,打开当前目录下的a.html文件,即可查看测试报告。
这里使用的是谷歌浏览器打开,效果如下:
演示断言失败时,网页版测试报告的特点:
刚才全是断言成功,现在为了更好的演示网页样式测试报告的特点,现在专门写一个错误的数据,来进行测试。这样就会断言失败。
例如:把list1 中的第一组测试数据的1,2,3改成1,2,4,也就是说把原来的1+2等于3改成了等于4,显然实际结果与预期结果不符,此时就会断言失败。
执行run_html_report.py,就会有一个F三个点,说明,四组数据中,第一个失败,后三个成功。
D:\anzhuang\Python37\python.exe D:/study_python/yy_daima/ceshikuangjia/study_report/html_report/run_html_report.py
F...<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
Time Elapsed: 0:00:00.000997
进程已结束,退出代码 0
再来看测试报告,用谷歌浏览器打开a.html,效果如下:
可以看到,总共执行了四个测试用例,三个通过,一个不通过。 点开Detail可以具体看到是哪个测试失败了。
点开fail也可以看到详细的报错信息。是由于3不等于4所以断言失败。
九、跳过
对于测试类中的方法,如果有一些代码还没有写完或者错误还没调试完的代码,在测试的时候可以先跳过,不执行它。注意:只适用于测试类中定义的方法。也就是说,在测试类中定义的方法,可以使用@unittest.skip进行跳过;而在类外面定义的函数用这个就会报错,此时,如果这个函数想跳过不执行的话就只能打#注释掉。
具体操作:在需要跳过的方法上面写上:@unittest.skip即可。
例如:比如一个测试类里面有三个方法,(类里面的一个方法就代表一个测试用例),也就是说它有三个测试用例,如果有一个不想执行,怎么办?两种办法,第一种:用例加载到测试套件的时候用TestSuite,把要执行的用例加进去,不想执行的就不加。第二种:用TestLoader,然后在不想执行的用例上面加上@unittest.skip。
例如:新建study_skip.py,内容如下:
import unittest
def func1(): # 定义一个函数
print("函数1被执行了")
class MyTest(unittest.TestCase):
def test01(self): # 方法1
func1() # 调用函数1
print("方法1被执行了")
def test02(self): # 方法2
print("方法2被执行了")
def test03(self): # 方法3
print("方法3被执行了")
运行:
# 第一种运行方式:鼠标右键【运行Unittests in 文件名】
结果:
Testing started at 10:01 ...
D:\anzhuang\Python37\python.exe "D:\anzhuang\PyCharm 2020.1\plugins\python\helpers\pycharm\_jb_unittest_runner.py" --path D:/study_python/yy_daima/ceshikuangjia/four_study_skip/study_skip.py
Ran 3 tests in 0.003s
Launching unittests with arguments python -m unittest D:/study_python/yy_daima/ceshikuangjia/four_study_skip/study_skip.py in D:\study_python\yy_daima\ceshikuangjia\four_study_skip
OK
函数1被执行了
方法1被执行了
方法2被执行了
方法3被执行了
进程已结束,退出代码 0
# 第二种方式:新建run_study_skip.py文件,内容如下:
import unittest
suit = unittest.TestLoader().discover(".", "study_skip.py")
runner = unittest.TextTestRunner()
runner.run(suit)
#鼠标右键【运行 文件名】
结果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
函数1被执行了
方法1被执行了
方法2被执行了
方法3被执行了
进程已结束,退出代码 0
现在不想执行函数1和方法3,怎么办?因为函数1不是方法,所以不能使用@unittest.skip ,只能用#把它注释掉,而方法3可以直接使用@unittest.skip 进行跳过。
# 修改study_skip.py内容如下:
import unittest
# def test_func1():
# print("函数1被执行了")
class MyTest(unittest.TestCase):
def test01(self):
# test_func1() # 调用函数1
print("方法1被执行了")
def test02(self):
print("方法2被执行了")
@unittest.skip # 跳过
def test03(self):
print("方法3被执行了")
运行run_study_skip.py查看结果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
方法1被执行了
方法2被执行了
进程已结束,退出代码 0
虽然结果中,显示的是运行了三条测试用例,但没有输出第三个测试用例中的“方法3被执行了”,说明实际上根本没有执行方法3 。只是在用例个数上算上这个用例了而已。
实际上,如果真不想执行哪段代码直接注释掉就好,没必要用这个@unittest.skip。用这个反倒看起来有点复杂。