Git Product home page Git Product logo

blog's Issues

Robot Framework的IT实践

前言

  1. 今天上午参与了公司新IT测试框架的选型讨论会,下面这套基于RF做的IT框架的时代马上就要结束啦(公司将开始基于pytest自己写框架了)。想到前年,我一个一点测试不懂的开发帮QA部门,从选型到搭框架再到写Jmeter测试用例那几周,脑壳都要挠破了,真是感慨万千。
  2. 这篇文章是当时搭建完IT框架后写的,目标读者是公司的QA同事。所以内容分两部分,一个是介绍了IT的流程,另外一个是介绍了RF本身和其使用方法。

1. 什么是Robot Framework

Robot Framework(以下简称RF)是一个基于Python的、可扩展的、关键字驱动的测试自动化框架。框架较为成熟,08年到15年由Nokia Networks维护,16年以后由Robot Framework Foundation维护。目前代码托管在Github上面。
RF的主要特色为(来自官方文档):

  • 表格式的语法简单易用,以统一的方式创建测试用例
  • 可以通过现有关键字创建可复用的高层关键字
  • 提供了直观的HTML格式的测试报告和日志文件
  • 作为一个测试平台,是应用无关的
  • 提供了 测试库API,可以轻易地使用Python或者Java创建自定义的测试库
  • 提供了命令行接口和基于XML的 输出文件,可以与现有框架集成(如持续集成系统)
  • 提供了多种测试库支持,如用于web测试的Selenium,Java GUI测试,启动进程,Telnet,SSH等
  • 可以创建数据驱动的测试用例
  • 内置支持变量,在不同的环境中特别实用
  • 提供标签来分类和选择测试用例
  • 非常容易与源码控制系统集成,因为测试套件就是文件夹和文本文件
  • 提供了用例级别和测试套件级别的setup和teardown
  • 模块化的架构,支持针对不同接口的应用程序创建测试

相关资料如下:

2. 为什么选用RF做IT测试

选用RF的优势:

  • 一致性:目前公司的UI测试使用的就是RF框架,RF框架也完全有能力做IT测试,因此使用RF框架做IT测试,可以降低学习成本,提高可维护性。

  • 复用性:在安装了Robot-Framework-JMeter-Library后,RF可以运行Jmeter脚本,并且将Jmeter运行结果转为Html格式。公司目前性能测试用的就是Jmeter,对于相同场景,只要小幅修改Jmeter脚本即可将其复用到IT测试上面

选用RF的一些问题:

  • 如果不复用Jmeter脚本,编写的API测试用例的成本非常高。

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可以轻松扩展关键字,也因此可能带来乱扩展关键字的问题,导致测试用例可读性和可维护性差的问题。

在RF中,关键字其实就是Python/Java的类方法,因此扩展起来非常容易,但是关键字一旦多起来,一个同事写的测试用例,其他人(甚至他自己过了一段时间)维护就非常麻烦(需要回去看关键字是如何规定的orz)。因此需要严格规定关键字的创建规范是一件值得深入讨论的事情。

3. 方案与实施

3.1 目前的IT方案

目前IT的方案大体是这样:

  1. 利用Jenkins的Pipeline管理测试场景

  2. Jenkins触发RF测试

  3. RF执行Jmeter脚本

  4. RF将Jmeter执行结果转为HTML格式

  5. Jenkins展示结果,并且将HTML格式结果邮件发送出去

3.2 目前的IT运转流程

如果自上而下描述一下这个流程,应该是这个样子:

  • 使用Jenkins自动部署一台测试环境的Job,部署完成后,将自动触发IT的Pipeline。

  • IT Pipeline 中Job的核心是执行了一个RF测试套件,该测试套件中包含一些测试用例。

  • 这个测试用例是使用Robot-Framework-JMeter-Library库执行的Jmeter脚本。

  • Jmeter脚本包含一些具体要测试的API。

3.3 实施

  1. 在Jenkins的节点上额外安装了一下类库:
  • robotframework==3.1
  • robotframework-jmeterlibrary==1.2
  • Jmeter5.0

注意:

  • 两个Python库都安装在了默认解释器扩展库中,Jmeter安装在/opt目录下面。
  • 因为Python2的编码问题,在robotframework-jmeterlibrary添加了设置utf-8为默认编码的代码。
  1. 配置Pipeline
    Jenkins的Pipeline中每一个Job都是一个test suit,不同Job的串联,组成了一个测试场景。目前配置了hourly test和nightly test。
    顾名思义,hourly test每小时触发一次,其中的的test suit是我们的产品的核心且主干功能。下图为hourly test的Pipeline
    WX20200805-232411
    而nightly test每晚跑一次,覆盖所有用户场景。下图为nightly test的Pipeline
    WX20200805-232700

  2. 编写测试用例
    测试用例全是Jmeter脚本,如果编写和组织Jmeter脚本,内容有点多,这里就略去了。

  3. 配置邮件
    发送邮件使用的是Jenkins的插件,邮件内容是用了robotframework-jmeterlibrary中的模板,即调用其中的run jmeter analyse jtl convert关键字。邮件示意图如下
    WX20200805-235313

4. 编写RF测试用例

4.1 编写原生RF测试用例

4.1.1 基本概念

RF框架的文档撰写的比较详细(地址),下面列出一些重点,方便能直接看懂第二节的测试用例。

  • 测试套件就是测试用例集合的文件,使用测试套件的文件夹构成了一个更高层次的测试套件。文件夹和文件的关系是父套件和子套件的关系。
  • 套件存在Setup和Teardown,测试套件的Setup在其中所有测试用例和子套件运行之前被执行, Teardown则是在之后执行。
  • 关键字其实就是Python中函数,注意RF会忽略大小写和下划线。
  • RF框架中的变量是自己封装的,分为标量、列表和字典,对应着符号$/@/&,例如:${SCALAR}, @{LIST}&{DICT}
  • RF使用 %{ENV_VAR_NAME} 这种语法格式来使用环境变量. 环境变量的值只能是字符串。
  • 执行robot *.robot来执行测试脚本。
  • 执行脚本中,可以使用--variable,--variablefile 来设置变量。

4.1.2 测试用例的编写示例

测试用例是在测试套件中的,一个测试套件最少包含SettingTest 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

4.1.3 编写测试库(扩展关键字)

测试库其实就是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 来控制。这个属性是一个字符串,可以有以下三种取值:

  • TEST CASE:为每个测试用例创建新的实例。如果有suite setup和suite teardown的话,同样也会新建。
  • TEST SUITE:为每个测试集创建新的实例。
  • GLOBAL:整个测试执行过程中只有一个实例被创建。所有的测试集和测试用例共享这个实例。

注意: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'

4.2 使用RF运行Jmeter脚本

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

快速上手Powershell

本文大量例子都来自 Powershell中文博客,特别是其中的在线教程
写这篇文章的目的主要是希望以后再需要写PowerShell时,可以通过读这篇文章,快速把它拾起来。因此文章中更多的是我觉得有用的例子。

最近一直帮我们公司产品接入SCVMM,因此写了一些PowerShell脚本为什么是PowerShell。不写不要紧,一写吓一跳,PowerShell真是名副其实——Power!虽然我还是一只PowerShell菜鸡,但是边写边查,写的还挺爽,虽然也遇到一些小坑,但是比想象的要顺利的多。对了,说下我的环境,本机MacOS,用的Mircosoft Romote DesktopRDP到一台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

SCVMM的自动化实践

最近公司新签了一个客户,他们有在用Hyper-V,所以需要我们平台接入SCVMM,纳管他们的Hyper-V机器,开始我以为这事类似接入OpenStack、VSphere一样呢,结果一上手才发现,这是天坑,SCVMM居然没有自带API服务,更别提SDK了。客户需求大于天,没有API也得硬着头皮上。本篇blog就讲讲我目前碰到的坑。

SPF还是PowerShell

SCVMM本身没有API服务,但是可以安装外挂API服务,即Service Provider Foundation(简称SPF)。SPF本质就是网络服务器,需要一些组件的支撑(SQLServer,.Net等)。操作SCVMM的另外一种方式PowerShell。

最后,我们选择使用PowerShell,这绝不是因为PowerShell好用,而且SPF本身的缺陷,逼着我们选了PowerShell:

  • SPF本质就是网络服务器,需要一些组件的支撑(SQLServer,.Net等),并且对组件版本有要求。在客户环境上安装这么多东西,客户可能会不同意,即使同意了,还有兼容性的问题,不符合我们产品化的原则。
  • SPF并不是专门给SCVMM做API服务,测试后发现,有些SCVMM操作SPF居然没有对应的API。

自建Agent还是使用WinRM

既然确定了使用PowerShell,那么下面的问题就是如何远程执行PowerShell命令。又是两条路,在客户机器上面搭Agent(HTTP服务),或者使用WinRM远程执行,经过比较,自搭HTTP服务的Agent(用Flask起的服务)比使用WinRM要快得多,平均一个命令自搭Agent要比Winrm能快出1到2秒。但是使用Agent有两个问题,一个是Agent要自己写,这是工作量,后期维护这又是工作量。另外一个是就是不安全,毕竟在客户环境上面开端口起服务,而且涉及管理权限。因为自搭Agent这两个缺点,我们最后决定选用WinRM远程执行方案(即使它真的很慢orz)。

WinRM的编码(字符集)问题

跨平台总逃不出编码问题,这次是在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

PowerShell的输出正则问题

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'}]

使用crontab碰到的一些坑

2019年3月写完,从老博客移植过来的

上周同事问了我一个crontab相关的问题,讨论完后,我想起来了这两年遇到的各种crontab的坑orz,搜了一下印象笔记,真是血泪史啊,往事不须再提了,整理一波坑吧。

1. 绝对路径

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.pydata.txt 并不会写入到/root/update_war ,而是直接出现在 /root 下面。

事实上稳妥的办法是涉及到crontab的文件,所有路径都使用绝对路径。

2. 执行间隔的语法

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分钟执行一次,跟上一句的区别就是,老的不执行完新的不会执行

3. 其他注意事项

  1. crontab有系统级任务和用户级任务,有些操作(比如重启机器)放到用户级是没有用的orz,只有放到系统级任务(放入/etc/crontab)才有用。
  2. 新的crontab最少要过2分钟才会执行orz,立即执行的办法是重启一下服务(执行/etc/init.d/cron restart)。
  3. crontab的log在/var/log/cron里面,多看log,能解决90%的问题orz。

#TODO

* */1 * * *  # 每分钟执行一次。因为crontab时间隔间如果冲突了,会按照时间短来。

谈敏捷开发

本文是从老博客移过来的,完成时间大约是19年3月,当时我初做公司一个Scrum Team的Master,一脸懵逼。如今跌跌撞撞一年了,我对敏捷开发又有了一些新的体会。这周争取写一篇博客,记录一下我一年的敏捷开发实践。

前言

  1. 我们公司一直在实行敏捷开发,但是我身边的同事对敏捷开发的理解各不相同,有的甚至观点相悖。为了确认到底啥是敏捷开发,提升我们组的开发效率,这两天我看了一圈文章和其他公司的实践,写了这篇文章。
  2. 本文中的敏捷开发有时特指Scrum,文中可能没有全部标明。
  3. 本文的关于敏捷开发(Scrum)的描述部分(即【一、什么是敏捷开发】),除非特别注明,均来自The Home of Scrum中的The Scrum Guide(中文PDF版点击这里)。为了行文方便,文中就不一一标注了。

一、什么是敏捷开发

1.1 敏捷开发的定义

敏捷开发是一套软件开发中的观点(即《敏捷软件开发宣言》)和原则(即《敏捷宣言遵循的原则》)。符合该价值观的和原则的软件开发方法,都可以认为是敏捷开发。

敏捷软件开发宣言
我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观:
个体和互动高于流程和工具,
工作的软件高于详尽的文档,
客户合作高于合同谈判,
响应变化高于遵循计划,
也就是说,尽管右项有其价值,我们更重视左项的价值。

敏捷宣言遵循的原则
我们遵循以下原则:
我们最重要的目标,是通过持续不断地及早交付有价值的软件使客户满意。
欣然面对需求变化,即使在开发后期也一样。为了客户的竞争优势,敏捷过程掌控变化。
经常地交付可工作的软件,相隔几星期或一两个月,倾向于采取较短的周期。
业务人员和开发人员必须相互合作,项目中的每一天都不例外。
激发个体的斗志,以他们为核心搭建项目。提供所需的环境和支援,辅以信任,从而达成目标。
不论团队内外,传递信息效果最好效率也最高的方式是面对面的交谈。
可工作的软件是进度的首要度量标准。
敏捷过程倡导可持续开发。责任人、开发人员和用户要能够共同维持其步调稳定延续。
坚持不懈地追求技术卓越和良好设计,敏捷能力由此增强。
以简洁为本,它是极力减少不必要工作量的艺术。
最好的架构、需求和设计出自自组织团队。
团队定期地反思如何能提高成效,并依此调整自身的举止表现。

敏捷开发有5个主要的价值观和3个支柱,一般来说,敏捷开发也应该有这些特性。

敏捷开发的价值观

  • 专注:由于我们在一段时间内只专注于少数几件事情,所以我们可以很好地合作并获得优质的产出。我们能够更快地交付有价值的事项。
  • 公开:在团队合作中,大家都会表达我们做得如何,以及遇到的障碍。我们发现将担忧说出来是一件好事,因为只有这样才能让这些担忧及时得到解决。
  • 尊重:因为我们在一起工作,分享和成功失败,这有助于培养并加深互相之间的尊重,并帮助彼此成为值得尊重的人。
  • 承诺:由于对自己的命运有更大的掌握,我们会有更坚强的信念获得成功。
  • 勇气:因为我们不得单打独斗,我们能够感受到支持,而且掌握更多的资源。这一切赋予我们勇气去迎接更大的挑战。

敏捷开发的支柱

  • 透明:过程中的关键环节对于那些对产出负责的人必须是显而易见的。要拥有透明,就要为这些关键环节制定统一的标准,这样所有留意这些环节的人都会对观察到的事物有统一的理解。
  • 检视:Scrum 的使用者必须经常检视 Scrum 的工件和完成 Sprint 目标的进展,以便发现不必要的差异。检视不应该过于频繁而阻碍工作本身。当检视是由技能娴熟的检视者在工作中勤勉地执行时,效果最佳。
  • 适应:如果检视者发现过程中的一个或多个方面偏离可接受范围以外,并且将会导致产品不可接受时,就必须对过程或过程化的内容加以调整。调整工作必须尽快执行如此才能最小化进一步的偏离。

1.2 敏捷(Scrum)开发的流程

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开发流程(不借助于任何工具)如下

  1. 产品负责人将整个产品设计成产品Backlog。产品Backlog本质就是需求列表。
  2. 召开Sprint计划会议。
  3. Sprint计划会议定下的任务写在纸条上贴在任务墙,让Scrum成员认领分配并细分。(任务墙就是把未完成、正在做、已完成 的工作状态贴到一个墙上,这样大家都可以看得到任务的状态 )
  4. 举行每日站立会议,让大家在每日会议上总结昨天做的事情、遇到什么困难,今天开展什么任务。
  5. 绘制燃尽图,保证任务的概况能够清晰看到。(燃尽图把当前的任务总数和日期一起绘制,每天记录一下,可以看到每天还剩多少个任务,直到任务数为0 ,这个sprint就完成了)。
  6. Sprint评审会议是在Sprint完成时举行,要向客户演示自己完成的软件产品 。
  7. 最后是Sprint总结会议,以轮流发言方式进行,每个人都要发言,总结上一次Sprint中遇到的问题、改进和大家分享讨论。

二、如何理解敏捷开发

2.1 敏捷开发的优势

敏捷开发(Scrum)只是一套积极响应变化的开发框架。如果开发效率被定义为更短时间里开发出需要的功能(或者相同的时间里开发出更多的功能),那么敏捷开发肯定会降低效率。想一想效率最高的开发方式是啥:需求明确,文档详尽,码不停蹄。而敏捷开发与"效率最高"的开发方式正好相反。

  • 敏捷开发讲究影响变化,因此需求特别不明确,甚至开发过程中,需求还有变更。
  • 需求变化就意味着返工,返工不仅浪费时间,更会浪费开发人员的感情,导致Scrum成员士气低迷。
  • 敏捷开发弱化了文档,很多时候,你有疑问,想找一下文档,会发现文档描述非常模糊,甚至根本就没有文档。
  • 敏捷开发强调(依赖)大量沟通,沟通就难免会打断手头的工作。

既然敏捷开发会降低效率,那么为什么这么企业(包括咱们公司)还要使用敏捷开发呢?因为敏捷开发可以大幅提升投入产出比。高效率开发出来的功能,如果不是客户需要的,那就是纯浪费,效率再高,投入产出比也是零。具体来说,敏捷开发对投入产出比的提高来主要来自以下几个方面:

  • Sprint周期短,Scrum人员少:毕竟"人月神话",制定计划很难,制定靠谱的长期多人参与的计划更是难上青天。因此敏捷开发因为周期短人员少,制定出来的计划就有了天然的优势。
  • 不断集成,不断提供一个可展现的成果:**没有人可能真的理解用户需要什么,哪怕用户自己也不行。**优秀的敏捷开发应该(这里是应该,仅仅应该)从第一天开始就持续集成,这样更加方便检查,让用户确认,一旦开发偏离用户需求,就可以及时更正。
  • 集中资源,解决用户最迫切的需求:每次Sprint计划会议会对Backlog里面的每一项进优先级排序,在每个Sprint中,只开发用户最迫切的需求。这样就保证了本来就有限的资源不会浪费。

2.2 敏捷开发的劣势

敏捷开发的劣势也是明显的,就是太依赖人了。

  • 没有文档,信息传递靠沟通。如果团队人员稳定还好,沟通有默契,很多基础信息也都了解,有些问题,提一句,听者立马懂了。如果团队人员流动大,这就太恐怖了,信息的高度不对等,沟通方式的不同步,这样沟通成本会成倍增加,新来的同事受挫感强,很难融入,老同事开发经常被打断,也烦。
  • 敏捷开发中,需求是会变更的。这个就要求团队成员的技术实力比较高,不仅懂自己这摊,还要懂其他人开发的内容,在开发的过程中,代码就要考虑到各方面问题,留有扩展性,这样在每次需求变更中,大家才能更快的找到最佳解决方案从而适配新的需求。另外,并不是每个Sprint每个人的Task都会非常均匀,Scrum成员最好能提其他伙伴分担一点,比如Task开发完成后,开发人员自己也可以跑测试案例的。
  • 敏捷开发的工作量很难有一个衡量标准。建立并执行的一个衡量标准的成本是极其高的,因此敏捷开发中的工作量认定更多是依靠Scrum成员的真诚。一旦失去真诚氛围,敏捷开发就只剩空壳,要知道没什么比每个人都要假装有事情干更恐怖的了。

我觉得可以这么说,优秀的敏捷开发团队一定是由一群自建设的人自组织起来的。敏捷开发可以有Scrum Master(Scrum Master本质就是敏捷开发教练,指导大家更好的进行敏捷开发)(当然最好可以没有Scrum Master,因为每个都是Scrum Master),但是Scrum Master只能深度参与,决不能Push,一旦Push就会毁了自建设,这等于毁了敏捷开发的基石。

由Crypto库的坑到Python编码问题

2019年2月完成,迁移过来的

最近维护之前实习小哥哥的代码,小哥哥代码写的好,但是都是Python3的,目前公司大多数代码还是Python2,因此维护过程中,就难免遇到移植的问题。

Crypto库的坑

将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

Python编码问题

上节问题三说到底就是编码问题,我是被编码坑了好多次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+ba+c 是报错还是有结果,为什么?

a='编码'
b=b'code'
c=u'code'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.