51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 4049|回复: 0
打印 上一主题 下一主题

[转贴] Python应用实战—基于Selenium的功能测试

[复制链接]
  • TA的每日心情
    擦汗
    5 天前
  • 签到天数: 1027 天

    连续签到: 2 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-6-17 09:21:31 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
    1.1. 单元测试覆盖task_start函数
      参考前面的单元测试例子,完成test_task_start任务下达的单元测试代码,断言状态是否变更为下达状态。
          def test_task_start(self):
              """
              测试任务下达.
              """
              #1构建模拟任务
              data={'TaskNum':100,'Source':'101','Target':'05-01-01','Barcode':'101001001008','State':1,'Priority':1,}
              task = Task(**data)
              #task.save()
              #2创建业务类对象,并调用任务分解函数
              taskBiz=TaskBiz()
              taskBiz.task_decompose(task,None)#①
              taskBiz.task_start(task)#②
              self.assertEqual(task.State,Task.STATE_RUNNING)
      ①:执行任务分解。
      ②:执行任务下达,断言任务下达是否满足状态控制要求。
      通过IDE的快捷菜单进入到cmd命令行运行单元测试,VS IDE 的Test Explore笔者用下来不是非常好用,有的时候单元测试函数刷新不出来。

      命令行单元测试执行效果:

      it's ok!
      1.2. selenium功能测试
      通过编写模仿用户操作的 Selenium 测试脚本,可以从终端用户的角度来测试应用程序,就像真实用户所做的一样。与通常的测试人员通过人工操作的方式,采用Selenium 确实能够带来效率的大幅度提升,尤其新版本发布回归测试的时候!
      1.2.1. python环境安装Selenium

      键入selenuim,点击安装链接。

      1.2.2. 安装chromedriver驱动
      如果要使用WebDriver在Chrome浏览器上进行测试时,需要从安装chromedriver驱动程序。下载网址:http://chromedriver.storage.googleapis.com/index.html
      笔者写这篇的本机环境如下:

      下载对应版本的chromedriver驱动文件后,下载后把文件解压,然后放到本机chrome浏览器文件路径里即可,如:C:\Program Files (x86)\Google\Chrome\Application
      1.3. 添加functional_test.py功能测试代码
      先加入简单的测试脚本代码,打开客户端任务列表,判断当前浏览器窗口标题是否满足断言值——“任务清单”。
      from  unittest import TestCase
      import django
      from selenium import webdriver
      class FunctionalTest(TestCase):
          @classmethod
          def setUpClass(cls): #①
              super(FunctionalTest, cls).setUpClass()
              django.setup()   
              cls.browser=webdriver.Chrome()
              cls.live_server_url = 'http://localhost:8001/task/'  

          @classmethod
          def tearDownClass(cls): #②
              cls.browser.quit()
          def test_task_list(self): #③
              self.browser.get(self.live_server_url )
              self.browser.maximize_window()
              self.browser.implicitly_wait(3)#④
              #假定网页应该包含“任务列表”的标题
             self.assertIn('任务列表',self.browser.title)
      ①:单元测试类初始化函数,执行测试时,只初始化执行一次,我们把测试需要准备的一些数据放在这里初始化。
      ②:单元测试类销毁函数,执行测试时,只销毁执行一次。
      ③:访问任务列表url,并断言窗口标题是否包含“任务列表”。
      ④:使用隐式等待3秒钟,如果selenium 提前获得返回,会提前结束等待。
      命令行运行测试:
      D:\my tfs\IndDemo>python manage.py test Task.functional_test
      System check identified no issues (0 silenced).
      DevTools listening on ws://127.0.0.1:64721/devtools/browser/0c65b5e3-1000-4233-a746-30b9142532fa
      F
      ======================================================================
      FAIL: test_task_list (Task.functional_test.FunctionalTest)
      ----------------------------------------------------------------------
      Traceback (most recent call last):
        File "D:\my tfs\IndDemo\Task\functional_test.py", line 26, in test_task_list
          self.assertIn('任务列表',self.browser.title)
      AssertionError: '任务列表' not found in ''
      ----------------------------------------------------------------------
      Ran 1 test in 6.486s
      FAILED (failures=1)
      D:\my tfs\IndDemo>

      这里我们演示一下测试驱动开发里,小步的推进的原则,先添加代码满足这个测试条件,然后再运行测试,满足当前测试了,添加新的测试断言,再添加新的代码来满足这个测试断言。测试驱动的开发对于开发新手来说确实会带来很多好处,就是不用每次考虑够多功能点,积硅步而至千里。
      <!DOCTYPE html>
      <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
      <head>
          <meta charset="utf-8" />
          <title>任务列表</title>
      </head>
      <body>
      ...

      模板文件:tasks.html
      运行功能测试:
      D:\my tfs\IndDemo>python manage.py test Task.functional_test
      System check identified no issues (0 silenced).
      DevTools listening on ws://127.0.0.1:65125/devtools/browser/94e6664d-cda5-41e1-adc7-b9820cb73241
      .
      ----------------------------------------------------------------------
      Ran 1 test in 5.677s
      OK
      D:\my tfs\IndDemo>

      这次我们收获了一个“ok”!
      1.4. 场景功能测试
      本例客户端功能测试场景中,假定WCS客户端不能手动增加新任务,只能够查看任务列表和详情,和源地址和目标地址的修改操作,以及执行分解、下达操作等。下面最后完成功能测试代码:
      from  unittest import TestCase
      import django
      from django.test import LiveServerTestCase
      from time import sleep
      from selenium import webdriver
      from selenium.webdriver.common.keys import Keys
      from Task.TaskBiz import Task
      class FunctionalTest(LiveServerTestCase):
          @classmethod
          def setUpClass(cls):
              super(FunctionalTest, cls).setUpClass()
              django.setup()   
              cls.browser=webdriver.Chrome()
              #cls.live_server_url = 'http://localhost:8001/task/'  
              #1初始化测试任务1
              data={'TaskNum':200,'Source':'101','Target':'05-01-01','Barcode':'101001001008','State':1,'Priority':1,}
              task = Task(**data)
              task.save()
              #2初始化测试任务2
              data={'TaskNum':201,'Source':'102','Target':'05-01-02','Barcode':'101001001009','State':1,'Priority':1,}
              task2 = Task(**data)
              task2.save()

          @classmethod
          def tearDownClass(cls): #②
              cls.browser.quit()
          def test_task_list(self): #③
              #print(self.live_server_url + '/task/')
              self.browser.get(self.live_server_url + '/task/' )
              self.browser.maximize_window()
              self.browser.implicitly_wait(3)#④
              #假定网页应该包含“任务列表”的标题
              self.assertIn("任务列表",self.browser.title)
              #获取table并断言table row 里是否包含初始化的任务数据
              table = self.browser.find_element_by_id('id_task_table')
              #table = self.browser.find_elements_by_tag_name('table')
              rows = table.find_elements_by_tag_name('tr')
              #表标题行
              self.assertIn("任务号",rows[0].text)         
              #表第一行数据
              self.assertIn('200',rows[1].text)        
              self.assertIn('101001001008',rows[1].text)
               #表第二行数据
              self.assertIn('201',rows[2].text)
              #对第一个执行任务分解操作
              self.browser.find_element_by_id('1-decompose').click()
              self.browser.implicitly_wait(3)
              #sleep(3)

              table = self.browser.find_element_by_id('id_task_table')
              rows = table.find_elements_by_tag_name('tr')
              #表第一行数据包含子任务数 10
              self.assertIn('10',rows[1].text)        
              self.assertIn('处理成功',rows[1].text)
              #对第一个执行任务下达操作
              self.browser.find_element_by_id('1-start').click()
              self.browser.implicitly_wait(3)
              table = self.browser.find_element_by_id('id_task_table')
              rows = table.find_elements_by_tag_name('tr')
              #表第一行数据包含子任务数 10      
              self.assertIn('执行中',rows[1].text)
              #修改第二个任务
              self.browser.find_element_by_id('2-change').click()
              self.browser.implicitly_wait(3)
              self.browser.find_element_by_name('source').send_keys('111')
              self.browser.find_element_by_name('target').send_keys('05-01-11')
              self.browser.find_element_by_name('target').send_keys(Keys.ENTER)
              self.browser.implicitly_wait(3)
              table = self.browser.find_element_by_id('id_task_table')
              rows = table.find_elements_by_tag_name('tr')
              self.assertIn('111',rows[2].text)        
          self.assertIn('05-01-11',rows[2].text)

      运行功能测试:
      D:\my tfs\IndDemo>python manage.py test Task.functional_test
      Creating test database for alias 'default'...
      System check identified no issues (0 silenced).
      DevTools listening on ws://127.0.0.1:51015/devtools/browser/82f971f5-83ba-4b55-bc19-ff700e7aedb1
      E----------------------------------------
      ----------------------------------------
      ======================================================================
      ERROR: test_task_list (Task.functional_test.FunctionalTest)
      ----------------------------------------------------------------------
      Traceback (most recent call last):
        File "D:\my tfs\IndDemo\Task\functional_test.py", line 59, in test_task_list
          self.browser.find_element_by_id('1-decompose').click()
        File "C:\Python\Python36-32\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 360, in find_element_by_id
          return self.find_element(by=By.ID, value=id_)
        File "C:\Python\Python36-32\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 978, in find_element
          'value': value})['value']
        File "C:\Python\Python36-32\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
          self.error_handler.check_response(response)
        File "C:\Python\Python36-32\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
          raise exception_class(message, screen, stacktrace)
      selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="1-decompose"]"}
        (Session info: chrome=90.0.4430.72)
      ----------------------------------------------------------------------
      Ran 1 test in 8.513s
      FAILED (errors=1)
      Destroying test database for alias 'default'...
      D:\my tfs\IndDemo>

      代码“self.browser.find_element_by_id('1-decompose').click()”这句代码找不到相应的id='1-decompose'的html element。 因此,需要改进一下模板代码如下:
      ...
              {% for task in tasks %}
              <tr>
                  <td>{{task.TaskId }}</td>
                  <td>{{task.TaskNum}}</td>
                  <td>{{task.Source}}</td>
                  <td>{{task.Target}}</td>
                  <td>{{task.Barcode}}</td>
                  <td>{{task.get_State_display}}</td>
                  <td>{{task.get_Priority_display}}</td>
                  <td>-</td>
                  <td>-</td>
                  <td>{{task.job_set.count}}</td>
                  <td><a id="{{task.TaskId}}-decompose" href="{{task.TaskId }}/decompose/">分解</a> <a id="{{task.TaskId}}-start" href="{{task.TaskId }}/start/">下达</a> <a id="{{task.TaskId}}-change" href="{{task.TaskId }}/change/">修改</a></td>
              </tr>
              {%endfor%}
      ...

      相对于每行的操作链接赋值一个唯一的id 值,重新运行功能测试:
      D:\my tfs\IndDemo>python manage.py test Task.functional_test
      Creating test database for alias 'default'...
      System check identified no issues (0 silenced).
      DevTools listening on ws://127.0.0.1:51636/devtools/browser/89f5a372-fbe3-4ecd-b581-07d813d56c55
      .
      ----------------------------------------------------------------------
      Ran 1 test in 6.224s
      OK
      Destroying test database for alias 'default'...
      D:\my tfs\IndDemo>

      功能测试运行通过,接下来我们进一步完善单元测试。
      1.5. 单元测试覆盖view
      Django test 也可以针对发布的url进行单元测试,从而覆盖url和view代码,下面我在Task/tests.py里增加 class TaskViewTest(TestCase) 类专门测试发布的url是否符合开发预期,测试代码如下:
      ...
              class TaskViewTest(TestCase):
          """Tests for the application views."""
          # Django requires an explicit setup() when running tests in PTVS
          @classmethod
          def setUpClass(cls):
              super(TaskURLTest, cls).setUpClass()
              django.setup()
              #1初始化测试任务1
              data={'TaskNum':200,'Source':'101','Target':'05-01-01','Barcode':'101001001008','State':1,'Priority':1,}
              task = Task(**data)
              task.save()
              #2初始化测试任务2
              data={'TaskNum':201,'Source':'102','Target':'05-01-02','Barcode':'101001001009','State':1,'Priority':1,}
              task2 = Task(**data)
              task2.save()
          def test_task_change(self):
              data={'source':'111','target':'05-01-11'}
              #更新第一个task的源和目标值
              response=self.client.post('/task/1/change/',data)

              model = Task.objects.get(pk=1)
              self.assertEqual(model.Source,'111')
              self.assertEqual(model.Target,'05-01-11')
              response=self.client.get('/task/')      
              self.assertIn('111',response.content.decode())
          self.assertTemplateUsed(response,'Task/tasks.html')
          def test_task_decompose(self):
              response=self.client.get('/task/1/decompose/')        
              model = Task.objects.get(pk=1)
              self.assertEqual(model.job_set.count(),10)
          def test_task_decompose(self):
              self.client.get('/task/1/decompose/')        
              model = Task.objects.get(pk=1)
              self.assertEqual(model.job_set.count(),10)
              self.client.get('/task/1/start/')        
              model = Task.objects.get(pk=1)
              self.assertEqual(model.State,Task.STATE_RUNNING)

      目前,Task APP 单元测试覆盖了所有发布的url,项目迭代推进过程中,新的改动会不会导致bug,回归运行一把单元测试,如果“红”了一片,赶紧回滚改动的代码。
      1.6. 小结
      django的单元测试每次运行都是重新构建数据库和销毁数据库,所以不用担心测试数据重复或状态的问题,数据每次运行都是按照测试逻辑来执行的。尤其功能测试基于LiveServerTestCase时,这个特点简直“香”得不要不要的。传统人工操进行的功能测试每次数据准备都够忙一阵子的,这一点也是笔者使用django爽点之一。
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-10-5 07:20 , Processed in 0.085289 second(s), 25 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表