本章节我们再来说说测试,单元测试和功能测试。单元测试我们在数据验证章节简单提过了,本章我们进一步如何用单元测试来测试view的功能代码;同时,也涉及一下基于selenium的功能测试做法。笔者过去的项目上常规的功能测试都是由测试人员通过人工点击按钮的方式来完成的,这里我们利用selenium来完成,大家体会一下当功能测试可以回归的时候是啥赶脚。

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爽点之一。

python工业互联网应用实战13—基于selenium的功能测试的更多相关文章

  1. python工业互联网应用实战2—从需求开始

    前言:随着国家工业2025战略的推进,工业互联网发展将会提速,将迎来一个新的发展时期,越来越多的企业开始逐步的把产线自动化,去年年底投产的小米亦庄的智能工厂就是一个热议的新闻.小米/华为智能工厂只能说 ...

  2. python工业互联网应用实战1—SQL与ORM

    从sql到ORM应该说也是编程体系逐步演化的结果,通过类和对象更好的组织开个过程中遇到的各种业务问题,面向对象的解耦和内聚作为一套有效的方法论,对于复杂的企业应用而言确实能够解决实践过程中很多问题. ...

  3. python工业互联网应用实战3—模型层构建

    本章开始我们正式进入到实战项目开发过程,如何从需求分析获得的实体数据转到模型设计中来,变成Django项目中得模型层.当然,第一步还是在VS2019 IDE环境重创建一个工程项目,本文我们把工程名称命 ...

  4. python工业互联网应用实战15-前后端分离模式1

    我们在13章节里通过监控界面讲了如何使用jquery的动态加载数据写法,通过简单案例来说明了如何实现动态的刷新监控界面的数据,本章我们将演示如何从Django模板加载数据逐步演化到前后端分离的异步数据 ...

  5. python工业互联网应用实战14——单元测试覆盖率

    前面的章节我们完成了任务管理主要功能的开发及单元测试编写,可如何知道单元测试效果怎么样呢?测试充分吗?还有没有没有测到的地方呢? 本章节我们介绍一个统计测试代码覆盖率的利器Coverage,Cover ...

  6. python工业互联网应用实战3—Django Admin列表

    Django Admin笔者使用下来可以说是Django框架的开发利器,业务model构建完成后,我们就能快速的构建一个增删查改的后台管理框架.对于大量的企业管理业务开发来说,可以快速的构建一个可发布 ...

  7. python工业互联网应用实战7—业务层

    本章我们演示代码是如何"进化"的,实战的企业日常开发过程中,系统功能总伴随着业务的不断增加,早期简单的代码慢慢的越来越复杂,敏捷编程中的"禅"--简单设计.快速 ...

  8. python工业互联网应用实战8—django-simpleui

    笔者也使用过一段时间adminx组件,后来由于adminx停更,又遇到更简单的django-simpleui后,现在基本上只使用simpleui了,使用simpleui的几个好处,笔者认为排在第一位的 ...

  9. python工业互联网应用实战11—客户端UI

    这个章节我们将演示用户端界面的开发,当前演示界面还是采用先实现基本功能再逐步完善的"敏捷"模式.首先聚焦在功能逻辑方面实现普通用户与系统的交互,普通用户通过url能查看到当前任务的 ...

随机推荐

  1. 推荐一款好用的免费远程控制软件——ToDesk

    创作立场声明:我在本文中评测的软件为自用,感觉不错并且全免费,第一时间发出来和大家分享,欢迎理性观点交流碰撞. 疫情刚开始的时候,待在家里不能上班,但是还是有很多工作需要在线完成,常常需要跑回办公室拿 ...

  2. Python序列之列表(一)

    在Python中,列表是一种常用的序列,接下来我来讲一下关于Python中列表的知识. 列表的创建 Python中有多种创建列表的方式 1.使用赋值运算符直接赋值创建列表 在创建列表时,我们直接使用赋 ...

  3. 将VMware工作站最小化到托盘栏

    目录 前言 将VMware最小化到托盘栏的方法 1.下载 Trayconizer 2.解压 trayconizerw.zip 3.创建 VMware 快捷方式 4.修改 VMware 快捷方式 5.运 ...

  4. STL容器整理

    1.vector c++STL中的可变长度数组,主要支持操作有:建立,添加到末尾,返回长度,调整大小,定义迭代器及对迭代器的具体操作.具体如下: 1.建立一个元素类型为int的可变长度数组v,最开始N ...

  5. Win10下ctrl与alt键互换

    我之前尝试过用第三方软件修改,但是总是不成功,后来发现直接去修改注册表也不麻烦,记录一下步骤. win + r 输入 regedit 进到这个路径 点击Keyboard Layout 右键,新建一个 ...

  6. 《C++ Primer》笔记 第3章 字符串、向量和数组

    位于头文件的代码一般来说不应该使用using声明. 如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去.与之相反,如果不使用等号,则执行的是直接 ...

  7. Kafka集群消息积压问题及处理策略

    通常情况下,企业中会采取轮询或者随机的方式,通过Kafka的producer向Kafka集群生产数据,来尽可能保证Kafka分区之间的数据是均匀分布的. 在分区数据均匀分布的前提下,如果我们针对要处理 ...

  8. LeetCode-宝石与石头

    宝石与石头 LeetCode-771 使用哈希表. 这里使用内置算法库中的map /** * 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头.  * S 中每个字符代表了一种你拥有的 ...

  9. Kibana 插件环境搭建教程

    原文 环境背景, Kibana 7.4.0, Elasticsearch 7.4.0 注意, 执行以下命令时, 尽量在管理员权限的命令行窗口里执行, 避免一些没有权限的报错; 1. 准备 Kibana ...

  10. C语言float和double输入问题

    统计给定的n个数中,负数.零和正数的个数. Input    输入数据有多组,每组占一行,每行的第一个数是整数n(n<100),表示需要统计的数值的个数,然后是n个实数:如果n=0,则表示输入结 ...