UnitTest测试框架

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

 

 

 

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

白盒测试原理

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

自动化测试用例编写

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

初始化 测试步骤 测试断言 测试清理

UnitTest测试框架

UnitTest组件

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

测试固件:1、初始化 2、清理

TestCase TestSuite TestRunner TestReport

 

 

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

import  unittest

class TestSina(unittest.TestCase):
    #初始化(打开要测试的地址)
    def setUp(self) -> None:
        pass

测试固件

测试固件表示一个测试用例或者多个测试以及清理工作所需要的设置或者准备

import unittest


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

测试套件

测试套件顾名思义就是相关测试用例的集合。在unittest中主要通过TestSuite类提供对测试套件的支持

import  unittest
from selenium import  webdriver
from selenium.webdriver.common.by import By
import time

class TestSina(unittest.TestCase):
    #初始化(打开要测试的地址)
    def setUp(self) -> None:
        self.driver=webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.get('https://mail.sina.com.cn/')

    '''
    测试用例规范
    1、必须是以test开头的
    2、每个测试点必须有标题
    3、每个测试点都必须得有断言
    4、测试点的名称不能一直
    5、执行顺序
        A、先执行setUp()
        B、执行测试点的代码
        C、最后执行tearDown()
    '''
    def test_sina_username(self):
        '''验证用户名为空的错误提示信息'''
        pass

    #清理
    def tearDown(self) -> None:
        self.driver.close()

测试运行

管理和运行测试用例的对象。

测试断言

对所测试的对象依据返回的实际结果与期望结果进行断言校验

assertEqual

def test_sina_username(self):
    '''验证用户名为空的错误提示信息'''
    self.driver.find_element(By.CLASS_NAME,'loginBtn').click()
    time.sleep(1)
    divText=self.driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]')
    #==:比较两个对象的值
    self.assertEqual(divText.text,'请输入邮箱名')

assertIN

def test_sina_username_in(self):
    '''验证用户名为空的错误提示信息'''
    self.driver.find_element(By.CLASS_NAME,'loginBtn').click()
    time.sleep(1)
    divText=self.driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]')
    #==:比较两个对象的值
    self.assertIn(divText.text,'请输入邮箱名')

布尔类型

def test_isAuto_login(self):
    '''验证自动登录已勾选'''
    isLogin=self.driver.find_element(By.ID,'store1')
    self.assertTrue(isLogin.is_selected())

def test_isNotAuto_login(self):
    '''验证自动登录没有勾选'''
    isLogin=self.driver.find_element(By.ID,'store1')
    isLogin.click()
    self.assertFalse(isLogin.is_selected())

unittest测试固件详解

在unittest中测试固件依据方法可以分为两种执行方式,一种是测试固件只执行一次,另外一种是测试固件每次都执行

测试固件每次均执行

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

测试固件只执行一次

类测试固件:不管一个类里面有多少个测试用例,测试固件只执行一次

执行完一个测试点,要设置到首页的页面

class TestSina(unittest.TestCase):
    #初始化(打开要测试的地址)
    @classmethod
    def setUpClass(cls) -> None:
        cls.driver=webdriver.Chrome()
        cls.driver.maximize_window()
        cls.driver.get('https://mail.sina.com.cn/')
        cls.driver.implicitly_wait(30)

        # 清理
    @classmethod
    def tearDownClass(cls) -> None:
            cls.driver.close()

测试报告

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

详情见https://www.cnblogs.com/weke/p/15490318.html 3.7测试报告

把该模块需要放在 python文件夹lib的目录下。

加载所有的测试模块

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

import os
import unittest


def allTests():
    '''使用测试套件来加载所有的测试模块'''
    suite=unittest.TestLoader().discover(
        start_dir=os.path.dirname(__file__),
        pattern='test_*.py')
    return suite

# for item in allTests():
#     for i in item:
#         for j in i:
#             print(item)

if __name__ == '__main__':
    unittest.TextTestRunner().run(allTests())

e是运行出错,f是运行失败,.是运行成功

TestSuite

执行测试套件(查找到符合要求的测试用例来执行) 把所有的测试模块放在测试套件里面来进行批量的执行

import os
import unittest


def allTests():
    '''使用测试套件来加载所有的测试模块'''
    suite=unittest.TestLoader().discover(
        start_dir=os.path.dirname(__file__),
        pattern='test_*.py')
    return suite
import os
import unittest


def allTests():
    '''使用测试套件来加载所有的测试模块'''
    suite=unittest.TestLoader().discover(
        start_dir=os.path.dirname(__file__),
        pattern='test_*.py')
    return suite

# for item in allTests():
#     for i in item:
#         for j in i:
#             print(item)

if __name__ == '__main__':
    unittest.TextTestRunner().run(allTests())

生成HTML测试报告

import  unittest
import os
import time
import HTMLTestRunner

#unittest+HTMLTestRunner来生成HTML的测试报告

def allTests():
   '''使用测试套件来加载所有的测试模块'''
   suite=unittest.TestLoader().discover(
      start_dir=os.path.dirname(__file__),
      pattern='test_*.py')
   return suite

def base_dir():
   return os.path.dirname(os.path.dirname(__file__))

# def nowTime():
#  return time.strftime('%y_%m_%d %h_%m_%s',time.localtime(time.time()))

def run():
   filename=os.path.join(base_dir(),'report','index.html')
   fp=open(filename,'wb')
   runner=HTMLTestRunner.HTMLTestRunner(
      stream=fp,
      title='自动化测试报告',
      description='UI自动化测试报告')
   runner.run(allTests())

if __name__ == '__main__':
   run()

执行后,输出结果为:

 

 在report的文件夹下,生成的测试报告打开后显示如下:

Unittest参数化

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

pip3 install parameterized

参数化:相同的测试步骤,不同的测试数据,那么这样的场景可以使用参数化,使用参数化可以有效的提升测试的效率

 

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

import time
import unittest
from selenium import webdriver
from parameterized import param,parameterized
from selenium.webdriver.common.by import By

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


    def tearDown(self) -> None:
        self.driver.close()
    
    @parameterized.expand([
        param('','','请输入邮箱名'),
        param('fgddfg','','您输入的邮箱名格式不正确'),
        param('fdgd@sina.com','dfgsg','登录名或密码错误')
    ])
    def test_sina_login_param(self,username,password,result):
        '''使用参数化覆盖上面的三个测试场景'''
        self.driver.find_element(By.ID,'freename').send_keys(username)
        self.driver.find_element(By.ID,'freepassword').send_keys(password)
        self.driver.find_element(By.CLASS_NAME,'loginBtn').click()
        time.sleep(3)
        isformat=self.driver.find_element(By.XPATH,'/html/body/div[1]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]')
        self.assertEqual(isformat.text,result)


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

实战:测试新浪邮箱注册的几个功能

#! D:\test\pyhton code
# -*- coding:utf-8 -*-
# author:bahawood

import time
import unittest
from selenium import webdriver
from parameterized import param,parameterized
from selenium.webdriver.common.by import By

class Init(unittest.TestCase):
    def setUp(self) -> None:
        self.driver=webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.get('https://mail.sina.com.cn/')
        self.driver.implicitly_wait(30)
        nowHeadler = self.driver.current_window_handle
        time.sleep(2)
        self.driver.find_element(By.CLASS_NAME,'registerBtn').click()
        allHeadlers = self.driver.window_handles
        for hander in allHeadlers:
            # 判断是不是当前窗口
            if hander != nowHeadler:
                # 切换到新的窗口
                self.driver.switch_to.window(hander)
                time.sleep(1)
        self.driver.find_element(By.ID,'agreement1').click()
        time.sleep(3)


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

class TestSinaEmail(Init):
    # def test_sina_sign_usernamenull(self):
    #     self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
    #     usernamenull=self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[1]/div[3]/p/abbr')
    #     self.assertEqual(usernamenull.text,'请输入邮箱名')
    #
    # def test_sina_sign_wrongusername(self):
    #     self.driver.find_element(By.CLASS_NAME,'inputStyle').send_keys('fdgdfg')
    #     self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
    #     time.sleep(2)
    #     wrongusername=self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[1]/div[3]/p/abbr')
    #     self.assertEqual(wrongusername.text,'邮箱名已存在。\n可注册fdgdfg时通知我')

    @parameterized.expand([
        param('','请输入邮箱名'),
        param('fdgdfg','邮箱名已存在。\n可注册fdgdfg时通知我'),
        param('@@@','邮箱名必须是4-16个字符之间(包括4、16)')
    ])
    def test_sina_login_param(self,username,result):
        '''使用参数化覆盖上面的两个测试场景'''
        self.driver.find_element(By.CLASS_NAME,'inputStyle').send_keys(username)
        self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
        time.sleep(3)
        isformat=self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[1]/div[3]/p/abbr')
        self.assertEqual(isformat.text,result)


    def test_sina_sign_passwordnull(self):
        self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
        passwordnull = self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[2]/div[1]/p/abbr')
        self.assertEqual(passwordnull.text,'请输入密码')

    def test_sina_sign_wrongpassword(self):
        self.driver.find_element(By.CLASS_NAME,'inputNormal').send_keys('sd')
        self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
        passwordnull = self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[2]/div[1]/p/abbr')
        self.assertEqual(passwordnull.text,'密码的长度应该在6-16个字符之间')

    def test_sina_sign_passwordagain(self):
        self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
        passwordagain = self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[3]/div/p/abbr')
        self.assertEqual(passwordagain.text,'请再次输入密码')

    def test_sina_sign_checkpassword(self):
        self.driver.find_element(By.NAME,'psw').send_keys('123456789')
        self.driver.find_element(By.NAME,'npsw').send_keys('123')
        self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[3]/ul/li/div/div/a').click()
        time.sleep(5)
        passwordagain = self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div/div/div/form[1]/div[2]/ul/li[3]/div/p/abbr')
        self.assertEqual(passwordagain.text,'您两次输入的密码不一致')



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

 

 

posted on 2022-09-19 19:16  xiaobia1  阅读(195)  评论(0)    收藏  举报