damaoshao / blog Goto Github PK
View Code? Open in Web Editor NEWlt's my log file, and logging level is info.
lt's my log file, and logging level is info.
2019年2月完成,迁移过来的
最近维护之前实习小哥哥的代码,小哥哥代码写的好,但是都是Python3的,目前公司大多数代码还是Python2,因此维护过程中,就难免遇到移植的问题。
将Python2中下面的代码移植到Python3中就出现了各种问题,我就按照报错的顺序捋一捋。
from Crypto.Cipher import AES
def decrypt_password(password):
if not password:
return None
unpad = lambda s: s[0:-ord(s[-1])]
# AES_KEY is str
cipher = AES.new(AES_KEY)
decrypted = unpad(cipher.decrypt(password.decode('hex')))
return decrypted
1.问题一
报错:ModuleNotFoundError: No module named ’Crypto
解决办法:安装pycryptodome库
Crypto库在python3中可以安装,但是不可用,python3中对应的库名为pycryptodome,而且此库与Crypto库冲突,即在python3中如果两个库都安装了,那么一个库不能用。 这一点官方文档上面是有说明。
2.问题二
报错: new() missing 1 required positional argument: 'mode'
解决办法:指定具体的模式
pycryptodome中mode哪怕使用默认值,也必须要填,Python2中该项默认值为AES.MODE_ECB,如果在Python2代码中mode没有指定,直接指定成AES.MODE_ECB即可。
3.问题三
报错:Object type <class 'str'> cannot be passed to C code
解决办法:将代码中AES_KEY转为bytes格式。
pycryptodome库还是和Python2一样,默认bytes类型,而且这个库不接受Unicode类型,而Python3默认就是Unicode,因此需要把key转为bytes类型。又因为使用了bytes类型,所以在进行字符串操作前,还要转换回来。
因此,上述Python2代码在Python3中应该为
from Crypto.Cipher import AES
def decrypt_password(password):
bytes2str = lambda s: str(s, encoding='utf-8')
unpad = lambda s: s[0:-ord(s[-1])]
# AES_KEY is bytes
cipher = AES.new(AES_KEY, mode=AES.MODE_ECB)
decrypted = unpad(bytes2str(cipher.decrypt(bytes.fromhex(password))))
return decrypted
上节问题三说到底就是编码问题,我是被编码坑了好多次orz,关于Python编码问题的文章,网上有好多,这里就不再赘述了。只是总结一下我对Python编码的理解:
字符串天然有两种状态,文本和字节(二进制)。编码就是文本转字节,解码就是字节转文本。Python2和Python3的不同就在于,编码过程中的文本,内部表示方法是不同的(难的地方就在这)。
在Python2中,Unicode是编码前的文本字符,str是编码后的字节序列,但凡被括号引起来的字符,都是str,而在Python3中str是编码过的Unicode文本字符,bytes是编码前的字节序列,被括号引起来的字符,其实已经是Unicode类型的str了,而这个str是什么样的编码,取决于文件头的声明和文件保存方式。
Python中编码这么坑,有没有一种统一的解决办法呢?有的,就是Unicode Everywhere
原因。
在输入的时候,不管输入是什么格式,decode转化为Unicode,保证整个代码都是Unicode一种编码格式,涉及输出的时候,在统一encode需要的需要的编码。
看了上面的原则,屏幕前的你可以会问了,那我咋知道输入是啥编码格式呢,是的,之前我写爬虫的时候,也有这个困扰,不过后来发现了这个库(charted),这个问题就再也不是问题啦。
安装:pip install charted
文档: https://chardet.readthedocs.io/en/latest/index.html>
使用起来非常爽,大体是这个样子:
import requests
import chardet
resp = requests.get('http://baidu.com/')
chardet.detect(resp.content)
# 输出
# {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}
先公布上期答案:
每分钟执行一次。因为crontab时间隔间如果冲突了,会按照时间短来。
本期小问题:
a、b和c三个变量分别为以下三个值,请问,在Python2和Python3下,执行 a+b
和 a+c
是报错还是有结果,为什么?
a='编码'
b=b'code'
c=u'code'
最近公司新签了一个客户,他们有在用Hyper-V,所以需要我们平台接入SCVMM,纳管他们的Hyper-V机器,开始我以为这事类似接入OpenStack、VSphere一样呢,结果一上手才发现,这是天坑,SCVMM居然没有自带API服务,更别提SDK了。客户需求大于天,没有API也得硬着头皮上。本篇blog就讲讲我目前碰到的坑。
SCVMM本身没有API服务,但是可以安装外挂API服务,即Service Provider Foundation(简称SPF)。SPF本质就是网络服务器,需要一些组件的支撑(SQLServer,.Net等)。操作SCVMM的另外一种方式PowerShell。
最后,我们选择使用PowerShell,这绝不是因为PowerShell好用,而且SPF本身的缺陷,逼着我们选了PowerShell:
既然确定了使用PowerShell,那么下面的问题就是如何远程执行PowerShell命令。又是两条路,在客户机器上面搭Agent(HTTP服务),或者使用WinRM远程执行,经过比较,自搭HTTP服务的Agent(用Flask起的服务)比使用WinRM要快得多,平均一个命令自搭Agent要比Winrm能快出1到2秒。但是使用Agent有两个问题,一个是Agent要自己写,这是工作量,后期维护这又是工作量。另外一个是就是不安全,毕竟在客户环境上面开端口起服务,而且涉及管理权限。因为自搭Agent这两个缺点,我们最后决定选用WinRM远程执行方案(即使它真的很慢orz)。
跨平台总逃不出编码问题,这次是在Linux上面远程执行Windows的PowerShell命令,编码就是头号拦路虎。
首先是字符集。使用pywinrm库执行PowerShell命令的输出中,如果有中文,就会显示成?
。看了一圈源码,我发现,pywinrm库暴露出来的执行PowerShell命令的run_ps
方法默认使用的字符集是437,这是Windows中美国字符集编码(**字符集编码是936),为了统一使用utf-8
写代码,这里最好指定成utf8
的字符集编码65001
。字符集在Protocol
类中的open_shell
方法中可以指定。
然后是编码问题。指定了65001
的字符集后,一旦远程执行的命令有问题,标准错误中含有中文,代码就报无法编码的错误。报错位置在session
类的_clean_error_msg
中。上下文看一下,就很容易发现根本原因pywinrm解码方式是ascii码,对,你没有看错,9102年了,pywinrm居然还在用字符集437
和ascii解码。没有办法,我只能重写了输出的方法,将将解码改成utf8
。
# 为了编码过程中统一使用utf-8,重写下面两个方法,修改了字符集和解码方式。
# set codepage to 65001(the powershell utf-8 charset code is 65001)
# use utf-8 to decode
class SessionUTF8(Session):
def run_cmd(self, command, args=()):
shell_id = self.protocol.open_shell(codepage=65001)
command_id = self.protocol.run_command(shell_id, command, args)
rs = Response(self.protocol.get_command_output(shell_id, command_id))
self.protocol.cleanup_command(shell_id, command_id)
self.protocol.close_shell(shell_id)
return rs
def run_ps(self, script):
encoded_ps = b64encode(script.encode('utf_16_le')).decode('utf-8')
rs = self.run_cmd('powershell -encodedcommand {0}'.format(encoded_ps))
if len(rs.std_err):
rs.std_err = self._clean_error_msg(rs.std_err.decode('utf-8'))
return rs
SCVMM的Powershell输出就是一行行的:
隔开的键值对,如果有多个对象,每个对象之间是空行隔开。最坑是,每行最多80字符,超过80字符就会换行。为了方便前端使用,我需要把这格式转成Json。
# PowerShell的输出格式
ServerConnection : Microsoft.SystemCenter.VirtualMachineManage
r.Remoting.ServerConnection
ID : c755cbc0-d481-49db-b0a5-484c8f930767
MarkedForDeletion : False
ComputerTierTemplate :
CapabilityProfile :
ApplicationProfile :
SQLProfile :
ServerFeatures : {}
TotalVHDCapacity : 42949672960
VirtualizationPlatform : HyperV
# pywinrm的输出的字符串。记为output。
ServerConnection : Microsoft.SystemCenter.VirtualMachineManage\r\n r.Remoting.ServerConnection\r\nID : afafac26-4b05-4458-9eb8-529a589ec0cb\r\nMarkedForDeletion : False\r\nComputerTierTemplate : \r\nCapabilityProfile : \r\nApplicationProfile : \r\nSQLProfile : \r\nServerFeatures : {}\r\nTotalVHDCapacity : 42949672960\r\nVirtualizationPlatform : Hyper
一开始我准备用正则解决这个问题的,但是作为一个正则菜鸡,实在想不出健壮的正则来解决换行问题。所以我首先想到的是,让输出不要换行。经过一番尝试,我发现在本地将PowerShell的输出重定向到文本中,那么文件里面的输出是不换行的。但是这仅仅在于本地,一旦使用pywinrm执行,哪怕重定向到文本中也换行orz
这条路走不通,我只能想办法把换行合并回去了。分析输出结构,我发现,每个对象间的分隔是\r\n\r\n
,每行的分隔是\r\n
,键和值的分隔是:
,这直接用字符串操作就能解决啦,出现\r\n
带着一大串空格的就是值
换行,直接替换成''
,等于就把换成合并回去了,然后在按对象(\r\n\r\n
)分隔,每个对象按:
分隔,就把键值对分解出来啦。仅仅需要两个列表推导,很舒服。
EMPTY_STRING = ''
ONE_BLANK_SPACE = ' '
KV_DEFAULT_INTERVAL = ': '
NEWLINE = '\r\n'
KV_OFFSET = 2
def _preprocess_merge_newlines(text):
"""
change string to list, merge the newlines
text: str
return: list
"""
components = [component for component in text.split(NEWLINE * 2) if component != EMPTY_STRING]
interval = NEWLINE + (int(components[0].find(':')) + KV_OFFSET) * ONE_BLANK_SPACE
return map(lambda component: component.replace(interval, EMPTY_STRING), components)
def _preprocess_regex(components):
"""
change string to json in the list
:param components: [string,string,string]
:return: [json,json,json]
"""
return [dict([(key_value.split(KV_DEFAULT_INTERVAL)[0].strip(),
key_value.split(KV_DEFAULT_INTERVAL)[1].strip())
for key_value in component.split(NEWLINE)]) for component in components]
def common_regex(text):
"""
convert the output of powershell into json.
:param text: str
:return: [json,json,json] --> every json is instance property set
"""
components = _preprocess_merge_newlines(text)
return _preprocess_regex(components)
经过转换,output
变为:
[{'ServerConnection': 'Microsoft.SystemCenter.VirtualMachineManager.Remoting.ServerConnection',
'ID': 'afafac26-4b05-4458-9eb8-529a589ec0cb',
'MarkedForDeletion': 'False',
'ComputerTierTemplate': '',
'CapabilityProfile': '',
'ApplicationProfile': '',
'SQLProfile': '',
'ServerFeatures': '{}',
'TotalVHDCapacity': '42949672960',
'VirtualizationPlatform': 'Hyper'}]
2019年3月写完,从老博客移植过来的
上周同事问了我一个crontab相关的问题,讨论完后,我想起来了这两年遇到的各种crontab的坑orz,搜了一下印象笔记,真是血泪史啊,往事不须再提了,整理一波坑吧。
crontab执行脚本的时候,其实是把脚本移动到用户的目录下面执行的,并不是在脚本本来的位置执行的。
比如在 /root/update_war 路径下面有脚本 testpy.py
data = ['a','b','c']
with open("data.txt","a+") as f:
f.writelines(data)
使用root用户的crontab执行
# crontab-0
0 */1 * * * /usr/bin/sh /root/update_war/update_war.sh >> /root/update_war/update.log
Python脚本testpy.py
中data.txt
并不会写入到/root/update_war
,而是直接出现在 /root
下面。
事实上稳妥的办法是涉及到crontab的文件,所有路径都使用绝对路径。
crontab的语法简单(这里就不赘述了),但也有点道道。
比如有个经典问题,每隔1小时执行一次,很多童鞋会这么写
# contab-1
0 * * * *
这是有问题的,上面这句的意思是每小时0分钟时候执行一次。比如10点50写了这句,11点0分就会执行,而不是11点50执行。
那该怎么办呢,其实就是化成分钟来解决,下面这种写法就是间隔60分钟了。
# crontab-2
*/60 * * * *
很多童鞋会发现,很多crontab语句,每小时整点执行会这么写(上一节中的crontab-0
也是这类似的)。
# crontab-3
0 */1 * * *
感觉这句crontab-3
跟上面crontab-1
是一样的,寻思这个 /1
不是脱裤子放屁多此一举嘛,其实不然呦,crontab-1
是严格的每小时0分钟执行一次,雷打不动, crontab-3
则是在每小时0分钟检查一下,如果上一个执行完了,才能会执行新的,不然不会执行的。
好的,就这个小问题总结一下
*/60 * * * * #每60分钟即每小时执行一次
0 * * * * #每小时0分钟执行一次,跟下一句的区别就是雷打不动坚决执行
0 */1 * * * #每小时0分钟执行一次,跟上一句的区别就是,老的不执行完新的不会执行
/etc/crontab
)才有用。/etc/init.d/cron restart
)。/var/log/cron
里面,多看log,能解决90%的问题orz。#TODO
* */1 * * * # 每分钟执行一次。因为crontab时间隔间如果冲突了,会按照时间短来。
本文大量例子都来自 Powershell中文博客,特别是其中的在线教程。
写这篇文章的目的主要是希望以后再需要写PowerShell时,可以通过读这篇文章,快速把它拾起来。因此文章中更多的是我觉得有用的例子。
最近一直帮我们公司产品接入SCVMM,因此写了一些PowerShell脚本为什么是PowerShell
。不写不要紧,一写吓一跳,PowerShell真是名副其实——Power!虽然我还是一只PowerShell菜鸡,但是边写边查,写的还挺爽,虽然也遇到一些小坑,但是比想象的要顺利的多。对了,说下我的环境,本机MacOS,用的Mircosoft Romote Desktop
RDP到一台Windows虚拟机,虚拟机操作系统版本是Windows Server 2012 R2 Standard
,PowerShell版本是4.0
。
学习总是从复制粘贴开始的,毕竟熟练的复制粘贴就是成功的一大半高手都是从模仿练习这一步来的。默认情况下(快速编辑模式,推荐使用此模式),PowerShell复制的方法是,左键选中要复制的内容,然后点击右键,确认选中。粘贴就是右键。如果将PowerShell设置到标准模式(即属性中去掉快速编辑模式中的对勾),那么复制都需要先右键标记,粘贴则是右键粘贴。
刚开始用PowerShell的复制粘贴,感觉是很奇怪的,不过用多了,也就习惯了。不过Powershell的复制粘贴经常失效。有时候,哐哐哐狂敲右键就是粘贴不了orz,经过我的反复折腾,解决这种情况用下面两种方法可以解决这种情况。
一个是重启大法。运行下面两行命令重启一下rdpclip.exe
程序。
taskkill -im rdpclip.exe -f
rdpclip
另外一个是移花接木。从Mac上面复制,到PowerShell到粘贴,如果第一招不好用,就从Mac上面复制好后,先粘贴到记事本上,然后再从记事本上复制一次,再到PowerShell粘贴。虽然有点折腾,但是是可以成功的,总比手打强吧orz
PowerShell中的变量有三个点:不需要声明、不区分大小写和使用$
符标明。除了区分大小写这条,其他与Python中的变量是非常类似的,比如只用一步就可以交换变量。注意,PowerShell中所有不是我们自己的定义的变量都属于驱动器变量(比如环境变量),访问方法见下面示例。
# 赋值
$num=2
$text="保存文本"
$msg=$text*$num
# 输出变量
$msg # output: 保存文本保存文本
# 交换变量
$num,$text=$text,$num
$num # output: 保存文本
# 查看所有变量
ls variable:
# 查看某一个变量的值,支持通配符
ls variable:Max*
# 查看环境变量
ls env:
PowerShell命令的返回值都是数组,每行是一个元素,对,你没有看错,不是文本,是数组。PowerShell的数组有点像Python,可以存放不同类型的元素,支持索引选择,甚至支持负索引和多个索引,并且是引用类型。计算数组的元素使用.count
,复制数组使用Clone()
方法。
# 命令的输出都是数组
$res = ipconfig
$res[0] #output:
$res[1] #output: Windows IP 配置
# 数组创建,下面两种方式都是创建了一个元素为1,2,3的数组
$array_1 = 1,2,3
$array_2 = 0..9
# 空数组和判断数组类型
$array_3 = @()
$array_3 -is [array]
# 单元素数组,唯一的元素前加逗号
$array_4 = , 1
# 索引
$array_2 = 0..9 #output: 0,3,5,9
# 数组个数
$array_2.count #output: 9
$array_2.clone() #output: 元素为0-9的数组
# 创建
$hash = @{ key1 = "value1"; key2 = "value2"; key3 = "value3" }
$hash
# output:
---
Name Value
---- -----
key3 value3
key1 value1
key2 value2
# 访问方式1
$hash['key1']
# output
---
value1
# 访问方式2
$hash.key1
# output
---
value1
# 哈希中Key和Value中的个数
$hash.Count # output: 3
# 展示哈希中的所有的key或者value
$hash.Keys
$hash.Values
# 添加元素
$hash=@{}
$hash.key="value"
$hash
# output:
---
Name Value
---- -----
key3 value3
# 删除用remove
$hash.remove("key")
PowerShell想Bash一样支持管道符。
# 按照长度排序
ls | Sort-Object Length
# 按照两个维度排序
ls | Sort-Object @{expression="Length";Descending=$true},@{expression="Name";Ascending=$true}
# 分组
ls | Group-Object Extension
# PowerShell中的逻辑运算符
-eq :等于
-ne :不等于
-gt :大于
-ge :大于等于
-lt :小于
-le :小于等于
-contains :包含
-notcontains :不包含
-and :和
-or :或
-xor :异或
-not :逆
# if语句
If( $value -eq 1 )
{ "1" }
Elseif( $value -eq 2)
{ "2" }
Elseif( $value -eq 3 )
{ "3" }
Else
{ "4" }
# switch语句
switch($value)
{
1 {"1"}
2 {"2"}
3 {"3"}
4 {"4"}
}
# 使用 Switch 测试取值范围
switch($value)
{
{$_ -lt 1 } { "<1"; break}
{$_ -gt 2 } { ">2"; break}
{$_ -lt 3 } { "<3"; break}
Default {"nothing"}
}
PowerShell中的循环,支持for、while、do..while和foreach,同时支持break和continue,也可以利用Switch的特性实现循环。使用ForEach-Object 循环打印对象$_代表当前对象。
#for
$sum=0
for($i=1;$i -le 100;$i++)
{ $sum+=$i }
$sum
#while
$n=5
while($n -gt 0)
{
$n
$n=$n-1
}
# do..while
do { $n=Read-Host } while( $n -ne 0)
# foreach
$nums=10..7
foreach($n in $nums)
{
"n=$n"
}
#使用Foreach循环
$nums=10..7
foreach($n in $nums)
{
"n=$n"
}
# output:
n=10
n=9
n=8
n=7
#使用Switch循环
$nums = 10..7
Switch ($nums)
{
Default { "n= $_" }
}
# output:
n= 10
n= 9
n= 8
n= 7
删除函数直接使用del方法。
# 函数的格式
Function FuncName (args[])
{
code;
}
# 万能参数 $args 例子一
function sayHello
{
if($args.Count -eq 0)
{
"No argument!"
}
else
{
$args | foreach {"Hello,$($_)"}
}
}
sayHello # output: No argument!
sayHello LiLi Lucy Tom
# output:
Hello,LiLi
Hello,Lucy
Hello,Tom
# 万能参数 $args 例子二
function Add
{
$sum=0
$args | foreach {$sum=$sum+$_}
$sum
}
Add 10 7 3 100
#output:
120
# 指定参数
function StringContact($str1,$str2)
{ return $str1+$str2 }
StringContact moss fly # output: mossfly
StringContact -str1 word -str2 press #output: wordpress
# 指定参数
function stringContact($str1="moss",$str2="fly")
{ return $str1+$str2 }
stringContact Good Luck # output: stringContact
stringContact # output: mossfly
# 限制参数类型
function tryReverse( [switch]$try , [string]$source )
{
[string]$target=""
if($try)
{
for( [int]$i = $source.length -1; $i -ge 0 ;$i--)
{
$target += $source[$i]
}
return $target
}
return $source
}
tryReverse -source www.mossfly.com # output: www.mossfly.com
tryReverse -try $true -source www.mossfly.com # output: moc.ylfssom.www
前言
- 今天上午参与了公司新IT测试框架的选型讨论会,下面这套基于RF做的IT框架的时代马上就要结束啦(公司将开始基于pytest自己写框架了)。想到前年,我一个一点测试不懂的开发帮QA部门,从选型到搭框架再到写Jmeter测试用例那几周,脑壳都要挠破了,真是感慨万千。
- 这篇文章是当时搭建完IT框架后写的,目标读者是公司的QA同事。所以内容分两部分,一个是介绍了IT的流程,另外一个是介绍了RF本身和其使用方法。
Robot Framework(以下简称RF)是一个基于Python的、可扩展的、关键字驱动的测试自动化框架。框架较为成熟,08年到15年由Nokia Networks维护,16年以后由Robot Framework Foundation维护。目前代码托管在Github上面。
RF的主要特色为(来自官方文档):
相关资料如下:
选用RF的优势:
一致性:目前公司的UI测试使用的就是RF框架,RF框架也完全有能力做IT测试,因此使用RF框架做IT测试,可以降低学习成本,提高可维护性。
复用性:在安装了Robot-Framework-JMeter-Library后,RF可以运行Jmeter脚本,并且将Jmeter运行结果转为Html格式。公司目前性能测试用的就是Jmeter,对于相同场景,只要小幅修改Jmeter脚本即可将其复用到IT测试上面。
选用RF的一些问题:
RF对于变量类型的规定堪称僵硬(当然,这么规定带来的好处是方便类型检测),RF中对于字典类型的创建非常麻烦(嵌套的字典实例如下),对于咱们公司API请求中携带大量参数的情况,只能创建关键字来解决,不管是采取RF自带创建字典的方法,还是创建关键字的方法,都比较浪费时间(因为难以复用)。
# 代码块1
# RF在测试用例中创建字典实例
*** Test Cases ***
Create dictionary
&{demo}= create dictionary d1=d1 d2= d2
&{params}= create dictionary key=value key2=value2 key3=${demo}
&{data}= create dictionary bodycd1=${demo} body2=${params}
log data warn
在RF中,关键字其实就是Python/Java的类方法,因此扩展起来非常容易,但是关键字一旦多起来,一个同事写的测试用例,其他人(甚至他自己过了一段时间)维护就非常麻烦(需要回去看关键字是如何规定的orz)。因此需要严格规定关键字的创建规范是一件值得深入讨论的事情。
目前IT的方案大体是这样:
利用Jenkins的Pipeline管理测试场景
Jenkins触发RF测试
RF执行Jmeter脚本
RF将Jmeter执行结果转为HTML格式
Jenkins展示结果,并且将HTML格式结果邮件发送出去
如果自上而下描述一下这个流程,应该是这个样子:
使用Jenkins自动部署一台测试环境的Job,部署完成后,将自动触发IT的Pipeline。
IT Pipeline 中Job的核心是执行了一个RF测试套件,该测试套件中包含一些测试用例。
这个测试用例是使用Robot-Framework-JMeter-Library库执行的Jmeter脚本。
Jmeter脚本包含一些具体要测试的API。
注意:
配置Pipeline
Jenkins的Pipeline中每一个Job都是一个test suit,不同Job的串联,组成了一个测试场景。目前配置了hourly test和nightly test。
顾名思义,hourly test每小时触发一次,其中的的test suit是我们的产品的核心且主干功能。下图为hourly test的Pipeline:
而nightly test每晚跑一次,覆盖所有用户场景。下图为nightly test的Pipeline:
编写测试用例
测试用例全是Jmeter脚本,如果编写和组织Jmeter脚本,内容有点多,这里就略去了。
配置邮件
发送邮件使用的是Jenkins的插件,邮件内容是用了robotframework-jmeterlibrary中的模板,即调用其中的run jmeter analyse jtl convert
关键字。邮件示意图如下:
RF框架的文档撰写的比较详细(地址),下面列出一些重点,方便能直接看懂第二节的测试用例。
$/@/&
,例如:${SCALAR}
, @{LIST}
和 &{DICT}
。%{ENV_VAR_NAME}
这种语法格式来使用环境变量. 环境变量的值只能是字符串。robot *.robot
来执行测试脚本。--variable
,--variablefile
来设置变量。测试用例是在测试套件中的,一个测试套件最少包含Setting
和Test Cases
两个部分。具体意义见下方代码示例。
# 代码块2
*** Settings ***
# 引入库
Library Collections
Library String
Library JMeterLib.py
# 此为自定义库
Library GetError.py
*** Variables ***
# 创建标量
${NAME} Robot Framework
${VERSION} 2.0
${ROBOT} ${NAME} ${VERSION}
# 创建列表
@{NAMES} Matti Teppo
@{NAMES2} @{NAMES} Seppo
@{NOTHING}
@{MANY} one two three four
# 创建字典
&{USER 1} name=Matti address=xxx phone=123
&{USER 2} name=Teppo address=yyy phone=456
&{MANY} first=1 second=${2} ${3}=third
# Test Cases下面就是各个测试用例
*** Test Cases ***
# 这即为一个测试用例
Jenkins_runJMeterAndAnalyseAndConvertLog
# 这一句的含义是:运行函数(关键字)`run jmeter analyse jtl convert`,参数为`/opt/apache-jmeter-5.0/bin/jmeter`,`auto_it.jmx`,`auto.jtl`,并且将运行结果赋值给`${result}`.
# 注意`run jmeter analyse jtl convert`是从`JMeterLib.py`(即JL库)中引入的。
${result} run jmeter analyse jtl convert /opt/apache-jmeter-5.0/bin/jmeter auto_it.jmx auto.jtl
# 运行函数(关键字)log,参数为上一语句中的结果。
log ${result}
# catch error函数即自定义的函数,如何定义参加下面的代码框。
${error_num} catch error ${result}
# 判定结果的值,相当于是断言,断言成功,该测试用例成功,断言失败,该测试用例失败。
Should Be Equal As Strings ${error_num} 0
测试库其实就是Python或者Java的类库,下面是分别使用Python和Java扩展的测试库。
# 代码块3
*** Settings ***
Library MyLibrary 10.0.0.1 8080
Library AnotherLib ${VAR}
# 代码块4
# Python实现扩展关键字
from example import Connection
class MyLibrary:
def __init__(self, host, port=80):
self._conn = Connection(host, int(port))
def send_message(self, message):
self._conn.send(message)
// Java实现扩展关键字
public class AnotherLib {
private String setting = null;
public AnotherLib(String setting) {
setting = setting;
}
public void doSomething() {
if setting.equals("42") {
// do something ...
}
}
}
RF为了保证测试用例之间的独立性,默认情况下,它为每个测试用例创建新的测试库实例。然而,这种方式不总是我们想要的,比如有时测试用例需要共享某个状态的时候。此外,那些无状态的库显然也不需要每次都创建新实例。实例化测试库类的方式可以通过一个特别的属性 ROBOT_LIBRARY_SCOPE
来控制。这个属性是一个字符串,可以有以下三种取值:
注意:ROBOT_LIBRARY_SCOPE的默认值为TEST CASE,即每个测试用例都会创建新的实例,将导致涉及API测试时,就无法保持session,目前我们公司的测试场景,将ROBOT_LIBRARY_SCOPE设置为GLOBAL即可。
上一节中,我们实现的扩展关键字实现如下:
# 代码块5
# -*- coding:utf-8 -*-
import re
# 实现一个类,类方法就是关键字,在RF中使用时,会自动忽略大小写和下划线
class MyClass(object):
def __init__(self):
pass
# 在测试套件中,引入该库后,即可使用关键字get_num,Get Num等去调用改方法
def get_num(self, content):
"""
get nums from the output
"""
regex = r".*Err:\s*(\d).*"
matches = re.finditer(regex, content, re.MULTILINE)
numbers = []
for matchNum, match in enumerate(matches):
for groupNum in range(0, len(match.groups())):
groupNum = groupNum + 1
numbers.append(match.group(groupNum))
for number in numbers:
if number != '0':
return '1'
return '0'
# 上个代码框中,`${error_num} catch error ${result}` 就是调用了该方法
def catch_error(self, results):
"""
get the error from result
"""
for result in results:
if result.get('samplesSuccessRateInclAssert') != '100':
return '1'
return '0'
# 实现ROBOT_LIBRARY_SCOPE的类属性
class GetError(MyClass):
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
RF框架运行Jmeter脚本使用的是第三方Python库Robot-Framework-JMeter-Library(以下简称JL库),该库的主要作用是运行Jmeter脚本,并且将Jmeter结果转化为Html格式,该项目代码托管在Github上面。
针对公司目前复用Jmeter脚本的情况进行集成测试的情况,我们主要使用JL库中run jmeter analyse jtl convert
关键字。
在实际使用的时候,因为Jenkins拿到的是RF的处理结果,但是RF不论Jmeter的测试结果如果,只要Jmeter脚本跑完就判定测试成功,所以我们需要自己实现一个关键字(catch error),来对Jmeter的处理结果进行处理,只有有一个Jmeter请求失败,都会认为本次测试失败。具体代码请参考代码块5
。
本文是从老博客移过来的,完成时间大约是19年3月,当时我初做公司一个Scrum Team的Master,一脸懵逼。如今跌跌撞撞一年了,我对敏捷开发又有了一些新的体会。这周争取写一篇博客,记录一下我一年的敏捷开发实践。
前言
- 我们公司一直在实行敏捷开发,但是我身边的同事对敏捷开发的理解各不相同,有的甚至观点相悖。为了确认到底啥是敏捷开发,提升我们组的开发效率,这两天我看了一圈文章和其他公司的实践,写了这篇文章。
- 本文中的敏捷开发有时特指Scrum,文中可能没有全部标明。
- 本文的关于敏捷开发(Scrum)的描述部分(即【一、什么是敏捷开发】),除非特别注明,均来自The Home of Scrum中的The Scrum Guide(中文PDF版点击这里)。为了行文方便,文中就不一一标注了。
敏捷开发是一套软件开发中的观点(即《敏捷软件开发宣言》)和原则(即《敏捷宣言遵循的原则》)。符合该价值观的和原则的软件开发方法,都可以认为是敏捷开发。
敏捷软件开发宣言
我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观:
个体和互动高于流程和工具,
工作的软件高于详尽的文档,
客户合作高于合同谈判,
响应变化高于遵循计划,
也就是说,尽管右项有其价值,我们更重视左项的价值。
敏捷宣言遵循的原则
我们遵循以下原则:
我们最重要的目标,是通过持续不断地及早交付有价值的软件使客户满意。
欣然面对需求变化,即使在开发后期也一样。为了客户的竞争优势,敏捷过程掌控变化。
经常地交付可工作的软件,相隔几星期或一两个月,倾向于采取较短的周期。
业务人员和开发人员必须相互合作,项目中的每一天都不例外。
激发个体的斗志,以他们为核心搭建项目。提供所需的环境和支援,辅以信任,从而达成目标。
不论团队内外,传递信息效果最好效率也最高的方式是面对面的交谈。
可工作的软件是进度的首要度量标准。
敏捷过程倡导可持续开发。责任人、开发人员和用户要能够共同维持其步调稳定延续。
坚持不懈地追求技术卓越和良好设计,敏捷能力由此增强。
以简洁为本,它是极力减少不必要工作量的艺术。
最好的架构、需求和设计出自自组织团队。
团队定期地反思如何能提高成效,并依此调整自身的举止表现。
敏捷开发有5个主要的价值观和3个支柱,一般来说,敏捷开发也应该有这些特性。
敏捷开发的价值观
- 专注:由于我们在一段时间内只专注于少数几件事情,所以我们可以很好地合作并获得优质的产出。我们能够更快地交付有价值的事项。
- 公开:在团队合作中,大家都会表达我们做得如何,以及遇到的障碍。我们发现将担忧说出来是一件好事,因为只有这样才能让这些担忧及时得到解决。
- 尊重:因为我们在一起工作,分享和成功失败,这有助于培养并加深互相之间的尊重,并帮助彼此成为值得尊重的人。
- 承诺:由于对自己的命运有更大的掌握,我们会有更坚强的信念获得成功。
- 勇气:因为我们不得单打独斗,我们能够感受到支持,而且掌握更多的资源。这一切赋予我们勇气去迎接更大的挑战。
敏捷开发的支柱
- 透明:过程中的关键环节对于那些对产出负责的人必须是显而易见的。要拥有透明,就要为这些关键环节制定统一的标准,这样所有留意这些环节的人都会对观察到的事物有统一的理解。
- 检视:Scrum 的使用者必须经常检视 Scrum 的工件和完成 Sprint 目标的进展,以便发现不必要的差异。检视不应该过于频繁而阻碍工作本身。当检视是由技能娴熟的检视者在工作中勤勉地执行时,效果最佳。
- 适应:如果检视者发现过程中的一个或多个方面偏离可接受范围以外,并且将会导致产品不可接受时,就必须对过程或过程化的内容加以调整。调整工作必须尽快执行如此才能最小化进一步的偏离。
Scrum是敏捷开发的框架中的一种,也是最流行的一种。Sprint是Scrum的组成部分,可以直接理解其为一次迭代,一个开发周期。Scrum开发过程有4个正式事件组成。
- Sprint计划会议:Sprint 中要做的工作在 Sprint 计划会议中来做计划。这份工作计划是由整个 Scrum 团队共同协作完成的。该会议主要解决两个问题:在这个Sprint做什么(What),怎么做(How)。
- 每日Scrum站会:每日 Scrum 站会是开发团队的一个时间盒限定为 15 分钟的事件。每日 Scrum 站会Sprint 的每一天都举行。在每日 Scrum 站会上,开发团队为接下来的 24 小时的工作制定计划。通过检视上次每日Scrum 站会以来的工作和预测即将到来的 Sprint 工作来优化团队协作和效能。参与站会的每个人都要说为了打成这个Sprint的目标,昨天我做了什么,今天我做了什么和是否有障碍在阻塞我或者我的团队的进度这三个问题。
- Sprint评审会议:Sprint 评审会议在 Sprint 快结束时举行 ,用以检视所交付的产品增量并按需调整产品待办列表。在 Sprint 评审会议中,Scrum 团队和利益攸关者协同讨论在这次 Sprint 中所完成的工作。根据完成情况和 Sprint 期间产品待办列表的变化,所有参会人员协同讨论接下来可能要做的事情来优化价值。这是一个非正式会议,并不是一个进度汇报会议,演示增量的目的是为了获取反馈并促进合作。Sprint 评审会议的结果是一份修订后的产品待办列表,阐明很可能进入下个 Sprint 的产品待办列表项。产品待办列表也有可能为了迎接新的机会而进行全局性地调整。
- Sprint回顾会议:Sprint 回顾会议是 Scrum 团队检视自身并创建下一个 Sprint 改进计划的机会。Sprint 回顾会议的目的在于三点,1)检视前一个 Sprint 中关于人、关系、过程和工具的情况如何。2)找出并加以排序做得好的和潜在需要改进的主要方面。3)制定改进 Scrum 团队工作方式的计划。
一般的Scrum开发流程(不借助于任何工具)如下:
- 产品负责人将整个产品设计成产品Backlog。产品Backlog本质就是需求列表。
- 召开Sprint计划会议。
- Sprint计划会议定下的任务写在纸条上贴在任务墙,让Scrum成员认领分配并细分。(任务墙就是把未完成、正在做、已完成 的工作状态贴到一个墙上,这样大家都可以看得到任务的状态 )
- 举行每日站立会议,让大家在每日会议上总结昨天做的事情、遇到什么困难,今天开展什么任务。
- 绘制燃尽图,保证任务的概况能够清晰看到。(燃尽图把当前的任务总数和日期一起绘制,每天记录一下,可以看到每天还剩多少个任务,直到任务数为0 ,这个sprint就完成了)。
- Sprint评审会议是在Sprint完成时举行,要向客户演示自己完成的软件产品 。
- 最后是Sprint总结会议,以轮流发言方式进行,每个人都要发言,总结上一次Sprint中遇到的问题、改进和大家分享讨论。
敏捷开发(Scrum)只是一套积极响应变化的开发框架。如果开发效率被定义为更短时间里开发出需要的功能(或者相同的时间里开发出更多的功能),那么敏捷开发肯定会降低效率。想一想效率最高的开发方式是啥:需求明确,文档详尽,码不停蹄。而敏捷开发与"效率最高"的开发方式正好相反。
既然敏捷开发会降低效率,那么为什么这么企业(包括咱们公司)还要使用敏捷开发呢?因为敏捷开发可以大幅提升投入产出比。高效率开发出来的功能,如果不是客户需要的,那就是纯浪费,效率再高,投入产出比也是零。具体来说,敏捷开发对投入产出比的提高来主要来自以下几个方面:
敏捷开发的劣势也是明显的,就是太依赖人了。
我觉得可以这么说,优秀的敏捷开发团队一定是由一群自建设的人自组织起来的。敏捷开发可以有Scrum Master(Scrum Master本质就是敏捷开发教练,指导大家更好的进行敏捷开发)(当然最好可以没有Scrum Master,因为每个都是Scrum Master),但是Scrum Master只能深度参与,决不能Push,一旦Push就会毁了自建设,这等于毁了敏捷开发的基石。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.