月度归档: 2018年11月

需求池,开发池,交付池,避免发布混乱

最近公司开始搞sprint,2周一个sprint,这导致了一些问题,有一些存在相互依赖的功能,因为2周的时间线规定,被强行划成了两个sprint,老板要求测试人员必须跟着sprint,这导致环境问题频出,因为功能都不完整啊!所以我提出了建议,引入版本的概念。开发人员可以继续按照他们的sprint走,测试人员和项目经理,开发经理打成一致,把一个完整的功能做成一个版本,测试人员关注的是版本,而不是sprint。项目经理可以决定每次版本里包含哪些功能,但是版本里要求的功能一定是完整,且没有与另一个sprint或者版本存在相互依赖关系,版本可大可小。不可以再为了赶时间点,上一些不完整的功能呢。
目标是“稳定且完整”,而不是“依赖下个版本”

通过python-docx给word文档中的指定位置添加表格

需求

1.读取一个已有的word文档。docx格式。
2.在该word文档中,通过一个给定的文字。找到该位置。在该位置的下方添加一个表格。例如在图中“BUG情况表”的下方插入一个表格

3.表格内容如下。要求添加完该表格后,如果表格内容发生变更。还能再次通过该程序,修改表格里的数据。

设计

通过python-docx读取word文档。通过document.paragraphs定位指定文字的位置。
通过xlwings读取excel的内容,存成list[list[]]。
通过docx的add_table增加一个表格,并且更改表头颜色,合并表格等操作
通过识别表头的第一行,判断是否是已经存在这个表格,来决定是否要删除原表格

代码

# -*- coding: UTF-8 -*-
import sys
from copy import deepcopy
import xlwings
from docx import Document
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml
def copy_table_after(table, paragraph):
    tbl, p = table._tbl, paragraph._p
    new_tbl = deepcopy(tbl)
    p.addnext(new_tbl)
def move_table_after(table, paragraph):
    tbl, p = table._tbl, paragraph._p
    p.addnext(tbl)
def get_excel_date(filename):
    '''
    获得excel里的所有内容,返回list
    :param filename:  excel路径
    :return: list[list[]]
    '''
    app = xlwings.App(visible=False, add_book=True)
    app.display_alerts = False
    app.screen_updating = False
    wb = app.books.open(filename)
    sht = wb.sheets[0]
    rng = sht.range('A1')
    # 把excel里的数据读取成 年-月-日 时:分:秒的格式
    my_date_handler = lambda year, month, day, hour, minute, second, **kwargs: "%04i-%02i-%02i %02i:%02i:%02i" % (
        year, month, day, hour, minute, second)
    # 取出所有内容,这里用ig这个变量,是为了庆祝I.G获得LOL S8赛季总冠军
    ig = rng.current_region.options(index=False, numbers=int, empty='N/A', dates=my_date_handler)
    result = ig.value
    wb.close()
    app.quit()
    return result
def delete_table_with_title(document,expect_text):
    allTables = document.tables
    for activeTable in allTables:
        if activeTable.cell(0, 0).paragraphs[0].text == expect_text:
            print('删除成功')
            activeTable._element.getparent().remove(activeTable._element)
def insert_table_after_text(file_name,excel_name,expect_text):
    document = Document(file_name)
    # 因为docx读出来的都是unicode类型的,所以我们要用unicode类型的进行查找
    expect_text=expect_text.decode('utf-8')
    delete_table_with_title(document,expect_text)
    target = None
    for paragraph in document.paragraphs:
        paragraph_text = paragraph.text
        if paragraph_text.endswith(expect_text):
            target = paragraph
            break
    if target is not None:
        records = get_excel_date(excel_name)
        # 获得excel数据的栏数,初始化一个空的table
        col = len(records[0])
        table = document.add_table(rows=1, cols=col)
        table.style = 'Table Grid'
        # 给table加一个表头,并且合并第一栏
        shading_elm_1 = parse_xml(r'<w:shd {} w:fill="D9E2F3"/>'.format(nsdecls('w')))
        table.rows[0].cells[0]._tc.get_or_add_tcPr().append(shading_elm_1)
        table.rows[0].cells[0].text=expect_text
        table_row=table.rows[0]
        first=table_row.cells[0]
        end=table_row.cells[-1]
        first.merge(end)
        # 合并结束,开始把excel里的内容添加到table里
        for tr_list in records:
            row_cells = table.add_row().cells
            index = 0
            for td_list in tr_list:
                row_cells[index].text = td_list
                index = index + 1
        # 把添加的table移动到指定的位置
        move_table_after(table, target)
        # 保存
    document.save(file_name)
if __name__ == '__main__':
    insert_table_after_text('demo2.docx', 'demo.xlsx',"BUG情况表")

最终效果

API测试:通过faker生成测试数据,通过schema检查返回结果

需求

假定有如主图相同的http请求。我们一般的做法是,用postman去抓取http请求,然后修改request的body或者header里的数据,点击send按钮,检查返回的response的body是否正确。
对于输入。一般来说,我们会纯手工,或者半自动的,设计测试用例。例如使用边界值分析,等价类划分等方法,用在我们的输入参数中。比如我参数中的configname最多200个参数,我测试输入201个参数。
对于输出。一般来说,我们大部分时候是肉眼检查,或者写代码,通过jsonpath取参数,然后判断是否存在来检查。
这里我打算用一个新的方法来降低测试的手工特性,让他更自动化一点。以下想法还处于调试阶段,用于大规模使用,暂时不行。

设计

输入修改方案:引入faker库和jsonschema库。通过这两个库,我们可以产生随机的json串
faker是我无意之间发现的,能按照规律产生随机字的库,例如

fake.name()

是产生一个随机的名字,只要加入适当的providers,就能按照需要的规则产生随机字
jsonschema这个用的人很多,这里就不介绍了,下面推荐一个网站,能把json请求转换为schema格式
https://jsonschema.net/
schema中会注明每个字段的规则,例如是string类型还是integer。
输出修改方案:使用jsonschma的validate方法来检查(这种检查方法目前有一些检查不充分,但是已经可以让测试人员减少一些工作量了)

jsonschema.validate(response, schema)

使用方案

1.去postman抓取http请求,并且记录下所需要的输入json和输出json

2.打开https://jsonschema.net/ 把输入json和输入json 转换成jsonschema

3.把输入jsonschema文件,输出文件jsonschema放入相应的目录,自己写一个用于生成随机requestbody的provider和一个测试用的主函数

4.运行测试主入口文件,打印一下发送的json文件,看是不是随机化了,结果是确实随机化了。

代码

测试主入口test_json_from_schema.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import json
import faker
import jsonschema
import requests
from jsonschema.exceptions import ValidationError
import jsonprovider
def generate_request(request_json_schema):
    '''
    通过schema生成随机测试数据
    :param request_json_schema:
    :return:
    '''
    fake = faker.Faker()
    fake.add_provider(jsonprovider.JSONProvider)
    request_body = fake.json(json.load(open(request_json_schema)))
    print(request_body)
    return request_body
def check_json_schema(response, schema):
    '''
    通过json_schema检查返回的json串
    :param response:
    :param schema:
    :return:
    '''
    result = True
    try:
        jsonschema.validate(response, schema)
    except ValidationError, e:
        print("fail")
        result = False
    return result
if __name__ == '__main__':
    # 生成request body
    body = generate_request("schema_file/create_config_request_schemas.json")
    # 使用request库发送post请求
    url = "https://dev.honcloud.honeywell.com.cn:8080/dashboard/clustercentre/configmng/newconfig/addconfig"
    headers = {"Content-Type": "application/json", "authorization": "48a5eb61-914e-4b3a-a7a3-0b25f72d06d7"}
    response = requests.post(url, data=body, headers=headers)
    print(response.json())
    response_json=response.json()
    response_schema="schema_file/create_config_response_schemas.json"
    # 用生成的response的schema来检查
    result=check_json_schema(response_json,response_schema)
    print(result)

jsonprovider.py可以自行百度一个faker的provider的方案,我这里做的也不好,随机出来的值只遵循了字符类型,后面会考虑融合我们的边界值分析,等价类划分的方案进来,完善这个jsonprovider.py之后再放出来

python读取excel内容再转变成html添加到outlook中

需求

读取excel里的表格里的内容,然后打开本机的outlook。把excel里的内容添加到正文里,注意。这里是要添加到正文!正文!正文!而不是添加到附件里

设计思路

1.excel处理

打开excel的方法有很多,但是在不知道excel里,行和列的大小的情况下,就能获得excel里的非空值行列的办法不多。我这边采用的是xlwings这个库,用的方法是range.current_region这个方法。这个方法会选择当前range下,有值的区域(非空区域)
通过配置options选项,可以指定excel获得的值的格式,int或者string,或者空值返回N/A

2.打开outlook

打开outlook在windows上只能用win32模块了,通过下面方法可以打开outlook并且创建一个空的email

olook = win32com.client.Dispatch("Outlook.Application")
mail = olook.CreateItem(0)

然后配置邮件的htmlbody和outlook邮件悬停(可以手动更改邮件内容,手动发送),可以用以下方法

mail.HTMLBody = body_html
mail.Display(True)

完整代码

下面是完整代码,读者修改一下excel的路径,就可以自己去试试啦

# -*- coding: UTF-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import win32com.client
import xlwings
def get_excel_date(filename):
    '''
    获得excel里的所有内容,返回list
    :param filename:  excel路径
    :return: list[list[]]
    '''
    app = xlwings.App(visible=False, add_book=True)
    app.display_alerts = False
    app.screen_updating = False
    wb = app.books.open(filename)
    sht = wb.sheets[0]
    rng = sht.range('A1')
    # 把excel里的数据读取成 年-月-日 时:分:秒的格式
    my_date_handler = lambda year, month, day, hour, minute, second, **kwargs: "%04i-%02i-%02i %02i:%02i:%02i" % (
    year, month, day, hour, minute, second)
    # 取出所有内容,这里用ig这个变量,是为了庆祝I.G获得LOL S8赛季总冠军
    ig = rng.current_region.options(index=False, numbers=int, empty='N/A', dates=my_date_handler)
    result = ig.value
    wb.close()
    app.quit()
    return result
if __name__ == '__main__':
    olook = win32com.client.Dispatch("Outlook.Application")
    mail = olook.CreateItem(0)
    mail.Recipients.Add("357244849@qq.com")
    mail.Subject = "test report"
    body_html = ""
    body_html = body_html + '<body>Hi all:<br/>以下是XXXXX项目今天的测试情况:<br/><br/>明天的测试计划:<br/><br/>目前的bug:'
    body_html = body_html + '<table width="1" border="1" cellspacing="1" cellpadding="1" height="100">'
    # 这里用rng 是因为这一次rng止步8强!
    rng_list = get_excel_date("C:\lzw_programming\resource\reports\CurrentVersionAllDefectTable.xlsx")
    # 表头
    for tr_list in rng_list[:1]:
        body_html = body_html + "<tr>"
        for td_list in tr_list:
            # 这里也是奇葩需求,因为要求表头不能换行,所以用了nowrap
            body_html = body_html + '<th bgcolor="#C3C3C3" nowrap="nowrap">' + td_list + '</th>'
        body_html = body_html + "</tr>"
    # 表内容
    for tr_list in rng_list[1:]:
        body_html = body_html + "<tr>"
        for td_list in tr_list:
            body_html = body_html + "<td>" + td_list + "</td>"
        body_html = body_html + "</tr>"
    body_html = body_html + '</table>'
    body_html = body_html + "</body>"
    mail.HTMLBody = body_html
    mail.Display(True)

解决pyinstaller不兼容python-docx的方法

需求

python-docx是一个python的读写word的库,可以用来读写word文档,向word文档里插入表格。例如如下的操作docx的代码:

from docx import Document
document = Document()
document.add_heading('Document Title', 0)
p = document.add_paragraph('A plain paragraph having some ')
p.add_run('bold').bold = True
p.add_run(' and some ')
p.add_run('italic.').italic = True
document.add_heading('Heading, level 1', level=1)
document.add_paragraph('Intense quote', style='Intense Quote')
document.add_paragraph(
    'first item in unordered list', style='List Bullet'
)
document.add_paragraph(
    'first item in ordered list', style='List Number'
)
records = (
    (3, '101', 'Spam'),
    (7, '422', 'Eggs'),
    (4, '631', 'Spam, spam, eggs, and spam')
)
table = document.add_table(rows=1, cols=3,style='Light Grid Accent 1')
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Qty'
hdr_cells[1].text = 'Id'
hdr_cells[2].text = 'Desc'
for qty, id, desc in records:
    row_cells = table.add_row().cells
    row_cells[0].text = str(qty)
    row_cells[1].text = id
    row_cells[2].text = desc
document.add_page_break()
document.save('demo.docx')

 
pyinstaller是python打包成exe的工具。
当我们要把编写好的使用了python-docx的程序打包时,问题来了。
首先,命令行打包

pyinstaller -D word_generate.py

这个没问题,word_generate.py是我的主程序文件。这里打包也不报错。但是下一步,运行的时候,duang~报错了,报错如下:

C:lzw_programmingjira_testdistword_generate>word_generate.exe
Traceback (most recent call last):
  File "word_generate.py", line 4, in <module>
  File "site-packagesdocxapi.py", line 25, in Document
  File "site-packagesdocxopcpackage.py", line 116, in open
  File "site-packagesdocxopcpkgreader.py", line 32, in from_file
  File "site-packagesdocxopcphys_pkg.py", line 31, in __new__
docx.opc.exceptions.PackageNotFoundError: Package not found at 'C:LZW_PR~1JIRA_T~1distWORD_G~1docxtemplatesdefault.docx'
[4232] Failed to execute script word_generate

解决方法

在翻了很多地方之后,终于找到了解决方法。很简单。增加一个hook-docx.py文件在PyInstallerhooks目录下就可以了。下面是文件内容以及路径

#-----------------------------------------------------------------------------
# Copyright (c) 2018-2018, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
from PyInstaller.utils.hooks import collect_data_files
datas = collect_data_files("docx")

路径:

python根据excel的一列数据产生加权随机数

需求

最近遇到一个奇葩的事,行政那边说,让估算一下明年的这些杂七杂八费,然后给了我一个excel,里面有200多个这样的费用。我没做过行政,也搞不清这个到底咋来,为什么要弄这玩意。一番交流,原来是上头要的,不一定看,但是东西得有,让我弄个数字和去年差不多的就行。于是变有了下面的故事

设计

1.需要读取excel. 用的库是xlwing。可以根据sheet名称定位sheet,然后用range()方法定位一行,一列,或者指定几个单元格。
例如:定位 A1到A3 ,range1=sheet.range("A1:A3")
具体用法很多,在xlwing的官网上有介绍。http://docs.xlwings.org/en/stable/api.html
2.需要产生随机数,用的库是numpy,产生一个加权的随机数,然后我用的加权随机数规则为:
【-10%,概率10%】【不变,概率30%】【+2%,概率30%】【+5%,概率20%】【+10%,概率10%】

代码实现

代码中分为两个function,一个是读取excel,一个是根据一个值,产生一个加权随机数。观众老爷可以参考,对有疑问的地方,可以再来与我沟通O(∩_∩)O

# -*- coding: UTF-8 -*-
import xlwings
import numpy as np
def import_excel(filename, sheet_name, source,target):
    '''
    导入一个excel,根据某一列的值,产生一个加权随机数,覆盖掉另一列的值
    :param filename: excel路径
    :param sheet_name: sheet的名字
    :param source: 源列的第一个值 如:A1,C1
    :param target: 目标列的第一个值,如B1,D2
    :return:
    '''
    app = xlwings.App(visible=False, add_book=True)
    app.display_alerts = False
    app.screen_updating = False
    wb = app.books.open(filename)
    sht = wb.sheets[sheet_name]
    rng1 = sht.range(source)
    rng2 = sht.range(source).end('down')
    rng3 = sht.range(rng1, rng2)
    all_rng = rng3.value
    list_dest = []
    for item in all_rng:
        dest_item = arithmetic(item)
        # 这里要把结果变成[[1],[2]]这样的形式,才是一列的数据
        # 如果是[1,2]这样,是一行的数据
        list_temp = []
        list_temp.append(dest_item)
        list_dest.append(list_temp)
    sht.range(target).value = list_dest
    wb.save(filename)
    wb.close()
    app.quit()
def arithmetic(source):
    '''
    加权随机算法
    :param source: 输入一个原值
    :return: 返回浮动后的值
    '''
    power = np.array([0.1, 0.3, 0.3, 0.2, 0.1])
    index = np.random.choice([-0.1, 0, 2, 5, 10], p=power.ravel())
    rand = source * index / 100
    dest = int(source + rand)
    return dest
if __name__ == '__main__':
    import_excel("Book1.xlsx", 'Sheet1','C2','D2')

运行结果


苏ICP备18047533号-2