Python接口测试框架实战与自动化进阶☝☝☝
一、fiddler在工作中的运用
1、如何抓接口
抓紧手机端接口
①、在电脑终端输入:ipconfig ,找到电脑ip
②、打开手机,连接WiFi,进入WiFi详情,改用手动代理,将ip设置为电脑端的ip,端口默认(8888)
③、打开fiddler,找到并打开Fiddler Options ,选择Connections栏,做如下改动:
这样就可以尝试抓取接口了。
二、unittest使用
python自带的包
1、unittest简单使用
使用unittest的test类:TestCase ,重载相关方法:
import unittest
class TestMethod(unittest.TestCase):
@classmethod
def setUpClass(cls):
print('重载setUpClass类方法,类实例化(初始化)时调用')
@classmethod
def tearDownClass(cls):
print('重载tearDownClass方法,所有方法执行完后调用')
def setUp(self):
print('重载setUp方法,每个test方法执行前都会调用')
def tearDown(self):
print('重载tearDown方法,每个test方法执行完成后都会调用')
def test_01(self):
print('测试方法,必须以 test 开头')
if __name__ == '__main__':
unittest.main()
2、unittest基本介绍
参考:http://www.php.cn/python-tutorials-358252.html
unittest提供了多个类:
__all__ = ['TestResult', 'TestCase', 'TestSuite',
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
'expectedFailure', 'TextTestResult', 'installHandler',
'registerResult', 'removeResult', 'removeHandler']
TestCase
setUp() # 在每个test执行前都要执行的方法
tearDown() #在每个test执行后都要执行的方法。(不管是否执行成功)
setUpClass() # 在一个测试类中在所有test开始之前,执行一次且必须使用到Testsuite(只有在TestSuite的run方法里面才对其调用)
tearDownClass() # 在一个测试类中在所有test结束之后,执行一次且必须使用到Testsuite(只有在TestSuite的run方法里面才对其调用)
run() # 这是unnitest的核心,逻辑也相对复杂,但是很好理解,具体自己看源码。所有最终case的执行都会归结到该run方法
# 还有一个重要的_resultForDoCleanups私有变量,存储TestResult的执行结果,这个在构建后面的skip用到
我们要明确TestCase类中所有的测试用例是独立的,其实每个testcase就是一个个TestCase类的实例对象,所以不要企图在某个test存储或改变一个变量,下个test中能用到,除非利用到setUpClass。我们看个例子:
import unittest
class Mydemo(unittest.TestCase):
def test1(self):
self.a=1
print ("i am test1 the value of a is {}".format(self.a))
def test2(self):
print ("i am test2 the value of a is {}".format(self.a))
if __name__ == '__main__':
unittest.main()
打印结果:
C:\Python27\python.exe D:/Moudle/module_1/test4.py
i am test1 the value of a is 1
.E
======================================================================
ERROR: test2 (__main__.Mydemo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:/Moudle/module_1/test4.py", line 7, in test2
print ("i am test2 the value of a is {}".format(self.a))
AttributeError: 'Mydemo' object has no attribute 'a'
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (errors=1)
上面就是说明TestCase类中所有的测试用例是独立的,每个testcase就是由TestCase实例化的一个独立的实例。如果要使用共享变量,使用全局变量或者类变量就好了。
借用类方法setUpClass与tearDownClass只执行一遍的特性,实现个小例子:启动时开启浏览器,执行结束时关闭浏览器:
import unittest
from selenium import webdriver
class Mydemo(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.browser=webdriver.Firefox()
def test1(self):
'''登录'''
browser=self.browser
#do someting about login
def test2(self):
'''查询'''
browser = self.browser
# do someting about search
def test3(self):
'''提交数据'''
browser = self.browser
# do someting about submmit
@classmethod
def tearDownClass(cls):
browser=cls.browser
browser.close()
if __name__ == '__main__':
unittest.main()
一个class继承了unittest.TestCase,便是一个测试用例,但如果其中有多个以 test 开头的方法,那么每有一个这样的方法,在load的时候便会生成一个TestCase实例,如:一个class中有四个test_xxx方法,最后在load到suite中时也有四个测试用例。整个流程:
1 |
1、写好TestCase2、然后由TestLoader加载TestCase到TestSuite3、然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中4、我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例 这里加个说明,在Runner执行时,默认将执行结果输出到控制台,我们可以设置其输出到文件,在文件中查看结果(测试报告) |
demo:
# test_mathfunc.py
import unittest
from mathfunc import *
class TestMathFunc(unittest.TestCase):
"""Test mathfuc.py"""
def test_add(self):
"""Test method add(a, b)"""
self.assertEqual(3, add(1, 2))
self.assertNotEqual(3, add(2, 2))
def test_minus(self):
"""Test method minus(a, b)"""
self.assertEqual(1, minus(3, 2))
def test_multi(self):
"""Test method multi(a, b)"""
self.assertEqual(6, multi(2, 3))
def test_divide(self):
"""Test method divide(a, b)"""
self.assertEqual(2, divide(6, 3))
self.assertEqual(3, divide(5, 2))
if __name__ == '__main__':
unittest.main()
执行结果:
.F..
======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
File "E:/AutomaticTest/Test_Framework/temp/test_mathfunc.py", line 24, in test_divide
self.assertEqual(3, divide(5, 2))
AssertionError: 3 != 2.5
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=1)
能够看到一共运行了4个测试,失败了1个,并且给出了失败原因,3 != 2.5
这就是一个简单的测试,有几点需要说明的:
- 在第一行给出了每一个用例执行的结果的标识,成功是 .,失败是 F,出错是 E,跳过是 S。从上面也可以看出,测试的执行跟方法的顺序没有关系,test_divide写在了第4个,但是却是第2个执行的。
- 每个测试方法均以 test 开头,否则是不被unittest识别的。
- 在unittest.main()中加 verbosity 参数可以控制输出的错误报告的详细程度,默认是 1,如果设为 0,则不输出每一用例的执行结果,即没有上面的结果中的第1行;如果设为 2,则输出详细的执行结果,如下:
# if __name__ == '__main__':
# unittest.main(verbosity=2)
# 输出结果:
test_add (__main__.TestMathFunc)
Test method add(a, b) ... ok
test_divide (__main__.TestMathFunc)
Test method divide(a, b) ... FAIL
test_minus (__main__.TestMathFunc)
Test method minus(a, b) ... ok
test_multi (__main__.TestMathFunc)
Test method multi(a, b) ... ok
======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
File "E:/AutomaticTest/Test_Framework/temp/test_mathfunc.py", line 24, in test_divide
self.assertEqual(3, divide(5, 2))
AssertionError: 3 != 2.5
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=1)
批量测试TestCase:
1)不用 unittest.main() 执行,直接通过TextTestRunner来执行用例
import unittest
from test_mathfunc import TestMathFunc
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
suite.addTests(tests) # 将每个case用例都添加到TestSuite中
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite) # 使用 TextTestRunner执行案例,默认结果会输出倒控制台
2)上述是使用addTest方法添加单个TestCase用例到TestSuite列表中,另外还能使用addTests + TestLoader 添加TestCase用例到TestSuite中
# 直接用addTest方法添加单个TestCase
suite.addTest(TestMathFunc("test_multi"))
# 使用addTests + unittest.TestLoader()方法结合
# loadTestsFromName(),传入'模块名.TestCase名'
suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_mathfunc.TestMathFunc'])) # loadTestsFromNames(),类似,传入列表
# loadTestsFromTestCase(),传入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
需要注意的是:用TestLoader的方法是无法对case进行排序的。同时,suite中也可以套suite。
将测试结果输出到文件中
用例组织好了,但结果只能输出到控制台,这样没有办法查看之前的执行记录,我们想将结果输出到文件
import unittest
from test_mathfunc import TestMathFunc # TestCase用例
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
with open(r'D:\UnittestTextReport.txt', 'a') as f:
runner = unittest.TextTestRunner(stream=f,verbosity=2) # 将结果输出到D盘下的 UnittestTextReport.txt 文件中
runner.run(suite)
跳过case
unittest也提供了几种方法,用于临时跳过某个case不执行
1)skip装饰器
View Code
执行结果:
View Code
可以看到总的test数量还是4个,但divide()方法被skip了。
skip装饰器一共有三个:
- unittest.skip(reason) :无条件跳过
- unittest.skipIf(condition, reason) :f当condition为True时跳过
- unittest.skipUnless(condition, reason) :当condition为False时跳过。
2)TestCase.skipTest()方法
View Code
执行结果:
View Code
用HTMLTestRunner输出漂亮的HTML报告
HTMLTestRunner是一个第三方的unittest HTML报告库,HTMLTestRunner.py文件的创建参考下面内容
demo:
import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
with open(r'D:\HTMLReport.html', 'wb') as f:
runner = HTMLTestRunner(stream=f,
title='MathFunc Test Report',
description='generated by HTMLTestRunner.',
verbosity=2
)
runner.run(suite)
执行结果,控制台上可以看到:
ok test_add (test_mathfunc.TestMathFunc)
F test_divide (test_mathfunc.TestMathFunc)
ok test_minus (test_mathfunc.TestMathFunc)
ok test_multi (test_mathfunc.TestMathFunc)
Time Elapsed: 0:00:00.002000
并且在D盘中生成了HTMLReport.html,打开如下:
HTMLTestRunner介绍
HTMLTestRunner url:http://tungwaiyip.info/software/HTMLTestRunner_0_8_2/HTMLTestRunner.py
1)进入链接,复制代码,重新生成一个HTMLTestRunner.py文件,将复制的代码copy进去
2)将HTMLTestRunner.py文件放到python安装目录的Lib目录下
3)项目需要用到时,将HTMLTestRunner.py文件导入到项目中使用即可
python3-HTMLTestRunner
三、mock服务入门到实战
1、什么是mock?
mock在翻译过来有模拟的意思。这里要介绍的mock是辅助单元测试的一个模块。它允许您用模拟对象替换您的系统的部分,并对它们已使用的方式进行断言。
实际生产中的项目有些会很复杂,对其进行单元测试的时候,会遇到以下问题:
- 接口的依赖
- 外部接口调用
- 测试环境非常复杂
单元测试应该只针对当前单元进行测试, 所有的内部或外部的依赖应该是稳定的, 已经在别处进行测试过的.此时使用mock 就可以对外部依赖组件实现进行模拟并且替换掉, 从而使得单元测试将焦点只放在当前的单元功能,而不再受外部依赖影响。
1 2 |
在Python2.x 中 mock是一个单独模块,需要单独安装 在Python3.x中,mock已经被集成到了unittest单元测试框架中,所以,可以直接使用 |
2、mock的使用
1)首先,来个简单的例子
①、创建module.py文件:
#module.py
class Count():
def add(self, a, b):
return a + b
②、对Count类的add方法进行测试,新建mock_test.py文件:
from unittest import mock
import unittest
from module import Count
class MockDemo(unittest.TestCase): #casetest用例
def test_add(self):
count = Count()
count.add = mock.Mock(return_value=13, side_effect=count.add) # 使用Mock类实例化对象模拟add()方法
result = count.add(8, 8) # 真正调用add方法,返回数据16
print(result)
count.add.assert_called_with(8, 8)
self.assertEqual(result, 16) # 判断结果是否相等
if __name__ == '__main__':
unittest.main()
解析:
count.add = mock.Mock(return_value=13, side_effect=count.add):
当side_effect为设置时(默认default),return_value设置的值会返回当作count.add方法的返回值,当side_effect被设置值时,return_value不起作用(side_effect参数和return_value是相反的,它给mock分配了可替换的结果,覆盖了return_value。简单的说,side_effect存在时一个模拟工厂调用将返回side_effect值,而不是return_value)
上面代码,将side_effect的值设为count.add,此时count.add并未调用。
result = count.add(8, 8):
真正调用count.add方法,返回值:8
count.add.assert_called_with(8, 8):
调用add方法时,mock模拟了add方法,参数会被临时存储起来,通过这个方法会自动检查传入的参数是否与 count.add(8,8)中调用的参数保持一致,如果一致则通过,如果不一致则测试失败。
2)解决测试依赖
前面的例子,只为了让大家对mock有个初步的印象。再接来,我们看看如何mock方法的依赖。
例如,我们要测试A模块,然后A模块依赖于B模块的调用。但是,由于B模块的改变,导致了A模块返回结果的改变,从而使A模块的测试用例失败
①、新建function.py文件:
# function.py
def add_and_multiply(x, y):
addition = x + y
multiple = multiply(x, y)
return (addition, multiple)
def multiply(x, y):
return x * y
add_and_multiply(x,y),内部调用了multiply(x,y)函数,当multiply()内部代码改变时,add_and_multiply函数肯定会受到影响,此时即是add_and_multiply依赖于multiply函数。
②、新建mock_test.py文件:
import unittest
from unittest import mock
import function
class MyTestCase(unittest.TestCase):
@mock.patch("function.multiply") # 装饰器,模拟类或对象。此时模拟multiply函数
def test_add_and_multiply2(self, mock_multiply): # mock_multiply 即是 multiply函数
x = 3
y = 5
mock_multiply.return_value = 15 # 设定multiply的返回值,即multiply的返回值为15,固定不变
addition, multiple = function.add_and_multiply(x, y) # 执行add_and_multiply函数,addition通过x+y得出数值为8 ,multiple通过multiply调用,此时被mock模拟,值固定为15
mock_multiply.assert_called_once_with(3, 5)
self.assertEqual(8, addition)
self.assertEqual(15, multiple)
if __name__ == "__main__":
unittest.main()
解析:
@mock.patch("function.multiply"):
mock.patch()装饰/上下文管理器可以很容易地模拟类或对象在模块测试。在测试过程中,您指定的对象将被替换为一个模拟(或其他对象),并在测试结束时还原。这里模拟function.py文件中multiply()函数
def test_add_and_multiply2(self, mock_multiply)::
在定义case测试用例中,将mock装饰的的multiply()函数(对象)重命名为 mock_multiply对象
mock_multiply.return_value = 15:
设定mock_multiply对象的返回值为固定的15
ock_multiply.assert_called_once_with(3, 5): 检查ock_multiply方法的参数是否正确
在测试代码中,我们调用了外部函数:add_and_multiply。它会调用内嵌的multiply函数。前面我们已经mock了multiply函数,此时通过add_and_multiply函数调用内部multiply函数,会被我们定义的mock对象取代。— 这个时候multiply函数被调用,传给它们的任何参数将被储存起来。顾名思义,mock对象的assert_called_once_with方法就是一个不错的捷径来验证某个对象是否被一组特定的参数调用过。如果被调用了,测试通过。反之,assert_called_once_with会抛出AssertionError的异常。即通过传入参数3 跟5 ,判断multiply之前是否引入这两个参数被调用过。
好吧,我们遇到了很多实际问题。首先,我们通过mock将multiply函数从add_and_multiply中分离出来。这就意味着我们的单元测试只针对add_and_multiply的内部逻辑。只有针对add_and_multiply的代码修改将影响测试的成功与否。
其次,我们现在可以控制内嵌函数的输出,以确保外部函数处理了不同的情况。例如,add_and_multiply可能有逻辑条件依赖于multiply的返回值:比如说,我们只想在乘积大于10的条件下返回一个值。通过人为设定multiply的返回值,我们可以模拟乘积小于10的情况以及乘积大于10的情况,从而可以很容易测试我们的逻辑正确性。
最后,我们现在可以验证被mock的函数被调用的次数,并传入了正确的参数。由于我们的mock对象取代了multiply函数的位置,我们知道任何针对multiply函数的调用都会被重定向到该mock对象。当测试一个复杂的功能时,确保每一步都被正确调用将是一件非常令人欣慰的事情。