1.unittest框架入门
1.1 要测的代码
其实不用测试框架也是可以完成脚本的。举个例子:开发写了一段代码如下
#calculator.py
#整数的加法运算
class Count:
#给类的实例变量赋初始值
def __init__(self,a,b):
self.a=int(a)
self.b=int(b)
#计算实例变量的和并返回
def add(self):
return self.a+self.b
1.2 不用框架的测试脚本
要测试上面这段代码,不用框架的话是这么写的:
#testcount.py
from calculator import Count
class TestCount:
def test_add(self):
try:
c=Count(2,3)
s=c.add()
assert s==5,'test fail' #断言格式:assert 表达式,'报错信息',表达式如果不成立,则抛出异常错误,给出后面的报错信息,如果成立,则测试通过
except AssertionError as msg:
print(msg)
else:
print('test pass')
mytest=TestCount()
mytest.test_add()
可以看出来,不用测试框架的时候辅助代码会写的比较多,最主要的一个缺点是不同的人写出来的代码不一样,为了让我们的单元测试代码更容易维护和编写,所以才引入了单元测试框架。
1.3 使用单元测试框架写脚本
据我所知,python的单元测试框架有unittest、pytest等,这篇笔记以unittest为例。
#testcount1.py
import unittest
from calculator import Count
class TestCount(unittest.TestCase): #使用unittest写单元测试时,创建的单元测试用例的类必须继承unittest的TestCase类
def setUp(self): #setUp()方法用于测试用例执行前的初始化工作
print('test start')
def tearDown(self): #tearDown()方法用于测试用例执行后的清理工作
print('test end')
def test_add(self): #使用unittest提供的assertEqual()方法对add的返回值进行断言
c=Count(2,3)
self.assertEqual(c.add(),5)
if __name__=='__main__':
unittest.main() #unittest提供了main()方法,main()方法可以自己去搜索所有包含再当前模块的所有以test开头的测试方法,并自动执行它们。
注意:1.使用unittest组织的单元测试里,可以自动执行的方法有:setUp(),tearDown(),以test打头的方法,除此之外的其他方法不会自动执行。
2.运行有两种方式:一种是上面这种直接通过unittest.main()来加载所有的测试用例进行执行,还有一种是将所有的测试用例添加到测试套件中,然后执行。
Python语言编写的WebDriver测试脚本通常使用单元测试框架来运行,由此可以看出单元测试框架的主要是提供了:
(1)测试用例的组织与执行
(2)丰富的断言方法
(3)丰富的日志信息
1.4 unittest里的4个重要概念
(1)Test Case(测试用例)
测试用例指的是一个完整的测试流程,包括测试前的准备工作、测试过程即测试后的环境清理
(2)Test Suite(测试套件)
一个功能的验证往往需要多条测试用例,将许多的测试用例组织在一起执行,就产生了测试套件的概念。Test Suite可以用来组装单个的测试用例
(3)Test Runner(测试运行器)
一个Test Runner由执行设定的测试用例和将测试结果提供给用户两部分功能组成
(4)Test Fixture(测试固件)
一个Test Fixture代表一个或多个测试执行前的准备工作和测试结束后的清理工作
下面的栗子是将测试用例添加到测试套件中进行加载的示例:
import unittest
from calculator import Count
class TestCount(unittest.TestCase):
def setUp(self):
print('test start')
def tearDown(self):
print('test end')
def test_add1(self):
c=Count(2,3)
self.assertEqual(c.add(),5)
def test_add2(self):
c=Count(20,30)
self.assertEqual(c.add(),50)
if __name__=='__main__':
#调用unittest的TestSuite类来创建测试套件(根据TestSuite类生成一个对象/实例)
suite=unittest.TestSuite()
#使用测试套件提供的addtest方法将测试用例加载进来
suite.addTest(TestCount("test_add1"))
suite.addTest(TestCount("test_add2"))
#执行测试(根据TestRunner生成一个对象,调用对象的run方法)
runner=unittest.TextTestRunner()
runner.run(suite)
1.5 unittest常用的断言方法
assertEqual(a,b) 判断a和b是否相等,相等则测试通过,不相等则测试失败
assertNotEqual(a,b) 判断a和b是否不相等,相等则测试失败,不相等则测试失败
assertTrue(x) 判断表达式x是否为True(成立),成立则测试通过,不成立则测试失败
assertFalse(x)
assertIs(a,b) 判断a,b是否为同一个对象,是则测试通过,不是则测试失败
assertIsNot(a,b)
assertIsNone(x):判断x是否为None,不是则测试失败
assertIsNotNone(x)
assertIn(a,b):判断a是否在b中,不在则测试失败
assertNotIn(a,b)
assertIsInstance(a,b):判断a是否是b的实例,不是则测试失败
assertNotIsInstance(a,b)
1.6 跳过测试和预期失败
在运行测试时,有时需要跳过某些测试用例或者将某些测试用例标记为预期失败。unittest提供了实现这些需求的装饰器。
装饰器:本质上是一个python函数,它可以让它所修饰的函数在不做任何代码变动的前提下增加额外的功能。
(1)@unittest.skip(原因)
无条件的跳过该测试用例,说明跳过测试的原因
(2)@unittest.skipIf(条件,原因)
如果条件成立,则跳过该测试用例
(3)@unittest.skipUnless(条件,原因)
如果条件成立,则不跳过该测试用例,否则跳过
(4)@unittest.expectedFailure()
不管执行结果是否失败,该用例标记为失败
示例:
#testzsq.py
import unittest
class MyTest(unittest.TestCase):
@unittest.skip('无条件跳过该测试')
def test_skip(self):
print('print a')
@unittest.skipIf(2>1,'当条件成立时,跳过该测试')
def test_skipif(self):
print('print b')
@unittest.skipUnless(2>1,'当条件成立时,执行该测试')
def test_skipunless(self):
print('print c')
@unittest.expectedFailure
def test_expectedfailure(self):
self.assertEqual(2,3)
if __name__=='__main__':
unittest.main()
2.unittest进阶
2.1 单元测试用例的组织与执行
(1)扩展calculator.py,增加整数的减法功能
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
(2)扩展testcount.py,增加针对整数减法功能的测试用例
import unittest
from calculator import Count
class TestAdd(unittest.TestCase):
def setUp(self) -> None:
print('test start')
def tearDown(self) -> None:
print('test end')
def test_add1(self):
c=Count(2,3)
self.assertEqual(c.add(), 5)
def test_add2(self):
c=Count(4,5)
self.assertEqual(c.add(),9)
class TestSub(unittest.TestCase):
def setUp(self) -> None:
print('test start')
def tearDown(self) -> None:
print('test end')
def test_sub1(self):
c=Count(2,3)
self.assertEqual(c.sub(),-1)
def test_sub2(self):
c=Count(4,5)
self.assertEqual(c.sub(),-1)
if __name__ == '__main__':
# unittest.main()
#构造测试套件
#调用unittest的TestSuite()类来创建测试套件
suite=unittest.TestSuite()
#使用测试套件提供的addtest方法将测试用例加载进来
suite.addTest(TestAdd("test_add1"))
suite.addTest(TestAdd("test_add2"))
suite.addTest(TestSub("test_sub1"))
suite.addTest(TestSub("test_sub2"))
#执行测试
runner=unittest.TextTestRunner()
runner.run(suite)
注意:以上代码可以优化。如果每个类中的setUp()和tearDown()方法所做的事情都一样,那么可以将它们封装到一个类里,其他类继承它,就不用重复去书写。
import unittest
from calculator import Count
class MyTest(unittest.TestCase):
def setUp(self) -> None:
print('test start')
def tearDown(self) -> None:
print('test end')
class TestAdd(MyTest):
def test_add1(self):
c=Count(2,3)
self.assertEqual(c.add(), 5)
def test_add2(self):
c=Count(4,5)
self.assertEqual(c.add(),9)
class TestSub(MyTest):
def test_sub1(self):
c=Count(2,3)
self.assertEqual(c.sub(),-1)
def test_sub2(self):
c=Count(4,5)
self.assertEqual(c.sub(),-1)
if __name__ == '__main__':
# unittest.main()
#构造测试套件
#调用unittest的TestSuite()类来创建测试套件
suite=unittest.TestSuite()
#使用测试套件提供的addtest方法将测试用例加载进来
suite.addTest(TestAdd("test_add1"))
suite.addTest(TestAdd("test_add2"))
suite.addTest(TestSub("test_sub1"))
suite.addTest(TestSub("test_sub2"))
#执行测试
runner=unittest.TextTestRunner()
runner.run(suite)
随着软件功能的不断增加,对应的测试用例也会增加,如果把所有的测试用例都写在一个py文件里,那么这个文件就会变得非常大,代码的可读性和可维护性就变得很差。在实际工作中,通常我们会将测试用例按照所测试的功能进行拆分,分散到不同的文件里。
上述代码可以拆分为5个文件:
calculator.py 要测试的功能
testadd.py 整数加法功能的测试用例
testsub.py 整数减法功能的测试用例
mytest.py 所有测试用例公用的方法
runtest.py 测试用例的执行
代码示例
#-----------calculator.py 要测试的功能(复制过来)
#-----------testadd.py 整数加法功能的测试用例
import unittest
from mytest import MyTest
from calculator import Count
class TestAdd(MyTest): //继承MyTest类
def test_add1(self):
c=Count(2,3)
self.assertEqual(c.add(),5)
def test_add2(self):
c=Count(4,5)
self.assertEqual(c.add(),9)
#-----------testsub.py 整数减法功能的测试用例
import unittest
from mytest import MyTest
from calculator import Count
class TestSub(MyTest):
def test_sub1(self):
c=Count(4,3)
self.assertEqual(c.sub(),1)
def test_sub2(self):
c=Count(3,4)
self.assertEqual(c.sub(),-1)
#-----------mytest.py 所有测试用例公用的方法
import unittest
class MyTest(unittest.TestCase):
def setUp(self):
print('test start')
def tearDown(self):
print('test end')
#-----------runtest.py 测试用例的执行
import unittest
from testadd import TestAdd
from testsub import TestSub
if__name__=='__main__':
#unittest.main()
suite=unittest.TestSuite()
suite.addTest(TestAdd("test_add1"))
suite.addTest(TestAdd("test_add2"))
suite.addTest(TestSub("test_sub1"))
suite.addTest(TestSub("test_sub1"))
runner=unittest.TextTestRunner()
runner.run(suite)
新的问题:以上拆分设计没有解决添加用例的问题,当用例成百上千,通过addTest添加用例非常麻烦,这个时候,我们也可以选择使用discover()自动识别测试用例。
2.2 discovery自动发现测试用例
#runtest2.py
import unittest
if name == ‘main’:
discover=unittest.defaultTestLoader.discover(’./’,pattern=“test*.py”)
runner=unittest.TextTestRunner()
runner.run(discover)
注解:
1)unittest里的defaultTestLoader下提供的discover()方法可以自动去发现某个目录下的测试用例并加载进来;
2)discover()方法:第一个参数指明测试用例所在目录,第二个参数指明测试用例文件名的匹配原则。
2.3 测试用例的执行顺序
规律:unittest框架默认根据ASCII码(为了再计算机中区分字符给的一种编码)的顺序来加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z.
所以:Add类优先于B得到类得到执行,test_aa()优先于test_cc()被执行,并不是按照代码编写的先后顺序来执行的。示例的结果:
test bb
test aa
test cc
#testsequence.py
import unittest
class Bdd(unittest.TestCase):
def test_cc(self):
print('test cc')
def test_aa(self):
print('test aa')
class Add(unittest.TestCase):
def test_bb(self):
print('test bb')
if __name__ == '__main__':
# unittest.main()
suite=unittest.TestSuite()
suite.addTest(Add("test_aa"))
suite.addTest(Add("test_bb"))
suite.addTest(Add("test_cc"))
runner=unittest.TextTestRunner()
runner.run(suite)
注解:
1)使用unittest.main()方法来加载测试用例,或者使用unittest in testsequency.py方式,它们都是按照ASCII顺序进行用例的加载;
2)如果你希望按照特定的顺序来执行测试用例,那么有两种方式:
——给测试用例命名时,按照ASCII顺序去组织;
——使用addTest()方法加载,就会按照测试用例加载的顺序去执行。
小结:
我们通过unittest的学习,目的并不是为了编写单元测试用例,而是为了用它来组织web自动化测试用例。利用单元测试框架来组织测试用例、断言预期结果以及批量执行测试用例等功能,可以很好的辅助我们进行web自动化测试的开发。
4.使用unittest组织自动化测试脚本
import unittest
from selenium import webdriver
class MyTestCase_CCP(unittest.TestCase)
def setUp(self):
self.driver=webdriver.Chrome()
self.driver.maxmize_window()
self.base_url="..."
def test_CCP(self):
self.driver.get(self.base_url)
self.driver.find_element_by_xpath(...)
self.driver.find_element_by_xpath(...).click
time.sleep(2)
def tearDown(self):
self.driver.quit()
if__name__=='__main__':
unittest.main()