UI自动化测试-UnitTest测试框架
一、金字塔模型
所谓金字塔模型,越往下的测试投入资源越高,当然得到的回报也越大。在一般在测试工作中,最底层的单元测试往往是很难去完成的,但是要想学好自动化测试,单元测试的框架以及单元测试的知识体系是作为一名自动化测试工程师必备的技能。
在Python语言中,单元测试框架有UnitTest标准库。
二、白盒测试
从软件架构的层面来说,测试最核心的步骤就是在软件开发过程中。
就软件本身而言,软件的行为或者功能是软件细节实现的产物,这些最终产物是要交付给用户的东西,所以在早期执行测试的系统有可能是一个可测试和健壮的系统,它给用户提供的功能往往是让人满意的结果。因此给予这样的⻆度,开始执行测试的最佳方法是来自源代码,也就是开发人员编写软件的过程。由于源代码是对开发人员是可见的,这样的一个测试过程我们可以称为白盒测试。
三、自动化测试用例编写
不管基于什么样的测试框架,自动化测试用例的编写都需要按照下图的规则来编写:
四、UnitTest测试框架
UnitTest的核心组件:
测试用例:就是测试类里面编写的测试方法,它的英文单词是TestCase
测试固件:初始化和清理,使用到的方法分别是setUp()和tearDown()
测试套件:TestSuite,就是测试用例的集合,在一个测试套件可以有很多的测试用例
测试执行:TestRunner,执行测试套件或者测试用例
测试报告:TestReport,就是执行所有测试用例后的结果信息
4-1:UnitTest的导入格式为:
import unittest
4-2:测试用例
1 import unittest 2 from selenium import webdriver #包:selenium,模块:webdriver 3 from selenium.webdriver.common.by import By 4 import time as t 5 6 def add(a,b): 7 return a+b 8 class TestAdd(unittest.TestCase): 9 10 def test_add_int(self): 11 assert add(1,2)==3 #中间部分就是测试用例 12 13 def test_wwt(self): 14 pass 15 if __name__ == '__main__': 16 unittest.main() #在unittest里执行测试用例
运行结果如下:
Ran 2 tests in 0.002s OK Process finished with exit code 0
上述的测试用例执行顺序:test_add_int()-->test_wwt()
这是因为,在unittest中,测试点的执行顺序是依据ASCII码来执行的,也就是说根据ASCII码的顺序加载,数字与字母的顺序为:0-9,A-Z,a-z,所以以A开头的测试用例方法会优先执行,以a开头会后执行。也就是根据数字的从小到大执行的,切记数字的大小值的是不包含test,比的是test后面的测试点的大小顺序。
unittest是单元测试框架,使用它的注意事项:
a、测试类建议以Test开头,如第8行
b、每个测试类必须要继承unittest模块中的TestCase类,这样就可以直接调用里面的方法,如第8行
知识点回顾:如果不想继承该类,而又想调用该类的方法,我们应该:进行类的对象实例化即可。
c、在测试类里面编写的方法叫测试方法,测试方法必须是以test开头的,如第10行
4-3:测试套件
顾名思义,就是有许多测试用例的集合体。
实战:以百度首页的相关内容为例:
1 import unittest 2 from selenium import webdriver #包:selenium,模块:webdriver 3 from selenium.webdriver.common.by import By 4 5 class TestBaidu(unittest.TestCase): 6 def setUp(self) -> None: 7 self.driver=webdriver.Chrome() 8 self.driver.maximize_window() 9 self.driver.get("http://www.baidu.com/") 10 self.driver.implicitly_wait(20) 11 12 def tearDown(self) -> None: 13 self.driver.quit() 14 15 def test_baidu_title(self): #测试用例1 16 '''百度首页的标题验证''' 17 #assert self.driver.title=='百度一下,你就知道' 18 self.assertEqual(self.driver.title,'百度一下,你就知道') 19 self.assertTrue(self.driver.title,'百度一下,你就知道') 20 self.assertIn(self.driver.title,'百度一下,你就知道') 21 22 23 def test_baidu_url(self): #测试用例2 24 '''百度首页的URL验证''' 25 #assert self.driver.current_url=='https://www.baidu.com/' #url执行时,网址必须使用https,否则会认为是非法网站 26 self.assertTrue(self.driver.current_url,'https://www.baidu.com/') 27 self.assertEqual(self.driver.current_url,'https://www.baidu.com/') 28 self.assertIn(self.driver.current_url,'https://www.baidu.com/') 29 30 31 def test_baidu_suosuo(self): #测试用例3 32 '''百度首页的搜索框里的输入内容验证''' 33 suosuo=self.driver.find_element(By.ID,'kw') 34 suosuo.send_keys('接口测试') 35 self.assertEqual(suosuo.get_attribute('value') ,'接口测试') 36 37 38 def test_baidu_enable(self): #测试用例4 39 '''百度首页的里的搜索框是否可以编辑''' 40 suosuo = self.driver.find_element(By.ID, 'kw') 41 self.assertTrue(suosuo.is_enabled()) 42 43 if __name__ == '__main__': 44 unittest.main()
执行顺序:先执行setUp(),再执行测试用例,最后释放tearDown()
其中:
a:你的鼠标放到那个测试用例的地方,就会执行这个测试用例的内容。
b:上述代码里使用到了断言:(之所以使用断言是因为,它可以让测试人员更加明确的知道,实际与期望的结果之间的问题。)
assertEqual():比较的是两个对象的==关系
assertIn():比较的是两个对象的包含关系
assertIs():比较两个字符串的内存地址
bool判断:assertTrue()、assertFalse()
4-4:测试固件
测试固件的主体结构1如下:
import unittest class TestBaidu(unittest.TestCase): def setUp(self) -> None: passs def tearDown(self) -> None: passs
其中:setUp() and tearDown():在一个测试类里面,有多少个测试用例,它就会被执行多少次,这样不仅会对我们的浏览器增加许多负载,而且也很麻烦,对此,我们可以使用类的方法:setUpClass() and tearDownClass()
测试固件的主体结构2如下:
import unittest @classmethod def setUpClass(cls) -> None: cls.driver=webdriver.Chrome() cls.driver.maximize_window() cls.driver.get('http://www.baidu.com') cls.driver.implicitly_wait(30) @classmethod def tearDownClass(cls) -> None: cls.driver.quit()
其中:setUpClass() and tearDownClass():是类的方法,不管测试类里面有多少测试用例,它只会执行一次,这样也会方便很多,但是也会存在一个弊端,它可以在单页面里更好的运行,而在一些跳转的页面里,它就无法去执行,除非代码里提前设置好跳转的处理操作。
实战:使用setUpClass() and tearDownClass()方法
1 import unittest 2 from selenium import webdriver 3 from selenium.webdriver.common.by import By 4 from selenium.webdriver.common.action_chains import ActionChains 5 import time as t 6 7 class TestBaidu(unittest.TestCase): 8 @classmethod 9 def setUpClass(cls) -> None: 10 cls.driver = webdriver.Chrome() 11 cls.driver.maximize_window() 12 cls.driver.get('http://www.baidu.com') 13 cls.driver.implicitly_wait(30) 14 15 16 @classmethod 17 def tearDownClass(cls) -> None: 18 cls.driver.quit() 19 20 21 def test_baidu_title(self): 22 '''百度首页的标题验证''' 23 # assert self.driver.title=="百度一下,你就知道" 24 self.assertEqual(self.driver.title,'百度一下,你就知道') 25 26 27 def test_baidu_url(self): 28 '''百度首页的URL验证''' 29 # assert self.driver.current_url=="https://www.baidu.com/" 30 #等价 31 wangzhi=self.driver.current_url == 'https://www.baidu.com/' 32 self.assertTrue(wangzhi, 'https://www.baidu.com/') 33 #等价 34 # self.assertTrue(self.driver.current_url,'https://www.baidu.com/') 35 36 37 def test_baidu_suosuo(self): 38 '''百度首页的搜索框里的输入内容验证''' 39 suosuo=self.driver.find_element(By.ID,'kw') 40 suosuo.send_keys('接口测试') 41 self.assertEqual(suosuo.get_attribute('value') ,'接口测试') 42 #get_attribute()方法,通过元素节点的属性名称获取属性的值。 43 44 def test_baidu_enable(self): 45 '''百度首页里的搜索框是否可以编辑验证''' 46 suosuo = self.driver.find_element(By.ID, 'kw') 47 self.assertTrue(suosuo.is_enabled()) 48 49 50 def test_baidu_setting(self): 51 '''百度首页的搜索设置是否成功验证''' 52 setting=self.driver.find_element(By.ID,'s-usersetting-top') 53 action=ActionChains(driver=self.driver) #实例化对象 54 action.move_to_element(setting).perform() 55 t.sleep(4) 56 self.driver.find_element(By.CSS_SELECTOR,'#s-user-setting-menu > div > a.setpref.first > span').click() 57 t.sleep(4) 58 sosetting=self.driver.find_element(By.CSS_SELECTOR,'#wrapper > div.bdlayer.s-isindex-wrap.new-pmd.pfpanel > div > div > ul > li.item.cur') 59 self.assertEqual(sosetting.text,'搜索设置') 60 61 if __name__ == '__main__': 62 unittest.main()
4-4-1:通过上述的代码,我们发现我们的代码还是有一些不美观和冗余,因此,我们可以自定义,步骤如下:
step1:我们首先在PyCharm里新建一个”init.py“文件,将我们的测试固件的整体放到这个文件里,这里的内容如下:
import unittest from selenium import webdriver #包:selenium,模块:webdriver class Init(unittest.TestCase): def setUp(self) -> None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get("http://www.baidu.com/") self.driver.implicitly_wait(20) def tearDown(self) -> None: self.driver.quit()
这里,使用继承的思想,把测试固件分离,这样的好处是只需要在一个地方维护测试固件,而不是多个地方维护。其他的测试类只需要继承测试固件就可以使用了。
step2:再新建一个文件,来存放我们剩余的代码,内容如下:
1 import unittest 2 from selenium.webdriver.common.by import By 3 from selenium.webdriver.common.action_chains import ActionChains 4 from test.init import Init 5 import time as t 6 7 class TestBaidu(Init): 8 def test_baidu_title(self): 9 '''百度首页的标题验证''' 10 assert self.driver.title=='百度一下,你就知道' 11 12 13 def test_baidu_url(self): 14 '''百度首页的URL验证''' 15 wangzhi=self.driver.current_url=='https://www.baidu.com/' 16 self.assertTrue(wangzhi,'https://www.baidu.com/') 17 18 19 def test_baidu_suosuo(self): 20 '''百度首页的搜索框里的输入内容验证''' 21 suosuo=self.driver.find_element(By.ID,'kw') 22 suosuo.send_keys('接口测试') 23 self.assertEqual(suosuo.get_attribute('value') ,'接口测试') 24 25 if __name__ == '__main__': 26 unittest.main()
4-5:综合实战-->以新浪邮箱为例
1 import unittest 2 from selenium import webdriver #包:selenium,模块:webdriver 3 from selenium.webdriver.common.by import By 4 import time as t 5 6 7 class TestBaidu(unittest.TestCase): 8 def setUp(self) -> None: 9 self.driver=webdriver.Chrome() 10 self.driver.maximize_window() 11 self.driver.get("https://mail.sina.com.cn/") 12 self.driver.implicitly_wait(20) 13 14 def tearDown(self) -> None: 15 self.driver.quit() 16 17 def test_username_null(self): 18 '''新浪邮箱的[请输入邮箱名]验证''' 19 self.driver.find_element(By.LINK_TEXT,'登录').click() 20 t.sleep(3) 21 divText=self.driver.find_element(By.CSS_SELECTOR,'body > div.mailLoginBox > div > div.mainBox.bg1 > div > div > div:nth-child(6) > div.loginBox > div.freeMailbox > div.freeError > span.loginError.tip11') 22 self.assertEqual(divText.text, '请输入邮箱名') 23 24 25 def test_email_format(self): 26 '''新浪邮箱的[您输入的邮箱名格式不正确]验证''' 27 self.driver.find_element(By.ID,'freename').send_keys('sdgj45') 28 self.driver.find_element(By.ID,'freepassword').send_keys('asd') 29 self.driver.find_element(By.LINK_TEXT,'登录').click() 30 t.sleep(3) 31 divText=self.driver.find_element(By.CSS_SELECTOR,'body > div.mailLoginBox > div > div.mainBox.bg1 > div > div > div:nth-child(6) > div.loginBox > div.freeMailbox > div.freeError > span.loginError.tip11') 32 self.assertEqual(divText.text,'您输入的邮箱名格式不正确') 33 34 35 def test_username_passwd_error(self): 36 '''新浪邮箱的[登录名或密码错误]验证''' 37 self.driver.find_element(By.ID,'freename').send_keys('SDGsg@sina.com') 38 self.driver.find_element(By.ID,'freepassword').send_keys('asd') 39 self.driver.find_element(By.LINK_TEXT,'登录').click() 40 t.sleep(4) 41 divText=self.driver.find_element(By.CSS_SELECTOR,'body > div.mailLoginBox > div > div.mainBox.bg1 > div > div > div:nth-child(6) > div.loginBox > div.freeMailbox > div.freeError > span.loginError.tip11') 42 self.assertEqual(divText.text,'登录名或密码错误') 43 44 if __name__ == '__main__': 45 unittest.main()
观察上述代码,我们可以发现,我们的代码,里面出现了很多重复的部分,这些重复的部分,只是所谓的测试数据发生了变化,其余的均一样,这时,我们引入参数化的知识。
参数化:当在一个测试场景中,它的测试步骤均相同,测试数据不一样的时候,那么就可以使用参数化的思想来解决。
在将上述代码,改装为参数化之前,我们需要引入一个第三方库:parameterized。
它的安装命令为:
pip install parameterized
它的导入格式为:
from parameterized import parameterized, param
改装如下:
import unittest from selenium import webdriver from selenium.webdriver.common.by import By from parameterized import parameterized, param import time as t class TestBaidu(unittest.TestCase): def setUp(self) -> None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.get("https://mail.sina.com.cn/") self.driver.implicitly_wait(20) def tearDown(self) -> None: self.driver.quit() @parameterized.expand([ param("","","请输入邮箱名"), param("sdgj45", "asd", "您输入的邮箱名格式不正确"), param("SDGsg@sina.com", "asd", "登录名或密码错误") ]) def test_username_passwd_error(self,username,password,result): self.driver.find_element(By.ID,'freename').send_keys(username) self.driver.find_element(By.ID,'freepassword').send_keys(password) self.driver.find_element(By.LINK_TEXT,'登录').click() t.sleep(4) divText=self.driver.find_element(By.CSS_SELECTOR,'body > div.mailLoginBox > div > div.mainBox.bg1 > div > div > div:nth-child(6) > div.loginBox > div.freeMailbox > div.freeError > span.loginError.tip11') self.assertEqual(divText.text,result) if __name__ == '__main__': unittest.main()
4-4:测试执行
正如字面意思所言,管理和运行测试用例的对象。
测试执行的原则:自动寻找到被执行的测试模块,然后根据测试模块寻找到测试类中符合要求的测试方法来进行执行。
4-5:测试报告
UnitTest生成测试报告需要借助第三方的库HtmlTestRunner.py的文件
我们在test的模块下,编写了很多的测试用例,但是实际的工作环境中,我们总不能按测试模块来执行,我们都是加载所有的测试模块来执行并且最终生成基于HTML的测试报告,测试报告会使用到第三方的库HTMLTestRunner。下载的地址为:https://github.com/tungwaiyip/HTMLTestRunne
一个大体的测试报告流程如下:
step1:将我们下载好的"HTMLTestRunner.py"文件,放到我们安装Python路径下的"Lib"文件夹下面:
step2:在我们的PyCharm的test包里,新建一个名为”run.py“的文件,在该文件里导入它试试,如果无报错,即成功,导入内容如下:
import HTMLTestRunner
step3:再在我们的test包里,新建一个名为”report“的文件夹,在该文件夹里新建一个存放我们测试报告的html报告,假设取名为”index1.html“。
step4:在名为”run.py“的文件里输入如下代码并运行就可以生成我们想要的测试报告:
import unittest import HTMLTestRunner import os def allTests(): suite=unittest.TestLoader().discover( start_dir=os.path.dirname(__file__), pattern='test_*.py') return suite def run(): #路径一定要拼接正确 filename=os.path.join(os.path.dirname(__file__),'report','index1.html') #红色标注的为我们生成测试文档的名字 fp=open(filename,'wb') runner=HTMLTestRunner.HTMLTestRunner( stream=fp, title='百度首页和新浪邮箱自动化测试', description='自动化测试内容' ) runner.run(allTests()) if __name__ == '__main__': run()
注意在执行上述代码之前,我们在上图的”test“包里的测试模块,都要执行过。
step5:运行完成后,我们用浏览器的方式打开”index1.html“,打开内容如下: