zl程序教程

您现在的位置是:首页 >  Python

当前栏目

UnitTest测试框架全栈详解

2023-03-14 22:48:11 时间

从软件架构的⻆度来说,测试最重要的步骤是在软件开发的时候界入比较好,所以在早期测试的界入,从软件经济学的⻆度上来说,发现的问题解决成本低,投入的资源比较少。因此,对一个测试的系统,开始最佳的测试就是源代码级别的测试,也就是单元测试阶段,这个过程也被成为白盒测试。单元测试是最基本也是最底层的测试类型,单元测试应用于最基本的软件代码,如类,函数。方法等,单元测试通过可执行的断言检查被测单元的输出是否满足预期结果。在测试金字塔的理论上来说,越往下的测试投入资源越高,得到的回报率越大,⻅测试金字塔模型:

抛开软件架构的层面,在自动化测试的体系中,单元测试框架以及单元测试的知识体系是必须要掌握的技能之一, 单元测试的知识体系是自动化测试工程师以及测试开发工程师的知识体系之一,而且是必须具备的知识之一。在 Python语言中应用最广泛的单元测试框架是unittest和pytest,unittest属于标准库,只要安装了Python解释器后就 可以直接导入使用了,pytest是第三方的库,需要单独的安装。

白盒测试原理

在软件架构的层面来说,测试最核心的步骤就是在软件开发过程中。就软件本身而言,软件的行为或者功能是软件细节实现的产物,这些最终是交付给用户的东⻄。所以在早期执行测试的系统有可能是一个可测试和健壮的系统,它会带来为用户提供的功能往往是让人满意的结果。因此给予这样的⻆度,开始执行测试的最佳方法是来自源代码,也就是软件编写的地方以及开发人员。由于源代码是对开发人员是可⻅的,这样的一个测试过程我们可以称为白盒测试。

自动化测试用例

不管基于什么的测试框架,自动化测试用例的编写都需要遵守如下的规则,具体总结如下:

UnitTest组件

unittest是属于Python语言的单元测试框架,它的核心组件具体可以总结为如下:

测试类继承unittest模块中的TestCase类后,依据继承的这个类来设置一个新的测试用例类和测试方法,案例代码:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest


class ApiTest(unittest.TestCase):
   def test_001(self):
      pass

测试固件

所谓测试固件,可以理解为执行一个或者是多个测试用例工作所需要的初始化设置和清理操作,具体案例代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest

class ApiTest(unittest.TestCase):
   def setUp(self):
      pass
   
   def tearDown(self):
      pass

测试套件

测试套件顾名思义就是测试用例的集合,在unittest测试框架中主要是通过TestSuite类提供了对测试套件的支持,具体案例代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest

class ApiTest(unittest.TestCase):
   def test_001(self):
      pass
   def test_002(self):
      pass
if __name__ == '__main__':
   suite=unittest.TestSuite()
   suite.addTest('test_001')
   unittest.TextTestRunner(verbosity=2).run(suite)

测试固件每次都执行

在unittest测试框架中,测试固件分为两类,一类是在一个测试类里面,不管有多少个测试用例,每次执行测试用例测试固件都会被执行,具体案例代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest
from selenium import  webdriver

class ApiTest(unittest.TestCase):
   def setUp(self):
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.get('http://www.baidu.com')
      self.driver.implicitly_wait(30)

   def tearDown(self):
      self.driver.quit()

   def test_baidu_title(self):
      self.assertEqual(self.driver.title,'百度一下,你就知道')
   
   def test_baidu_url(self):
      self.assertEqual(self.driver.current_url,'https://www.baidu.com/')

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

在如上的代码结构中,测试用例执行的顺序为:setUp()方法,下来是具体的测试用例,最后时tearDown(),同时在一个测试类里面不管有多少个测试用例,测试固件每次都会被执行。

测试固件只执行一次

类方法setUpClass()和tearDownClass()是指在一个测试类里面,不管有多少个测试用例,测试固件只会执行一次,具体见案例代码:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest
from selenium import  webdriver

class ApiTest(unittest.TestCase):
   @classmethod
   def setUpClass(cls):
      cls.driver=webdriver.Chrome()
      cls.driver.maximize_window()
      cls.driver.get('http://www.baidu.com')
      cls.driver.implicitly_wait(30)

   @classmethod
   def tearDownClass(cls):
      cls.driver.quit()

   def test_baidu_title(self):
      self.assertEqual(self.driver.title,'百度一下,你就知道')

   def test_baidu_url(self):
      self.assertEqual(self.driver.current_url,'https://www.baidu.com/')

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

编写测试用例注意事项

1、在一个测试类里面,每一个测试方法都是以test开头的,test不能是中间或者尾部,必须是开头,建议test_ 2、每一个测试用例方法都应该有注释信息,这样在测试报告就会显示具体的测试点的检查点 3、在自动化测试中,每个测试用例都必须得有断言,无断言的自动化测试用例是无效的 4、最好一个测试用例方法对应一个业务测试点,不要多个业务检查点写一个测试用例 5、如果涉及到业务逻辑的处理,最好把业务逻辑的处理方法放在断言前面,这样做的目的是不要因为业务逻辑执行错误导致断言也是失败 6、测试用例名称最好规范,有约束

UnitTest的测试框架中提供了很丰富的测试套件,所谓测试套件其实我们可以把它理解为测试用例的集合,或者可以说理解为一个容器,在这个容器里面可以存放很多的测试用例。下面详细的说明下各个不同测试套件的应用和案例实战。

按测试类执行

按测试类执行,可以理解为在测试套件中,我们按测试类的方式来进行执行,那么也就不需要在乎一个测试类里面有多少测试用例,我们是以测试类为单位来进行执行,测试类里面有多少的测试用例,我们都会进行执行,下面详细的演示这部分的具体应用,具体案例代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest
from selenium import  webdriver

class Baidu(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.get('http://www.baidu.com/')

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

   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'
   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'
if __name__ == '__main__':
   '''按测试类执行'''
   suite=unittest.TestLoader().loadTestsFromTestCase(Baidu)
   unittest.TextTestRunner(verbosity=2).run(suite)

按测试模块来执行

思维按测试模块来执行,就是以模块为单位来进行执行,那么其实在一个模块里面可以编写很多的类,下面通过详细的代码演示下这部分,具体案例代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest
from selenium import  webdriver

class Baidu(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.get('http://www.baidu.com/')

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

   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'
   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'
class BaiDuSo(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.get('http://www.baidu.com/')

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

   def test_baidu_so_value(self):
      '''百度搜索关键字的验证'''
      so=self.driver.find_element_by_id('kw')
      so.send_keys('Selenium4')
      assert so.get_attribute('value')=='Selenium4'
if __name__ == '__main__':
   suite=unittest.TestLoader().loadTestsFromModule('test_module.py')
   unittest.TextTestRunner(verbosity=2).run(suite)

自定义测试套件

针对测试套件的方式是很多的,那么我们是否可以把加载所有测试用例的方式单独分离出来了,当然其实是可以的,这样我们只需要关注更多的测试用例的执行,下面具体演示下测试套件的分离部分,案例代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest
from selenium import  webdriver

class Baidu(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.get('http://www.baidu.com/')

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

   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'
   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'
   def suite(self):
      '''自定义测试套件'''
      return unittest.TestLoader().loadTestsFromModule('test_customer.py')

if __name__ == '__main__':
   unittest.TextTestRunner(verbosity=2).run(Baidu.suite())

分离测试套件

在一个完整的自动化测试用例中,比如在UI的自动化测试用例中,我们的测试用例是按照业务模块来进行划分的,那么以为着我们需要编写很多的模块,但是就存在重复的代码,比如我们针对百度产品的测试,不管是测试什么模块,测试固件这部分的代码每个测试模块都是一样的,这样就导致很多的重复的代码,重复必然就带来测试效率的低下的问题,举一个很简单的问题,比如需要修改测试的地址,就需要修改很多的测试模块,但是如果把测试套件分离出来,我们这需要修改一个地方就可以了,这样我们的测试效率就提升了一点,毕竟效率的提升是需要做很多的,不可能一点就进行大幅度的提升的。分离测试套件的思想其实很简单的,就是使用了继承的思想来解决这个问题,我们把测试固件分离到init.py里面,代码具体如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import unittest
from selenium import  webdriver


class Init(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.implicitly_wait(30)
      self.driver.get('http://www.baidu.com')

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

这样其他测试模块就需要引人这个模块中的Init类就可以了,然后再继承这个类,具体的代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  unittest
from selenium import  webdriver
from test.init import Init

class Baidu(Init):
   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'
   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'
if __name__ == '__main__':
   '''按测试类执行'''
   suite=unittest.TestLoader().loadTestsFromTestCase(Baidu)
   unittest.TextTestRunner(verbosity=2).run(suite)

其实过程我们使用了很简单的思维,但是解决了一个很核心的问题。

UnitTest之参数化

在unittest的测试框架中,可以结合ddt的模块来达到参数化的应用,当然关于ddt库的应用在数据驱动方面有很详 细的解释,这里就直接说另外的一个第三方的库parameterized,安装的命令为:

pip3 install parameterized

安装成功后,这里就以一个两个数相加的案例来演示它的应用,实现的源码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import unittest
from parameterized import  parameterized,param

def add(a,b):
   return a+b


class AddTest(unittest.TestCase):
   @parameterized.expand([
      param(1,1,2),
      param('a','b','ab')
   ])
   def test_add_function(self,x,y,result):
      print(result)
      assert add(a=x,b=y)==result

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

在UI自动化测试中,parameterized也是特别的有用,如针对一个登录案例的测试,针对登录就会有很多的测试案 例的,主要是用户名和密码的input表单的验证以及错误信息的验证,下面就结合具体的案例来看它在UI自动化测 试中的应用,案例源码为:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import unittest
from parameterized import  parameterized,param
from selenium  import  webdriver
import  time as t

class AddTest(unittest.TestCase):

   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.implicitly_wait(30)
      self.driver.get('https://mail.sina.com.cn/#')

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

   @parameterized.expand([
      param('','','请输入邮箱名'),
      param('srtSA','saert','您输入的邮箱名格式不正确'),
      param('aserSDAsd@sina.com','asdfrty','登录名或密码错误')
   ])
   def test_sina_email(self,username,password,result):
      t.sleep(2)
      self.driver.find_element_by_id('freename').send_keys(username)
      t.sleep(2)
      self.driver.find_element_by_id('freepassword').send_keys(password)
      t.sleep(2)
      self.driver.find_element_by_link_text('登录').click()
      t.sleep(3)
      div=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]')
      assert div.text==result

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

测试报告

下面详细的说明下测试报告的生成以及加载所有测试模块的过程,我们在tests的模块下编写了很多的测试用例,但是实际的生产环境中总不能按测试模块来执行,我们都是加载所有的测试模块来执行并且最终生成基于HTML的测试报告,测试报告会使用到第三方的库HTMLTestRunner,下载的地址为:https://github.com/tungwaiyip/HTMLTestRunner,当然针对Python3需要修改下源码部分。把该模块需要放在 /Library/Frameworks/Python.framework/Versions/3.7/lib的目录下。

下面我们编写具体的函数来加载所有的测试模块,路径处理部分我们使用os的模块来进行处理,针对路径处理这部分特别的再说下,不能使用硬编码,使用硬编码只会带来维护的成本性,而且也涉及到不同的操作系统针对路径是有不同的,比如MAC和Linux下是没有C盘的,但是Windows操作系统是有的,这部分需要特别的注意下,下面的函数主要体现的是加载所有测试模块的代码,以及生成测试报告的过程,具体源码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯
import  os
import  unittest
import HTMLTestRunner
import time

def base_dir(filePath='test'):
   return os.path.join(os.path.dirname(__file__),filePath)

def getSuite():
   suite=unittest.defaultTestLoader.discover(
      start_dir=base_dir(),
      pattern='test_*.py'
   )
   return suite

def nowTime():
   '''获取当前的时间'''
   return time.strftime('%y-%m-%d %H_%S_%M',time.localtime(time.time()))


def run():
   fp=open(os.path.join(os.path.dirname(__file__),'report',nowTime()+'_index.html'),'wb')
   runner=HTMLTestRunner.HTMLTestRunner(
      stream=fp,
      title='UI自动化测试报告',
      description='This Is A Automation Ui HTML Report'
   )
   runner.run(getSuite())

if __name__ == '__main__':
   run()

执行如上的代码后,会在report的文件夹下生成基于HTML的测试报告。