unittest单元测试框架

概述:

官网帮助文档:https://docs.python.org/zh-cn/3/library/unittest.html

单元测试框架主要用来完成以下三件事:

  • 提供用例组织与执行:当测试用例只有几条时,可以不必考虑用例的组织,但是当用例达到成百上千条时,大量的用例堆砌在一起,就产生了扩展性与维护性等,单元测试框架就是用来解决用例的规范与组织问题。
  • 提供了丰富的比较方法:不论是功能测试,还是单元测试,在用例执行完成之后都需要将实际结果与预期结果进行比较(断言),从而判定用例是否执行通过。单元测试框架一般会提供丰富的断言方法。例如:判断相等/不等、包含/不包含、True/False的断言方法等。
  • 提供丰富的日志:当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后提供丰富的执行结果。例如:,总执行时间、失败用例数、成功用例数等。

一、unittest介绍

什么是单元测试?

单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。

例如:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 计算器类
class Count:
def __init__(self,a,b):
self.a = int(a)
self.b = int(b)
# 计算加法
def add(self):
return self.a + self.b

Test.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from calculator import Count class TestCount(unittest.TestCase): def setUp(self):
print("test start") def test_add(self):
  j = Count(2,3)
  self.assertEqual(j.add(),5) def tearDown(self):
  print("test end ") if __name__ == '__main__':
unittest.main()

 Test Case

一个TestCase的实例就是一个测试用例;

什么是测试用例?

就是一个完整的测试流程,包含测试前环境的搭建(setUp) --->实现测试过程的代码(run)---->以及测试后环境的还原(tearDown),一个测试用例就是一个完整的测试单元,通过运行这个测试单元,可以对某一个功能进行验证。

Test Suite

一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了测试套件TestSuite的概念。Test Suite用来组装单个测试用例。可以通过addTest加载TestCase到TestSuite中,从而返回一个TestSuite实例。

Test Runner

测试的执行也是单元测试中非常重要的一个概念,一般单元测试框架中都会提供丰富的执行策略和执行结果。在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行test suite/test case 。test runner可以使用图形界面、文本界面、或返回一个特殊的值等方式来表示测试执行的结果。

Test Fixture

对一个测试用例环境的搭建和销毁,就是一个Fixture,通过覆盖TestCase的setUp()和tearDown()方法来实现。

作用:例如:测试过程中需要访问数据库,那么可以在setUp()中通过建立数据库连接来进行初始化,在tearDown()中清除数据库产生的数据,然后关闭连接。

ps:tearDown的过程很重要,要为下一个testcase留一个干净的环境。

例子:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from calculator import Count class TestCount(unittest.TestCase): def setUp(self):
print("test start") def test_add(self):
j = Count(2,3);
print("执行了111")
self.assertEqual(j.add(),5) def test_add2(self):
print("执行了222")
j = Count(12,89)
self.assertEqual(j.add(),101) def tearDown(self):
print("test end ") if __name__ == '__main__': #构造测试集
suite = unittest.TestSuite();
suite.addTest(TestCount("test_add"));
#执行测试
runner = unittest.TextTestRunner();
runner.run(suite)

二、断言方法

assertEqual(first,second,msg=None)

断言第一个参数和第二个参数是否相等,如果不相等则测试失败。msg为可选参数,用于定义测试失败打印的信息。

例1:

assertEqual()比较两个参数是否相等

#!/usr/bin/env python
# -*- coding:utf-8 -*- import unittest class TestCase(unittest.TestCase):
def setUp(self):
self.number = 12
self.number = int(self.number); def test_case(self):
# 判断number是否等于10,且自定义错误时输出信息
self.assertEqual(self.number,10,msg="输入值不等于10"); def tearDown(self):
pass if __name__ == '__main__':
unittest.main()

 assertTure()断言方法

判断是否为质数

#!/usr/bin/env python
# -*- coding:utf-8 -*- def is_prime(n):
if n <= 1:
return False;
# 从2开始,质数只能被1和本身整除
for i in range(2,n):
  if n % i == 0:
  return True
is_prime(10)

 test3.py

#!/usr/bin/env python
# -*- coding:utf-8 -*- import unittest
from count import is_prime class TestCase(unittest.TestCase):
   def setUp(self):
  print("test start")
 def test_case(self):
  # 返回值是否为True
  self.assertTrue(is_prime(10),msg="不是质数")  def tearDown(self):
  print("test end")
if __name__ == '__main__':
  unittest.main()

 assertIs()断言

#!/usr/bin/env python
# -*- coding:utf-8 -*- import unittest
from count import is_prime class TestCase(unittest.TestCase):
def setUp(self):
  print("test start")
  self.a = "hello"
  self.b = "hello word"
def test_case(self):
# 返回值是否为True
a = self.a
b = self.b
self.assertIn(a,b,msg="b字符串中没有包含了字符串a") def tearDown(self):
print("test end")
if __name__ == '__main__':
unittest.main()

三、组织单元测试用例

1)当我们添加被测功能和相应的用例后,setUp()和tearDown()方法中所做的事情是每个用例都一样时:

a、另写一个Mytest类封装setUp()和tearDown()方法

b、让测试类继承Mytest类;

calculator.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2020/3/26 11:57' class Count:
def __init__(self,a,b):
self.a = int(a)
self.b = int(b) def add(self): #加法计算
return self.a + self.b def sub(self):#减法
return self.a - self.b
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from calculator import Count
class Mytest(unittest.TestCase):
  def setUp(self):
   print("test case start")   def tearDown(self):
  print("test case end")
# 继承Mytest类
class TestAdd(Mytest): def test_add(self):
  j = Count(2,3)
  print("执行了111")
  self.assertEqual(j.add(),5) def test_add2(self):
  print("执行了222")
  j = Count(12,89)
  self.assertEqual(j.add(),101)
# 继承Mytest类
class TestSub(Mytest): def test_sub(self):
  j = Count(2,3)
  self.assertEqual(j.sub(),-1) def test_sub1(self):
  j = Count(71,46);
  self.assertEqual(j.sub(),25) if __name__ == '__main__':
#构造测试集
suite = unittest.TestSuite()
suite.addTest(TestAdd("test_add"))
suite.addTest(TestAdd("test_add2"))
suite.addTest(TestSub("test_sub"))
suite.addTest(TestSub("test_sub1")) #执行测试
runner = unittest.TextTestRunner()
runner.run(suite)

 discover更多测试用例

当单元测试用例到达上百个时,如果把所有的测试用例都写在一个test.py中,这个文件会越来越臃肿,后期维护起来很麻烦。需要将这些用例按照所测试的功能进行拆分,分散到不同的测试文件中。

例:

目录结构

testpro/

-------  calculator.py

-------  runtest.py

-------  testadd.py

------- testsub.py

calculator.py

#!/usr/bin/env python
# -*- coding:utf-8 -*- # 计算器类 class Count(): def __init__(self,a,b):
  self.a = int(a)
  self.b = int(b) # 计算加法
def add(self):
  return self.a + self.b def add2(self):
  return self.a + self.b ; #计算减法
def sub(self):
  return self.a - self.b

 testadd.py

#!/usr/bin/env python
# -*- coding:utf-8 -*- from calculator import Count
import unittest class TestAdd(unittest.TestCase): def setUp(self):
  print("test case start") def tearDown(self):
  print("test case end") def test_add(self):
  j = Count(2,3)
  self.assertEqual(j.add(),5) def test_add2(self):
  j = Count(20,31);
  self.assertEqual(j.add(),51) if __name__ == '__main__':
  unittest.main()

 testsub.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import unittest
from calculator import Count class TestSub(unittest.TestCase):   def setUp(self):
  print("test case start")

  def tearDown(self):
  print("test case end")

  def test_sub(self):
  j = Count(2,3)
self.assertEqual(j.sub(),-1)

  def test_sub2(self):
   j = Count(71,46)
  self.assertEqual(j.sub(),25) if __name__ == '__main__':
  unittest.main()

 runtest.py

#!/usr/bin/env python
# -*- coding:utf-8 -*- import unittest # 加载测试文件
import testadd
import testsub # 构造测试集
# suite = unittest.TestSuite();
#
# suite.addTest(testadd.TestAdd("test_add"));
# suite.addTest(testadd.TestAdd("test_add2"));
#
#
# suite.addTest(testsub.TestSub("test_sub")) ;
# suite.addTest(testsub.TestSub("test_sub2")); # TestLoader该类负责根据各种标准加载测试用例,并将它们返回给测试套件。
# discover(start_dir,pattern='test*.py',top_level_dir=None)
# start_dir:要测试的模块名或测试用例目录;
# pattern='test*.py': 表用例文件名匹配原则,此处匹配文件名以"test"开头的".py"类型的文件,星号*表示任意多个字符;
# top_level_dir = None:测试模块的顶层目录,如果没有顶层目录,默认为None。 # 定义测试用例的目录为当前目录
test_dir ='./' discover = unittest.defaultTestLoader.discover(test_dir,pattern='test*.py')
if __name__ == '__main__': # 执行测试
runner = unittest.TextTestRunner()
runner.run(discover)

PS:

  • 拆分后带来的好处,可以根据不同的功能创建不同的测试文件,甚至是不同的测试目录,测试文件中还可以将不同的小功能划分不同的测试类,在类下编写测试用例,整体结构更加清晰。
  • 但是依然没有解决添加测试用例问题,addTest()添加/删除测试用例非常麻烦,所有使用TestLoader类中提供的discover()方法可以解决这个问题;

TestLoader

该类负责根据各种标准加载测试用例,并将它们返回给测试套件,正常情况下,不需要创建这个类的实例。unittest提供了可以共享的defaultTestLoader类,可以使用其子类和方法创建实例,discover()方法就是其中之一:

discover(start_dir,pattern='test*.py',top_level_dir=None)
start_dir:要测试的模块名或测试用例目录;
pattern='test*.py': 表用例文件名匹配原则,此处匹配文件名以"test"开头的".py"类型的文件,星号*表示任意多个字符;
top_level_dir = None:测试模块的顶层目录,如果没有顶层目录,默认为None。

四、关于unittest还需要知道

1、用例执行的顺序

用例的执行顺序涉及多个层次: 在多个测试目录的情况下,先执行哪个目录?在多个测试文件的情况下,先执行哪个文件?在多个测试类的情况下,先执行哪个测试类?在多个测试方法(用例)的情况下,先执行哪个测试方法?

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Tian
import unittest; class TestBdd(unittest.TestCase):
 def setUp(self):
  print("test TestBdd")
def test_ccc(self):
  print("test_ccc") def test_aaa(self):
  print("test_aaa") def tearDown(self):
  pass class TestAdd(unittest.TestCase):   def setUp(self):
  print("test TestAdd")

  def test_bbb(self):
  print("test bbb")

  def tearDown(self):
  pass if __name__ == '__main__':
  unittest.main()

输出结果:
test TestAdd
test bbb
test TestBdd
test_aaa
test TestBdd
test_ccc

  无论执行多少次,结果都一样,通过上面的结果可以发现,unittest框架默认根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A~Z,a~z。所以,TestAdd类会优先于TestBdd类执行,test_aaa()方法会优先于test_ccc()被执行,因而它并没有按照用例从上到下的顺序执行。

对于测试目录与测试文件来说,unittest框架同样是按照这个规则来加载测试用例的。

所以我们只能通过测试用例的命名来提高被执行的优先级。

 2、跳过测试和预期失败

  • unittest.skip(reason)          无条件地跳过装饰的测试,说明跳过测试的原因;
  • unittest.skipIf(condition,reason)   跳过装饰的测试,如果条件为真;
  • unittest.skipUnless(condition,reason) 跳过装饰的测试,除非条件为真;
  • unittest.expectedFailure()  测试标记为失败。不管执行结果是否失败,统一标记为失败。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Tian
import unittest
class MyTest(unittest.TestCase): def setUp(self):
  pass
def tearDown(self):
  pass
@unittest.skip('直接跳过')
def test_skip(self):
  print("test aaa")
@unittest.skipIf(3>2,"当条件为True时跳过测试")
def test_skipIf(self):
  print("test bbb")
@unittest.skipUnless(3>2,"当条件为True时执行测试")
def test_skip_unless(self):
  print("test ccc")
@unittest.expectedFailure #不管执行结果是否失败,统一标记为失败,但不会抛出错误信息。
def test_expected_failure(self):
  self.assertEqual(2,3) if __name__ == '__main__':
  unittest.main();

 fixtures

fixtures可以形象地把它看作是夹心饼干外层的两片饼干,这两片饼干就是setUp/tearDown,中间的心就是测试用例。除此之外,unittest还提供了更大范围的fixtures,例如对于测试类和模块的fixtures。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Tian
import unittest def setUpModule():
print("test module start >>>>>>>>>>>>") def tearDownModule():
print("test module end>>>>>>>>>>>>>>") class Test(unittest.TestCase): @classmethod
def setUpClass(cls):
print("test class start==========>")
@classmethod
def tearDownClass(cls):
print("test class end===========>") def setUp(self):
print("test case start --->") def tearDown(self):
print("test case end--->") def test_case(self):
print("test case1") def test_case2(self):
print("test case2") if __name__ == '__main__':
unittest.main() 输出:
test module start >>>>>>>>>>>>
test class start==========>
test case start --->
test case1
test case end--->
test case start --->
test case2
test case end--->
test class end===========>
test module end>>>>>>>>>>>>>>

setUpModule/tearDownModule   : 在整个模块的开始与结束的时候执行。

setUpClass/tearDownClass            : 在测试类的开始与结束时被执行。

setUp/tearDown                           :在测试用例的开始与结束时被执行。

需要注意的是,setUpClass/tearDownClass 的写法稍微有些不同。首先,需要通过@classmethod进行装饰,其次方法的参数为cls。其实,cls与self并没有什么特别之处,都只表示类方法的第一个参数。

编写Web测试用例

测试目录:

test_project/

|---------test_casl/

|             |------test_baidu.py

|             |------test_youdao.py

|---------report/

|             |------login.txt

|---------runtest.py

test_baidu.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Tian from selenium import webdriver; import unittest,time class MyTest(unittest.TestCase): def setUp(self): self.driver = webdriver.Ie()
self.driver.maximize_window()
self.driver.implicitly_wait(10)
self.base_url ="https://www.baidu.com" def test_baidu(self):
driver = self.driver
driver.get(self.base_url)
driver.find_element_by_xpath(".//*[@id='kw']").clear()
driver.find_element_by_xpath(".//*[@id='kw']").send_keys("unittest")
driver.find_element_by_xpath(".//*[@id='su']").click()
time.sleep(2)
title = driver.title
self.assertEqual(title,"unittest_百度搜索") def tearDown(self):
  self.driver.quit() if __name__ == '__main__':
  unittest.main()

 test_sougo.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Tian
from selenium import webdriver;
import unittest,time; class MyTest(unittest.TestCase): def setUp(self):
self.driver = webdriver.Ie()
self.driver.maximize_window()
self.driver.implicitly_wait(10)
self.base_url = "https://www.sogou.com/" def test_youdao(self):
driver = self.driver
driver.get(self.base_url)
driver.find_element_by_xpath(".//*[@id='query']").clear()
driver.find_element_by_xpath(".//*[@id='query']").send_keys("webdriver")
driver.find_element_by_xpath(".//*[@id='stb']").click()
time.sleep(2)
title = driver.title
self.assertEqual(title,"webdriver - 搜狗搜索") def tearDown(self):
self.driver.close() if __name__ == '__main__':
unittest.main()

 runntest.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Tian import unittest
from HTMLTestRunner_2 import HTMLTestRunne
# 定义测试用例的目录为当前目录 test_dir ="./";
discover = unittest.defaultTestLoader.discover(test_dir,pattern="test*.py") if __name__ == '__main__':
runner = unittest.TestSuite()
runner.addTest(discover)
# 报告存放路径
fp = open('result.html','wb')
# # 定义测试报告
runner = HTMLTestRunner(stream=fp,title='百度搜索测试报告',description='用例执行情况')
runner.run(discover); #运行测试用例
fp.close();

unittest单元测试框架的更多相关文章

  1. unittest单元测试框架总结

    unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果.今天笔者 ...

  2. unittest单元测试框架详解

    unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果.今天笔者 ...

  3. Selenium+Python ---- 免登录、等待、unittest单元测试框架、PO模型

    1.免登录在进行测试的过程中难免会遇到登录的情况,给测试工作添加了工作量,本文仅提供一些思路供参考解决方式:手动请求中添加cookies.火狐的profile文件记录信息实现.人工介入.万能验证码.去 ...

  4. unittest单元测试框架简单说明

    unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果.今天笔者 ...

  5. Python+selenium之简单介绍unittest单元测试框架

    Python+selenium之简单介绍unittest单元测试框架 一.unittest简单介绍 unittest支持测试自动化,共享测试用例中的初始化和关闭退出代码,在unittest中最小单元是 ...

  6. unittest单元测试框架总结(转载)

    转载:https://www.cnblogs.com/yufeihlf/p/5707929.html unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该 ...

  7. 四. 引入unittest单元测试框架

    1.   安装 SeleniumIDE(firefox) (1)下载地址:https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/ (2 ...

  8. unittest 单元测试框架断言方法

    unittest单元测试框架的TestCase类下,测试结果断言方法:Assertion methods 方法 检查 版本 assertEqual(a, b)  a == b assertNotEqu ...

  9. unittest 单元测试框架

    引入 unittest 框架 相想使用unittest 框架,首先要引入unittest 包 import unittest class Baidu(unittest.TestCase): Baidu ...

随机推荐

  1. union: git command

    # switch one tag # warning: if do that, can't commit any change git clone $project_path git checkout ...

  2. servlet运行原理

  3. linux crontab 命令,最小的执行时间是一分钟,如需要在小于一分钟内重复执行

    编写shell脚本实现 crontab.sh #!/bin/bash step=2 #间隔的秒数,不能大于60 for (( i = 0; i < 60; i=(i+step) )); do $ ...

  4. Java实现图片的裁剪(包括透明背景)

    需求: 有一张位置大小的图片,现在需要根据这张原图得到指定尺寸的图片,且得到的图片要符合原先图片的比例,就是在原图的基础上等比例缩放得到图片后,在进行剪裁,这样保证得到的图片是原图的一部分,而不是将原 ...

  5. Unity打包提示UnityEditor.BuildPlayerWindow+BuildMethodException: Build failed with errors.错误

    不要将打包的输出路径设置为Assets文件夹下面即可,MD真坑 老外给出的解释: As you have noticed after you click build settings you are ...

  6. Codeforces Educational Codeforces Round 44 (Rated for Div. 2) F. Isomorphic Strings

    Codeforces Educational Codeforces Round 44 (Rated for Div. 2) F. Isomorphic Strings 题目连接: http://cod ...

  7. HTML5的Rang对象

    基本概念 Range对象代表页面上的一段连续的区域.通过Range对象,可以获取或修改网页上的任何区域. Selection与Range对象的使用 <body> <script> ...

  8. thinkphp添加数据 add()方法

    thinkphpz内置的add()方法用于向数据库表添加数据,相当于SQL中的INSERT INTO 行为添加数据 add 方法是 CURD(Create,Update,Read,Delete / 创 ...

  9. UDF函数 解码url

    背景 URL 的编码 是ASCII十六进制格式.数仓接受到前端上报的URL,要对URL字段解码. 如要将 https"Fmybook.do%3Frequest_type%3D%26type% ...

  10. 《HTTP权威指南》4-连接管理

    TCP连接 TCP/IP是全球计算机及网络设备都在使用的一种常见的分组交换网络分层协议集客户端应用程序可以打开一条TCP/IP连接.连接到可能运行在世界任何地方的服务器应用程序 TCP的可靠数据管道 ...