草庐IT

UnitTest单元测试框架详解

大森玩测试 2023-04-22 原文

一、什么是Unittest框架

unittest是python自带的一个单元测试框架,不仅适用于单元测试,还可用于Web、Appium、接口自动化测试用例的开发与执行;此框架可以组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否执行通过,并生成测试结果。

二、Unittest框架原理

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

  1. test case:测试用例
  2. test suite:测试套件
  3. test runner: 用来执行测试用例和测试套件,并返回测试用例的执行结果
  4. TestLoader:批量执行测试用例
  5. test fixure:测试夹具

三、Unittest的使用

3.1 TestCase 测试用例

当我们在写接口用例的时候,会继承 unittest 当中的 TestCase 的类和方法,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例,unittest中的测试用例都是以 “test” 开头,并且它的执行顺序是按照方法名的ASCII值进行排序。

# test_001.py
import unittest

def add(x, y):
  return x + y

class Test_Case_01(unitest.TestCase):      # 新建测试类必须继承unittest.TestCase

  # 测试方法名称必须以 test 开头
  def test_add_01(self):  
    result = add(2, 4)
    print(f'两数和为:{result}')

  def test_add_02(self):
    result = add(5, 7)
    print(f'两数之和为:{result}')


if __name__ == '__main__':
  # 执行测试用例
  unittest.main()

3.2 TestSuite

一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了TestSuite的概念。我们可以把它理解成一个篮子,篮子里面放了很多东西(test case)。使用addTest来加载TestCase到TestSuite中。

testsuit的使用:
(1)实例化:suite = unittest.TestSuite()
(2)添加用例:suite.addTest(ClassName(“MethodName”))
(ClassName:类名,MethodName:方法名)
(3)添加扩展:suite.addTest(unittest.makeSuite(ClassName))
(搜索指定ClassName内 test 开头的方法,并添加到测试套件中)

提示: TestSuite需要配合TestRunner才能被执行

# 公众号:大森玩测试
# 案例

import unittest
from test_001 import Test_Case_01

# 实例化测试套件对象
suite = unittest.TestSuite()
# 单独添加测试用例
suite.addTest(Test_Case_01("test_add_01"))
suite.addTest(Test_Case_01("test_add_02"))

# 批量添加用例, addTest()添加的次数根据Test_Case_01中的测试用例个数决定,这里是添加2次
suite.addTest(unittest.makeSuite(Test_Case_01))

# 执行
runner = unittest.TextTestRunner()
runner.run(suite)

3.3 TextTestRunner

用来执行测试用例和测试套件,并返回测试用例的执行结果。在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行test suite/test case。它还可以用图形或者文本接口,把返回的测试结果更形象的展现出来,如:HTMLTestRunner。

使用:
(1)实例化:runner = unittest.TextTestRunner()
(2)执行:runner.run(suite) # suite为测试套件的名称,可以参考上文的代码

# 示例代码可以参考3.2 TestSuite 的案例,这里不再赘述

3.4 TestLoader

用来加载TestCase到TestSuite中,即加载满足条件的测试用例,并把测试用例封装成测试套件。

使用unittest.TestLoader(),通过该类下面的discover()方法自动搜索指定目录下指定开头的 .py 文件,并将查到的测试用例组装到测试套件。

# 示例
# 公众号:大森玩测试
import os
import unittest
# 获取根目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))   # 获取文件的根目录

# 测试用例地址
test_case_path = os.path.join(BASE_DIR, "Scripts")

suit = unittest.TestSuite()
loader = unittest.TestLoader()  # 专门用来查找用例的实例
# test_case_path:测试用例的所在路径。pattern:搜索以test开头的py文件,默认为‘test*.py’
suit.addTests(loader.discover(test_case_path, pattern="test*.py"))

runner = unittest.TextTestRunner()
runner.run(suit)

注意: 也可以使用unittest.defaultTestLoader 代替 unittest.TestLoader()

discover = unittest.defaultTestLoader.discover(test_case_path, pattern="t*.py")
suit.addTests(discover)

TestSuite和TestLoader的区别

共同点:都是测试套件

不同点:实现方式不同

(1) TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中的某个测试方法)

(2)TestLoader搜索指定目录下指定开头的py文件,并添加测试类中的所有测试方法,不能指定添加某个测试方法

3.5 Fixture

是对一个测试用例环境的初始化和销毁操作。即执行测试用例前准备环境的搭建(前置-SetUp),以及测试后环境的还原(后置-TearDown)。

控制级别:

(1)函数级别
def setUp() / def tearDown()
特性:几个测试函数,被执行几次。每个测试函数执行之前都会执行setUp,执行之后都会执行tearDown(运行一次测试方法就会运行一次 setUp 和 tearDown)

(2)类级别
def setUpClass() / def tearDownClass()
特性:测试类运行之前运行一次setUpClass;类运行之后运行一次tearDownClass

注意: 类方法必须使用 @classmethod装饰

(3)模块级别
def setUpModule() / def tearDownModule()
特性:模块运行之前执行一次 setUpModule;模块运行之后执行一次 tearDownModule

# 示例
# 公众号:大森玩测试
import time
import unittest
from selenium import webdriver

class Test_Tmall_Login(unittest.TestCase):

    def setUp(self) -> None:
        # 获取Chrome浏览器驱动对象
        self.driver = webdriver.Chrome()
        # 打开url
        self.driver.get("http://www.tmall.com")
        # 窗口最大化
        self.driver.maximize_window()
        # 隐式等待
        self.driver.implicitly_wait(30)

    def tearDown(self) -> None:
        # 等待3秒,关闭浏览器
        time.sleep(3)
        self.driver.quit()

    def test_login(self):
        driver = self.driver
        # 输入用户名
        driver.find_element(by="id", value="username").send_keys("test001")
        # 输入密码
        driver.find_element(by="id", value="password").send_keys("admin001")
        # 点击登录按钮
        driver.find_element(by="name", value="sbtbutton").click()

四、断言

1)什么是断言

让程序代替人工自动的判断预期结果和实际结果是否相符

2)为什么要学习断言

自动化脚本在执行的时候一般都是无人值守状态,我们不知道执行的结果是否符合预期结果,所以需要让程序代替人工检测程序执行的结果是否符合预期结果,这个时候就需要用到断言。

3) Unittest常用断言方法

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

断言方法在unittest.TestCase类中已经定义好了,我们自定义的测试类已经继承了 TestCase ,所以在测试方法中直接调用即可。

# 示例
# 公众号:大森玩测试
import unittest

def add(x, y):
    return x + y

class Test_Assert(unittest.TestCase):
    def setUp(self):
        print("前置操作")

    def tearDown(self):
        print("后置操作")

    # 测试用例
    def test_01(self):
        result = add(2, 4)
        # 断言
        self.assertEqual(2, result)

    def test_02(self):
        result = add(5, 7)
        is_ok = result == 12
        self.assertTrue(is_ok)

    def test_03(self):
        result = add(2, 3)
        result = str(result)
        self.assertIn(result, "1234567")

五、参数化

1)通过参数的方式来传递数据,从而实现数据和脚本分离,也可以把测试数据定义到数据文件或者数据库中

2)针对同一个测试方法,可以实现用例的重复执行,减少代码冗余,提高测试效率

3) unittest测试框架,本身不支持参数化,但是可以通过安装扩展插件parameterized来实现

安装

pip install parameterized

使用方式

导包:from parameterized import parameterized

修饰测试函数 @parameterized.expand([数据])

数据格式:

  1. 单个参数:类型为列表

  2. 多个参数:类型为列表嵌套元组

  3. 在测试函数中的参数设置变量引用参数值,注意:变量的数量必须和数据值的个数相同

六、跳过

对于一些未完成的或者不满足测试条件的测试函数和测试类,可以跳过执行。

使用方式

1.直接将测试函数标记成跳过

@unittest.skip('代码未完成')

2.根据条件判断测试函数是否跳过

@unittest.skipIf(condition, reason)

七、生成HTML测试报告

测试脚本执行完后,可以生成以 HTML( 网页 ) 格式的测试报告。

这里给大家推荐一个个人感觉不错的HTMLTestRunner版本——XTestRunner

安装方式:

pip install XTestRunner

pip install -U git+https://github.com/SeldomQA/XTestRunner.git@master

使用:

# 注意:生成HTML报告,必须使用wb,以二进制形式写入
with open(report_dir, "wb") as f :
    HTMLTestRunner(stream=f, verbosity=2, title="XXX自动化测试报告", description="欢迎关注公众号:大森玩测试")

有关UnitTest单元测试框架详解的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  4. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  5. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  6. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  7. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  8. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  9. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  10. ruby-on-rails - 如何调试 cucumber 测试? - 2

    我有:When/^(?:|I)follow"([^"]*)"(?:within"([^"]*)")?$/do|link,selector|with_scope(selector)doclick_link(link)endend我打电话的地方:Background:GivenIamanexistingadminuserWhenIfollow"CLIENTS"我的HTML是这样的:CLIENTS我一直收到这个错误:.F-.F--U-----U(::)failedsteps(::)nolinkwithtitle,idortext'CLIENTS'found(Capybara::Element

随机推荐