Appearance
Unittest Web 自动化小案例
一、简介
单元测试适合白盒或者开发人员做。
单元测试框架可以用来写测试用例,也适合做Web自动化测试、App自动化测试、接口自动化测试等。
Python中有doctest、unittest、pytest、nose等单元测试框架。
(1)什么是单元测试
本质是通过一段代码去验证另一段代码,所以不要单元测试框架也可以写单元测试。
python
class Calculation:
""" 完成加法计算 """
def __init__(self, a, b):
self.a = int(a)
self.b = int(b)
# 加法
def add(self):
return self.a + self.b
python
from test.test2 import Calculation
# 测试加法
def test_add():
c = Calculation(1, 1)
rs = c.add()
# 这里我特意断言失败
assert rs == 3, '加法执行错误'
if __name__ == '__main__':
test_add()
但这种方法有问题:1是要自己定义失败的提示,2是如果某个测试函数执行失败,后面的测试函数不再执行,3是无法统计结果(编写更多代码可以解决,但偏离测试的初衷)
使用unittest编写测试用例:
python
import unittest
from test.test2 import Calculation
class TestCalculation(unittest.TestCase):
def test_add(self):
c = Calculation(1, 1)
rs = c.add()
self.assertEqual(rs, 3)
if __name__ == '__main__':
unittest.main()
这里有个坑,就是比如我想使用unittest运行单个用例,鼠标在方法名右键执行就行,运行整个的,就在main那里右键执行。(这种方式运行是如python tests那样,因为使用了unittest,默认是使用这种方式运行)
但是这种方式是没有测试报告的,必须这样(如果不知道路径,搞个简单的输出文件复制一下就行)
这样输出就有对应的结果了(也可以吧python tests的删除了,鼠标在main后,就能看到test的run了)
这里结果的含义:
- .:表示通过
- E:表示运行错误
- s:表示运行跳过
- F:表示运行失败
二、概念
Test Case:最小的测试单元,Unittest提供了TestCase用于创建测试用例
Test Suite:测试套件,用于组装一组要运行的测试,Unittest提供了TestSuite用于创建测试套件
Test Runner:用于协调测试的执行并向用户提供结果,Unittest提供了TextTestRunner用于运行测试用例,提供HTMLTestRunner用于生成HTML测试报告
Test Fixture:代表执行一个或多个测试所需的环境准备,以及关联的清理动作。如setUp、tearDown
python
import unittest
from test.test2 import Calculation
# 必须继承unittest模块的TestCase类
class TestCalculation(unittest.TestCase):
# 测试用例前置动作
def setUp(self):
print("测试开始")
# 测试用例后置动作
def tearDown(self):
print("测试结束")
# 测试方法以test开头
def test_add(self):
print("第一条测试用例")
c = Calculation(1, 1)
rs = c.add()
# 由于继承了TestCase类,可以通过self直接调用断言方法
self.assertEqual(rs, 3)
def test_sub(self):
print("第二条测试用例")
c = Calculation(1, 1)
rs = c.sub()
# 由于继承了TestCase类,可以通过self直接调用断言方法
self.assertEqual(rs, 0)
if __name__ == '__main__':
# 抛弃了这个方法
# unittest.main()
# 创建测试套件
suit = unittest.TestSuite()
suit.addTest(TestCalculation("test_add"))
suit.addTest(TestCalculation("test_sub"))
# 创建测试运行器
runner = unittest.TextTestRunner()
runner.run(suit)
好处是可以控制执行顺序,还有需要运行的用例。
三、断言
方法 | 检查 | 版本 |
---|---|---|
assertEqual(a,b) | a==b | |
assertNotEqual(a,b) | a!=b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a,b) | a is b | 3.1 |
assertIsNot(a,b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a,b) | a in b | 3.1 |
assertNotIn(a,b) | assertNotIn(a,b) | 3.1 |
assertIsInstance(a,b) | Isinstance(a,b) | 3.1 |
assertNotIsInstance(a,b) | Not Isinstance(a,b) | 3.1 |
可以通过这种方式推出断言的使用:
python
import unittest
class TestAssert(unittest.TestCase):
def test_equal(self):
self.assertEqual(1+1, 2)
if __name__ == '__main__':
unittest.main()
四、其他相关
(1)一个测试类对应一个被测试功能,如加法类,里面有很多方法,如整数相加、小数相加等
格式:
unittest中的TestLoader类提供了discover()方法可以从多个文件中查找测试用例,
不需要创建该类实例,unittest提供了可共享的defaultTestLoader类,可以使用其子类或方法创建实例,再调用discover方法
python
import unittest
# 测试用例目录
test_dir = './test'
# 测试用例目录、匹配以test开头的多个文件、还有一个参数是top_level_dir=None:测试模块的顶层目录,若没有则默认是None
suits = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suits)
(2)测试用例的执行顺序
测试用例的执行顺序涉及多个层级:多个测试目录>多个测试文件>多个测试类>多个测试方法(测试用例)
unittest默认根据ASCII码的顺序加载测试用例(0~9,A~Z,a~z),这是main()方法执行的规律
discover()方法和main()一样,对于测试目录、测试文件同样适用
(3)执行多级目录的测试用例
多个目录,如果discover()方法中的start_dir参数定义为“./test”目录,为了能查到更下层的目录,可以在每个子目录放一个__init__.py文件。作用是将一个目录记为一个标准的python模块。
(4)跳过测试和预期失败
不仅仅可以作用于方法,类也可以,下面是作用于方法
python
import unittest
class MyTest(unittest.TestCase):
# 无条件跳过,需要说明跳过测试的原因
@unittest.skip("直接跳过测试")
def test_skip(self):
print("1")
@unittest.skipIf(2 > 1, "条件为真时跳过测试")
def test_skip_if(self):
print("2")
@unittest.skipUnless(2 > 1, "条件为真时执行")
def test_skip_unless(self):
print("3")
# 不管执行结果是否失败,都将测试标为失败,但不会抛出失败信息(这里我看结果的符号是u)
@unittest.expectedFailure
def test_expected_failure(self):
print("4")
if __name__ == '__main__':
unittest.main()
(5)Fixture
夹心饼干的饼干,setIp和tearDown,还有更大范围的,如测试类和模块的fIxture
python
import unittest
# setUpModule和tearDownModule在整个模块的开始与结束执行
def setUpModule():
print("1")
def tearDownModule():
print("1-end")
class MyTest(unittest.TestCase):
# setUpClass和tearDownClass在测试类的开始和结束执行
# 都是类方法,使用classmethod装饰,cls可以理解为和self一样的表示第一个参数的意思
@classmethod
def setUpClass(cls):
print("2")
@classmethod
def tearDownClass(cls):
print("2-end")
# setUp和tearDown在测试用例的开始和结束执行,所以先2-测试1-2-end,再2-测试2-2-end
def setUp(self):
print("3")
def tearDown(self):
print("3-end")
def test_case1(self):
print("测试1")
def test_case2(self):
print("测试2")
if __name__ == '__main__':
unittest.main()
五、web自动化小案例
python
import unittest
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestBaiDu(unittest.TestCase):
# 使用这个比setUp好,不用每执行一次用例就重启和关闭一次浏览器,提高效率
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
cls.test_url = "https://www.baidu.com"
# 封装重复代码(不是test开头,不会当成用例去执行)
def baidu_search(self, search_key):
self.driver.get(self.test_url)
self.driver.find_element(By.ID, "kw").send_keys(search_key)
self.driver.find_element(By.ID, "su").click()
sleep(2)
# 用例1
def test_search_haha(self):
search_key = "haha"
self.baidu_search(search_key)
title = self.driver.title
self.assertEqual(title, search_key+"_百度搜索")
# 用例2
def test_search_hehe(self):
search_key = "hehe"
self.baidu_search(search_key)
title = self.driver.title
self.assertEqual(title, search_key+"_百度搜索")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main()