前言

unittest 是一款基于 Python 语言的单元测试框架。unittest 是 Python 开发包
中的一个标准模块,使用的时候直接导入即可。

官方文档:
https://docs.python.org/2/library/unittest.html

为啥要用框架?

  • 方便组建测试用例
  • 一键执行用例,跳过用例等
  • 并生成可视化测试报告
  • 他支持测试自动化,多个用例共享前置以及清理代码


官网文档例子:

import unittest
print(help(unittest))


语法规则:

  • 导入unittest:import unittest
  • 创建一个测试类,必须要继承unittest.TestCase类
  • 创建一个测试方法,且方法要以“test” 开头


import unittest


class IntegerArithmeticTestCase(unittest.TestCase):
    def testAdd(self):  # test method names begin with 'test'  # 测试用例的名称要以test开头
        self.assertEqual((1 + 2), 3)   # 断言,assertEqual 判断相等
        self.assertEqual(0 + 1, 1)

    def testMultiply(self):
        self.assertEqual((0 * 10), 0)
        self.assertEqual((5 * 8), 40)


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

"""
执行结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

"""


unittest的执行结果:

  • 2 : 表示执行了两条用例
  • . 表示测试用例执行通过
  • F 表示执行失败
  • E 表示执行错误
  • S 表示运行跳过



重要概念

  • Test Case:
    最小的测试单元,即测试方法。
    unittest提供了TestCase基类,我们创建的测试类要继承该基类,它可以用来创建新的测试用例。


  • Test Suite:
    测试用例、测试套件或两者的集合,用于组装一组要运行的测试。
    使用TestSuite类来创建测试套件。


  • Test Runner:
    Test Runner是一个组件,用于协调测试的执行并向用户提供结果。
    unittest提供了TextTestRunner类运行测试用例。


  • TestLoder:
    是用来加载 TestCase到TestSuite中,其中有几个loadTestsFrom_()方法,就是从各个地方寻找TestCase,创建他们的实例,然后add到TestSuite中,再返回一个TestSuite实例


  • TextTestResult:
    测试结果会保存到TextTestResult实例中,包括运行了多少用例,成功与失败多少等信息;


  • TestFixture:
    测试用例的初始化准备及环境还原,主要是setUp() 和 setDown()方法;



测试用例执行顺序

unittest默认按照ASCII码的顺序加载测试用例(包括测试目录和测试文件、测试类、测试方法),即它并不是按照测试用例的创建顺序从上到下执行的。

discover() 和 main()方法的执行顺序是一样的。故想让某个测试文件先执行,可以在命名上加以控制。

如何控制顺序?

可以通过TestSuite类的addTest()方法按照一定的顺序来加载测试用例,这样想先被执行的用例就可以先加载。

import unittest


class IntegerArithmeticTestCase(unittest.TestCase):
    def testAdd(self):  # test method names begin with 'test'
        print("加法")
        self.assertEqual((1 + 2), 3)
        self.assertEqual(0 + 1, 1)

    def testMultiply(self):
        print("乘法")
        self.assertEqual((0 * 10), 0)
        self.assertEqual((5 * 8), 40)


class SubtractionTestCase(unittest.TestCase):
    def testSubtraction(self):
        print("减法")
        self.assertEqual((5 - 3), 2)


if __name__ == '__main__':
    # 创建测试套件
    suit = unittest.TestSuite()
    suit.addTest(IntegerArithmeticTestCase("testAdd"))  # 添加测试用例,添加 加法 用例到测试套件中
    suit.addTest(SubtractionTestCase("testSubtraction"))  # SubtractionTestCase 类名称,testSubtraction 用例名称
    # 创建测试运行器
    runner = unittest.TextTestRunner()
    runner.run(suit)

"""
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
加法
减法

"""



Setup和Teardown

前置和后置
使用场景:
  • 前置: 先登录、插入数据,再执行其他用例
  • 后置: 执行完用例,退出登录,清理数据等


  • setUpModule/tearDownModule
    在整个模块的开始与结束时被执行
  • setUpClass/ tearDownClass
    在测试类的开始与结束时被执行
  • setUp/tearDown
    在测试用例的开始与结束时被执行


# coding:utf-8
import unittest

class TestCase(unittest.TestCase):
    def setUp(self):
        print('这是用例的前置处理')

    def tearDown(self):
        print('这是用例的后置处理')

    def test01(self):
        print('这是第一条用例')

    def test02(self):
        print('这是第二条用例')

    def test03(self):
        print('这是第三条用例')

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

"""
结果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK
这是用例的前置处理
这是第一条用例
这是用例的后置处理
这是用例的前置处理
这是第二条用例
这是用例的后置处理
这是用例的前置处理
这是第三条用例
这是用例的后置处理

"""


通过结果发现每执行一条用例,先执行的前置条件,然后执行用例内容,最后执行后置的条件。

正常的使用场景应该是: 登录 - 执行操作,所有操作完成后 - 退出登录

可以通过添加装饰器 @classmethod 进行执行一次前置和后置。

# coding:utf-8
import unittest

class TestCase(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        print('这是用例的前置 - 登录、插入测试数据等')
    @classmethod
    def tearDownClass(self):
        print('这是用例的后置 - 退出登录,清理数据等')

    def test01(self):
        print('这是第一条用例')

    def test02(self):
        print('这是第二条用例')


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

"""
结果:

这是用例的前置 - 登录、插入测试数据等
这是第一条用例
这是第二条用例
这是用例的后置 - 退出登录,清理数据等

"""



跳过测试和预期失败

有时候版本迭代后,部分功能暂时取消了,那么可以执行跳过用例的操作。

import unittest


class TestCase(unittest.TestCase):

    @unittest.skip("跳过这条用例")
    def test_skip(self):
        print('第一条用例')

    @unittest.skipIf(6 > 5, "当条件为真时跳过测试")
    def test_skip_if(self):
        print('第二条用例')

    @unittest.skipUnless(9 > 7, "当条件为假时跳过测试")
    def test_skip_unless(self):
        print('第三条用例')

    # 不论执行结果是什么,都将测试标记为失败
    @unittest.expectedFailure
    def test_fail(self):
        print('第四条用例')


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

"""
结果:

Ran 4 tests in 0.007s

FAILED (skipped=2, unexpected successes=1)
"""



discover执行多个测试用例

unittest.defaultTestLoader.discover()方法可以从多个文件中查找测试用例。

该类根据各种标准加载测试用例,并将它们返回给测试套件。

discover(start_dir, pattern='Test*.py', top_level_dir=None)
  • start_dir:待测试的模块名/测试用例目录;
  • discover()方法会自动根据这个参数查找测试用例文件
  • pattern:测试用例文件名的匹配原则
  • top_level_dir:测试模块的顶级目录,如果没有顶级目录,默认为None


# -*- coding:utf-8 -*-
import unittest


def allCase():
    caseDir = "D:\\code\\ApiTest\\case"  # 测试用例路径
    discover = unittest.defaultTestLoader.discover(caseDir,
                                                   pattern="test*.py")
    print(discover)
    return discover


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

discover 加载到的用例是一个 list 集合,需要重新写入到一个 list 对象 testcase 里,
这样就可以用 unittest 里面的 TextTestRunner 这里类的 run 方法去执行。

返回 TextTestRunner()类的实例,

调用 run 方法去执行 all_case()这个函数。



Web 自动化测试

import unittest
from selenium import webdriver
from time import sleep


class TestBaiduCase(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome()
        cls.driver.implicitly_wait(5)
        cls.driver.get("http://www.baidu.com")
        cls.driver.maximize_window()

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

    def search(self, text):
        self.driver.find_element_by_id("kw").clear()

        self.driver.find_element_by_id("kw").send_keys(text)
        self.driver.find_element_by_id("su").click()
        sleep(2)


    def test_search_selenium(self):
        search_key = "selenium"
        self.search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")


    def test_search_python(self):
        search_key = "python"
        self.search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")


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



unittest模块的常用方法/TestCase类常用属性

  • setUp()
    用于测试用例执行前的初始化工作。如测试用例需要访问数据库,可以在setUp()中建立数据库连接并初始化,需要登录web,可以先实例化浏览器等。
  • tearDown()
    用于测试用例执行之后的善后工作,比如关闭数据库连接,关闭浏览器等。
  • assertEqual(a, b, [msg=测试失败时打印的信息])
    断言a,b两个值是否相等,相等则测试通过。msg为可选参数,用于失败时打印信息
  • assertNotEqual(a, b, [msg])
    断言a,b两个值是否相等,不相等则测试通过.
  • assertTrue(x, [msg])
    断言x是否True,true则测试通过
  • assertFalse(x, [msg])
    断言x是否False,False则测试通过
  • assertIs(a, b, [msg])
    断言a是否是b,是则测试通过
  • assertNotIs(a, b, [msg])
    断言a是否是b,不是则测试通过
  • assertIsNone(x, [msg])
    断言x是否是None,是None则测试通过
  • assertIsNotNone(x, [msg])
    断言x是否是None,是None则测试通过
  • assertIn(a, b, [msg])
    断言a是否在b中,是则测试通过
  • assertNotIn(a, b, [msg])
    断言a是否在b中,不在则测试通过
  • assertIsInstance(a, b, [msg])
    断言a是否是b的一个实例,是则测试通过
  • assertNotIsInstance(a, b, [msg])
    断言a是否是b的一个实例,不是则测试通过