行为驱动:自然语言映射到程序代码 应用场景:敏捷场景里用 (注释:行为驱动在敏捷开发里的应用)
注意事项:
1.目前lettuce只支持python2;lettuce 在这个路径 C:\Python27\Scripts
步骤:前置条件 py -2 -m pip install lettuce
2.必须新建features目录,否则执行会失败,目录结构如下:
features
-steps.py(写测试脚本和自然语言的测试步骤进行对应)
-xxxx.feature (写自然语言的测试步骤的)
运行:
法一:在features上级的目录下打开cmd 执行CMD命令,进来后,输入lettuce;features这个目录文件夹 是自己创建的
法二:在cmd中,cd进入features的父目录,执行lettuce
E:\PythonDemo\Glory\20190224\features
steps.py
zero.feature
就这俩文件
steps.py
#encoding=utf-8
from lettuce import *
#用于计算整数的阶乘函数
def factorial(number):
number = int(number)
if (number == 0) or (number == 1):
return 1
else:
return reduce(lambda x, y: x * y, range(1, number + 1))
@step('I have the number (\d+)')
def have_the_number(step, number):
# 将通过正则表达式匹配的数字存于全局变量world中
world.number = int(number)
@step('I compute its factorial')
def compute_its_factorial(step):
# 从全局变量world中取出匹配的数字,
# 计算其阶乘,并将结果再存回world中
world.number = factorial(world.number)
@step('I see the number (\d+)')
def check_number(step, expected):
# 通过正则匹配到预期数字
expected = int(expected)
# 断言计算阶乘结果是否等于预期
assert world.number == expected, "Got %d" %world.number
zero.feature
Feature: Compute factorial
In order to play with Lettuce
As beginners
We’ll implement factorial
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 1
Given I have the number 1
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 2
Given I have the number 2
When I compute its factorial
Then I see the number 2
Scenario: Factorial of 3
Given I have the number 3
When I compute its factorial
Then I see the number 6
方法二“
私有,排重的方法,都不会跟测试步骤对应
第一个用函数
E:\PythonDemo\Glory\20190224\features
上面那2个文件
第2个用类的方法
E:\PythonDemo\Glory\201902241\features
steps.py zero.feature
steps.py
#encoding=utf-8
from lettuce import world, steps
def factorial(number):
number = int(number)
if (number == 0) or (number == 1):
return 1
else:
return reduce(lambda x, y: x * y, range(1, number + 1))
@steps
class FactorialSteps(object):
"""Methods in exclude or starting with _ will not be considered as step"""
exclude = ['set_number', 'get_number']
def __init__(self, environs):
# 初始全局变量
self.environs = environs
def set_number(self, value):
# 设置全局变量中的number变量的值
self.environs.number = int(value)
def get_number(self):
# 从全局变量中取出number的值
return self.environs.number
def _assert_number_is(self, expected, msg="Got %d"):
number = self.get_number()
# 断言
assert number == expected, msg % number
def have_the_number(self, step, number):
'''I have the number (\d+)'''
# 上面的三引号引起的代码必须写,并且必须是三引号引起
# 表示从场景步骤中获取需要的数据
# 并将获得数据存到环境变量number中
self.set_number(number)
def i_compute_its_factorial(self, step):
"""When I compute its factorial"""
number = self.get_number()
# 调用factorial方法进行阶乘结算,
# 并将结算结果存于全局变量中的number中
self.set_number(factorial(number))
def check_number(self, step, expected):
'''I see the number (\d+)'''
# 上面的三引号引起的代码必须写,并且必须是三引号引起
# 表示从场景步骤中获取需要的数据以便断言测试结果
self._assert_number_is(int(expected))
FactorialSteps(world)
zero.feature
Feature: Compute factorial
In order to play with Lettuce
As beginners
We’ll implement factorial
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 1
Given I have the number 1
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 2
Given I have the number 2
When I compute its factorial
Then I see the number 2
Scenario: Factorial of 3
Given I have the number 3
When I compute its factorial
Then I see the number 6
BDD(Behavier Driven Data)使用多条用例,模拟数据库,类似于一个查询的操作
文件存储地方:
E:\PythonDemo\Glory\201902242\features
step.py
#encoding=utf-8
from lettuce import *
@step('I have the following students in my database:')
def students_in_database(step):
if step.hashes:
# 如果存在步骤表格数据,则继续后续步骤
print type(step.hashes)
assert step.hashes == [
{
'name': 'Anton',
'monthly_due': '$ 500',
'billed': 'no'
},
{
'name': 'Jack',
'monthly_due': '$ 400',
'billed': 'no'
},
{
'name': 'Gabriel',
'monthly_due': '$ 300',
'billed': 'no'
},
{
'name': 'Gloria',
'monthly_due': '$ 442.65',
'billed': 'no'
},
{
'name': 'Ken',
'monthly_due': '$ 907.86',
'billed': 'no'
},
{
'name': 'Leonard',
'monthly_due': '$ 742.84',
'billed': 'no'
},
]
@step('I bill names starting with "(.*)"')
def match_starting(step, startAlpha):
# 将通过正则表达式匹配步骤中最后一个字母,
# 并存于全局变量startAlpha中
world.startAlpha = startAlpha
print "no data exist:",step.hashes
@step('I see those billed students:')
def get_starting_with_G_student(step):
# 遍历步骤数据表中的数据
for i in step.hashes:
# 断言学生的名字是否以world.startAlpha变量存取的的字母开头
assert i["name"].startswith(world.startAlpha)
@step("those that weren't:")
def result(step):
for j in step.hashes:
# 断言学生名字不以world.startAlpha变量存取的的字母开头
# 断言学生名字不以world.startAlpha变量存取的的字母开头
assert world.startAlpha not in j["name"][0]
student.feature
Feature: bill students alphabetically
In order to bill students properly
As a financial specialist
I want to bill those which name starts with some letter
Scenario: Bill students which name starts with "G"
Given I have the following students in my database:
| name | monthly_due | billed |
| Anton | $ 500 | no |
| Jack | $ 400 | no |
| Gabriel | $ 300 | no |
| Gloria | $ 442.65 | no |
| Ken | $ 907.86 | no |
| Leonard | $ 742.84 | no |
When I bill names starting with "G"
Then I see those billed students:
| name | monthly_due | billed |
| Gabriel | $ 300 | no |
| Gloria | $ 442.65 | no |
And those that weren't:
| name | monthly_due | billed |
| Anton | $ 500 | no |
| Jack | $ 400 | no |
| Ken | $ 907.86 | no |
| Leonard | $ 742.84 | no |
sougou.feature
行为驱动 + webdriver + 数据驱动的例子
terrain 装饰器 自动执行 做准备 做清理
前提条件 :
py -2 -m pip install selenium
sogou.feature
sogou.py
terrain.py
sogou.feature
Feature: Search in Sogou website
In order to Search in Sogou website
As a visitor
We’ll search the NBA best player
Scenario: Search NBA player
Given I have the english name “<search_name>”
When I search it in Sogou website
Then I see the entire name “<search_result>”
Examples:
| search_name | search_result |
| Jordan | Michael |
| Curry | Stephen |
| Kobe | Bryant |
sogou.py
#encoding=utf-8
from lettuce import *
from selenium import webdriver
import time
@step('I have the english name "(.*)"')
def have_the_searchWord(step, searchWord):
world.searchWord = str(searchWord)
print world.searchWord
@step('I search it in Sogou website')
def search_in_sogou_website(step):
world.driver = webdriver.Ie(executable_path = "d:\\chromedriver.exe")
world.driver.get("http://www.sogou.com")
world.driver.find_element_by_id("query").send_keys(world.searchWord)
world.driver.find_element_by_id("stb").click()
time.sleep(3)
@step('I see the entire name "(.*)"')
def check_result_in_sogou(step, searchResult):
assert searchResult in world.driver.page_source, "got word:%s" %searchResult
world.driver.quit()
terrain.py
#encoding=utf-8
from lettuce import *
import logging
#初始化日志对象
logging.basicConfig(
# 日志级别
level = logging.INFO,
# 日志格式
# 时间、代码所在文件名、代码行号、日志级别名字、日志信息
format = '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
# 打印日志的时间
datefmt = '%a, %Y-%m-%d %H:%M:%S',
# 日志文件存放的目录(目录必须存在)及日志文件名
filename = 'e:/BddDataDriveRreport.log',
# 打开日志文件的方式
filemode = 'w'
)
#在所有场景执行前执行
@before.all
def say_hello():
logging.info("Lettuce will start to run tests right now...")
print "Lettuce will start to run tests right now..."
#在每个secnario开始执行前执行
@before.each_scenario
def setup_some_scenario(scenario):
# 每个Scenario开始前,打印场景的名字
print 'Begin to execute scenario name:' + scenario.name
# 将开始执行的场景信息打印到日志
logging.info('Begin to execute scenario name:' + scenario.name)
#每个step开始前执行
@before.each_step
def setup_some_step(step):
run = "running step %r, defined at %s" % (
step.sentence, # 执行的步骤
step.defined_at.file # 步骤定义在哪个文件
)
# 将每个场景的每一步信息打印到日志
logging.info(run)
#每个step执行后执行
@after.each_step
def teardown_some_step(step):
if not step.hashes:
print "no tables in the step" #会被打印9次,因为我们仅仅设定了场景的table,而不是给每个步骤设定table
#注意每个step可以有自己的table,等价于把table的数据作为一个字典传入到程序中使用。
logging.info("no tables in the step")
#在每个secnario执行结束执行
@after.each_scenario
def teardown_some_scenario(scenario):
print 'finished, scenario name:' + scenario.name
logging.info('finished, scenario name:' + scenario.name)
#在所有场景开始执行后执行
@after.all #默认获取执行结果的对象作为total参数
def say_goodbye(total):
result = "Congratulations, %d of %d scenarios passed!" % (
total.scenarios_ran, #一共多少场景运行了
total.scenarios_passed #一共多少场景运行成功了
)
print result
logging.info(result)
# 将测试结果写入日志文件
logging.info("Goodbye!")
print "------ Goodbye! ------"
基于中文的行为驱动:中文feature
baidu.feature
terrain.py
log.py
baidu.feature
在默认编码为GBK的Windows系统中执行场景使用中文描述的行为驱动测试时,打印到控制台的场景等信息,中文会出现乱码,这是由于lettuce框架将输出到控制台的场景描述信息转成UTF8编码的字符导致的。下面针对lettuce(0.2.23)版本给出具体解决方法。
(1)进入Python安装目录中lettuce安装路径中的plugins目录中,
比如本地路径为C:\Python27\Lib\site-packages\lettuce\plugins。
(2)找到该目录下的colored_shell_output.py文件,
(3)打开该文件,找到该文件的第32行代码what = what.encode(‘utf-8’)
,将其改成what = what#.encode(‘utf-8’)
程序见 E:\PythonDemo\Glory\201902244\features
baidu.feature
#encoding=utf-8
#language: zh-CN
特性: 在百度网址搜索IT相关书籍
能够搜索到书的作者,比如吴晓华
场景: 在百度网站搜索IT相关书籍
如果将搜索词设定为书的名字"<书名>"
当打开百度网站
和在搜索输入框中输入搜索的关键词,并点击搜索按钮后
那么在搜索结果中可以看到书的作者"<作者>"
例如:
| 书名 | 作者 |
| Selenium WebDriver实战宝典 | 吴晓华 |
| HTTP权威指南 | 协议 |
| Python核心编程 | Python |
terrain.py
#encoding=utf-8
from lettuce import *
from log import *
#在所有场景执行前执行
@before.all
def say_hello():
logging.info(u"开始执行行为数据驱动测试...")
#在每个secnario开始执行前执行
@before.each_scenario
def setup_some_scenario(scenario):
# 将开始执行的场景信息打印到日志
logging.info(u'开始执行场景“%s”' %scenario.name)
#每个step开始前执行
@before.each_step
def setup_some_step(step):
world.stepName = step.sentence
run = u"执行步骤“%s”, 定义在“%s”文件" % (
step.sentence, # 执行的步骤
step.defined_at.file # 步骤定义在哪个文件
)
# 将每个场景的每一步信息打印到日志
logging.info(run)
#每个step执行后执行
@after.each_step
def teardown_some_step(step):
logging.info(u"步骤“%s”执行结束" % world.stepName)
#在每个secnario执行结束执行
@after.each_scenario
def teardown_some_scenario(scenario):
logging.info(u'场景“%s”执行结束' %scenario.name)
#在所有场景开始执行后执行
@after.all #默认获取执行结果的对象作为total参数
def say_goodbye(total):
result = u"恭喜,%d个场景运行,%d个场景运行成功" % (
total.scenarios_ran, #一共多少场景运行了
total.scenarios_passed #一共多少场景运行成功了
)
logging.info(result)
# 将测试结果写入日志文件
logging.info(u"本次行为数据驱动执行结束")
log.py
#encoding=utf-8
import logging
#初始化日志对象
logging.basicConfig(
# 日志级别
level = logging.INFO,
# 日志格式
# 时间、代码所在文件名、代码行号、日志级别名字、日志信息
format = '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
# 打印日志的时间
datefmt = '%a, %Y-%m-%d %H:%M:%S',
# 日志文件存放的目录(目录必须存在)及日志文件名
filename = 'e:/BddDataDriveRreport.log',
# 打开日志文件的方式
filemode = 'w'
)
批量执行多个feature
E:\PythonDemo\Glory\201902245\features
中英文 文件
d:\chromedriver.exe
演示示例所需文件如下:
Login_Chinese.feature
Login_Chinese.py
Login_English.feature
Login_English.py
terrain.py
Login_Chinese.feature
#encoding=utf-8
#language: zh-CN
特性: 登录126邮箱和退出126邮箱登录
场景: 成功登录126邮箱
假如启动一个浏览器
当用户访问http://mail.126.com网址
当用户输入输入用户名“axu28990@126.com”和对应的邮箱密码
那么页面会出现“未读邮件”关键字
场景: 成功退出126邮箱
当用户从页面单击退出链接
那么页面显示“您已成功退出网易邮箱”关键内容
Login_Chinese.py
#encoding=utf-8
#language: zh-CN
from lettuce import *
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
@step(u'启动一个浏览器')
def open_browser(step):
try:
# 创建Chrome浏览器的driver实例,并存于全局对象world中,
# 供后续场景或步骤函数使用
world.driver = webdriver.Chrome(executable_path = "d:\\chromedriver.exe")
except Exception, e:
raise e
@step(u'用户访问(.*)网址')
def visit_url(step, url):
print url
world.driver.get(url)
@step(u'用户输入输入用户名“(.*)”和密码“(.*)”')
def user_enters_UserName_and_Password(step, username, password):
print username, password
# 浏览器窗口最大化
world.driver.maximize_window()
time.sleep(3)
# 切换进frame控件
world.driver.switch_to.frame(world.driver.find_element_by_xpath("//iframe[contains(@id,'x-URS-iframe')]"))
# 获取用户名输入框
userName = world.driver.find_element_by_xpath('//input[@name="email"]')
userName.clear()
# 输入用户名
userName.send_keys(username)
# 获取密码输入框
pwd = world.driver.find_element_by_xpath("//input[@name='password']")
# 输入密码
pwd.send_keys(password)
# 发送一个回车键
pwd.send_keys(Keys.RETURN)
# 等待15秒,以便登录后成功进入登录后的页面
time.sleep(15)
@step(u'页面会出现“(.*)”关键字')
def message_displayed_Login_Successfully(step, keywords):
# print world.driver.page_source.encode('utf-8')
# 断言登录成功后,页面是否出现预期的关键字
assert keywords in world.driver.page_source
# 断言成功后,打印登录成功信息
print "Login Success"
@step(u'用户从页面单击退出链接')
def LogOut_from_the_Application(step):
print "====",world.driver
# time.sleep(5)
# 点击退出按钮,退出登录
world.driver.find_element_by_link_text(u"退出").click()
time.sleep(8)
@step(u'页面显示“(.*)”关键内容')
def displayed_LogOut_Successfully(step, keywords):
# 断言退出登录后,页面是否出现退出成功关键内容
assert keywords in world.driver.page_source
print u"Logout Success"
# 退出浏览器
world.driver.quit()
Login_English.feature
#encoding=utf-8
Feature: login and logout
Scenario: Successful Login with Valid Credentials
Given Launch a browser
When User visit to http://mail.126.com Page
And User enters UserName"axu28990" and Password"邮箱对应密码"
Then Message displayed Login Successfully
Scenario: Successful LogOut
When User LogOut from the Application
Then Message displayed LogOut Successfully
Login_English.py
#encoding=utf-8
from lettuce import *
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
@step('Launch a browser')
def open_browser(step):
try:
world.driver = webdriver.Chrome(executable_path = "d:\\chromedriver.exe")
except Exception, e:
raise e
@step('User visit to (.*) Page')
def visit_url(step, url):
world.driver.get(url)
@step('User enters UserName"(.*)" and Password"(.*)"')
def user_enters_UserName_and_Password(step, username, password):
world.driver.maximize_window()
time.sleep(3)
world.driver.switch_to.frame(world.driver.find_element_by_xpath('//iframe[contains(@id,"x-URS-iframe")]'))
userName = world.driver.find_element_by_xpath('//input[@name="email"]')
userName.clear()
userName.send_keys(username)
pwd = world.driver.find_element_by_xpath("//input[@name='password']")
pwd.send_keys(password)
pwd.send_keys(Keys.RETURN)
time.sleep(15)
@step('Message displayed Login Successfully')
def message_displayed_Login_Successfully(step):
# print world.driver.page_source.encode('utf-8')
assert u"未读邮件" in world.driver.page_source
print "Login Success"
@step('User LogOut from the Application')
def LogOut_from_the_Application(step):
print "====",world.driver
# time.sleep(15)
world.driver.find_element_by_partial_link_text(u"退出").click()
time.sleep(4)
@step('Message displayed LogOut Successfully')
def displayed_LogOut_Successfully(step):
assert u"您已成功退出网易邮箱" in world.driver.page_source
print u"Logout Success"
world.driver.quit()
terrain.py
#encoding=utf-8
from lettuce import *
#在所有场景执行前执行
@before.all
def say_hello():
print u"开始执行行为数据驱动测试..."
#在每个secnario开始执行前执行
@before.each_scenario
def setup_some_scenario(scenario):
print u'开始执行场景“%s”' %scenario.name
#在每个secnario执行结束后执行
@after.each_scenario
def teardown_some_scenario(scenario):
print u'场景“%s”执行结束' %scenario.name
#在所有场景执行结束后执行
@after.all #默认获取执行结果的对象作为total参数
def say_goodbye(total):
result = u"恭喜,%d个场景被运行,%d个场景运行成功" % (
total.scenarios_ran, #一共多少场景运行了
total.scenarios_passed #一共多少场景运行成功了
)
print result