如何簡單實現(xiàn)接口自動化測試(基于 python)

一、簡介

本文從一個簡單的登錄接口測試入手沈善,一步步調(diào)整優(yōu)化接口調(diào)用姿勢乡数,然后簡單討論了一下接口測試框架的要點,最后介紹了一下我們目前正在使用的接口測試框架pithy闻牡。期望讀者可以通過本文對接口自動化測試有一個大致的了解净赴。

二、引言

1罩润、為什么要做接口自動化測試玖翅?

在當前互聯(lián)網(wǎng)產(chǎn)品迭代頻繁的背景下,回歸測試的時間越來越少,很難在每個迭代都對所有功能做完整回歸金度。但接口自動化測試因其實現(xiàn)簡單应媚、維護成本低,容易提高覆蓋率等特點猜极,越來越受重視中姜。

2、為什么要自己寫框架呢跟伏?
使用requets + unittest很容易實現(xiàn)接口自動化測試丢胚,而且requests的api已經(jīng)非常人性化,非常簡單受扳,但通過封裝以后(特別是針對公司內(nèi)特定接口)携龟,再加上對一些常用工具的封裝,可以進一步提高業(yè)務腳本編寫效率勘高。

三峡蟋、環(huán)境準備

確保本機已安裝python2.7以上版本,然后安裝如下庫:

pip install flask
pip install requests

后面我們會使用flask寫一個用來測試的接口相满,使用requests去測試层亿。

加入我們,立美,匿又,交流!群建蹄。碌更。642830685,領取最新軟件測試大廠面試資料和Python自動化洞慎、接口痛单、框架搭建學習資料!一起學習交流

四劲腿、測試接口準備

下面使用flask實現(xiàn)兩個http接口旭绒,一個登錄,另外一個查詢詳情焦人,但需要登錄后才可以挥吵,新建一個demo.py文件(注意,不要使用windows記事本)花椭,把下面代碼copy進去忽匈,然后保存、關閉矿辽。

接口代碼

#!/usr/bin/python
# coding=utf-8
from flask import Flask, request, session, jsonify

USERNAME = 'admin'
PASSWORD = '123456'

app = Flask(__name__)
app.secret_key = 'pithy'


@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != USERNAME:
            error = 'Invalid username'
        elif request.form['password'] != PASSWORD:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            return jsonify({'code': 200, 'msg': 'success'})
    return jsonify({'code': 401, 'msg': error}), 401


@app.route('/info', methods=['get'])
def info():
    if not session.get('logged_in'):
        return jsonify({'code': 401, 'msg': 'please login !!'})
    return jsonify({'code': 200, 'msg': 'success', 'data': 'info'})

if __name__ == '__main__':
    app.run(debug=True)

最后執(zhí)行如下命令:

python demo.py

響應如下:

 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat

大家可以看到服務已經(jīng)起來了丹允。

接口信息

登錄接口

-請求url

/login

-請求方法

post

-請求參數(shù)


-響應信息


詳情接口

-請求url

/info

-請求方法

get

-請求cookies



-響應信息


五郭厌、編寫接口測試

測試思路

  • 使用requests [使用鏈接] 庫模擬發(fā)送HTTP請求。

  • 使用python標準庫里unittest寫測試case雕蔽。

腳本實現(xiàn)

#!/usr/bin/python
# coding=utf-8
import requests
import unittest


class TestLogin(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.login_url = 'http://127.0.0.1:5000/login'
        cls.info_url = 'http://127.0.0.1:5000/info'
        cls.username = 'admin'
        cls.password = '123456'

    def test_login(self):
        """
        測試登錄
        """
        data = {
            'username': self.username,
            'password': self.password
        }

        response = requests.post(self.login_url, data=data).json()

        assert response['code'] == 200
        assert response['msg'] == 'success'

    def test_info(self):
        """
        測試info接口
        """

        data = {
            'username': self.username,
            'password': self.password
        }

        response_cookies = requests.post(self.login_url, data=data).cookies
        session = response_cookies.get('session')
        assert session

        info_cookies = {
            'session': session
        }

        response = requests.get(self.info_url, cookies=info_cookies).json()
        assert response['code'] == 200
        assert response['msg'] == 'success'
        assert response['data'] == 'info'

六折柠、優(yōu)化

封裝接口調(diào)用

寫完這個測試登錄腳本,你或許會發(fā)現(xiàn)萎羔,在整個項目的測試過程液走,登錄可能不止用到一次碳默,如果每次都這么寫贾陷,會不會太冗余了? 對嘱根,確實太冗余了髓废,下面做一下簡單的封裝,把登錄接口的調(diào)用封裝到一個方法里该抒,把調(diào)用參數(shù)暴漏出來,示例腳本如下:

#!/usr/bin/python
# coding=utf-8
import requests
import unittest
try:
    from urlparse import urljoin
except ImportError:
    from urllib.parse import urljoin


class DemoApi(object):

    def __init__(self, base_url):
        self.base_url = base_url

    def login(self, username, password):
        """
        登錄接口
        :param username: 用戶名
        :param password: 密碼
        """
        url = urljoin(self.base_url, 'login')
        data = {
            'username': username,
            'password': password
        }

        return requests.post(url, data=data).json()

    def get_cookies(self, username, password):
        """
        獲取登錄cookies
        """
        url = urljoin(self.base_url, 'login')
        data = {
            'username': username,
            'password': password
        }

        return requests.post(url, data=data).cookies

    def info(self, cookies):
        """
        詳情接口
        """
        url = urljoin(self.base_url, 'info')
        return requests.get(url, cookies=cookies).json()


class TestLogin(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.base_url = 'http://127.0.0.1:5000'
        cls.username = 'admin'
        cls.password = '123456'
        cls.app = DemoApi(cls.base_url)

    def test_login(self):
        """
        測試登錄
        """
        response = self.app.login(self.username, self.password)
        assert response['code'] == 200
        assert response['msg'] == 'success'

    def test_info(self):
        """
        測試獲取詳情信息
        """
        cookies = self.app.get_cookies(self.username, self.password)
        response = self.app.info(cookies)
        assert response['code'] == 200
        assert response['msg'] == 'success'
        assert response['data'] == 'info'

OK凑保,在這一個版本中冈爹,我們不但在把登錄接口的調(diào)用封裝成了一個實例方法,實現(xiàn)了復用欧引,而且還把host(self.base_url)提取了出來频伤,但問題又來了,登錄之后芝此,登錄接口的http響應會把session以 cookie的形式set到客戶端憋肖,之后的接口都會使用此session去請求,還有婚苹,就是在接口調(diào)用過程中岸更,希望可以把日志打印出來,以便調(diào)試或者出錯時查看膊升。

好吧怎炊,我們再來改一版。

保持cookies&增加log信息
使用requests庫里的同一個Session對象(它也會在同一個Session 實例發(fā)出的所有請求之間保持 cookie)廓译,即可解決上面的問題评肆,示例代碼如下:

#!/usr/bin/python
# coding=utf-8
import unittest
from pprint import pprint
from requests.sessions import Session
try:
    from urlparse import urljoin
except ImportError:
    from urllib.parse import urljoin


class DemoApi(object):

    def __init__(self, base_url):
        self.base_url = base_url
        # 創(chuàng)建session實例
        self.session = Session()

    def login(self, username, password):
        """
        登錄接口
        :param username: 用戶名
        :param password: 密碼
        """
        url = urljoin(self.base_url, 'login')
        data = {
            'username': username,
            'password': password
        }

        response = self.session.post(url, data=data).json()
        print('\n*****************************************')
        print(u'\n1、請求url: \n%s' % url)
        print(u'\n2责循、請求頭信息:')
        pprint(self.session.headers)
        print(u'\n3糟港、請求參數(shù):')
        pprint(data)
        print(u'\n4、響應:')
        pprint(response)
        return response

    def info(self):
        """
        詳情接口
        """
        url = urljoin(self.base_url, 'info')
        response = self.session.get(url).json()

        print('\n*****************************************')
        print(u'\n1院仿、請求url: \n%s' % url)
        print(u'\n2秸抚、請求頭信息:')
        pprint(self.session.headers)
        print(u'\n3速和、請求cookies:')
        pprint(dict(self.session.cookies))
        print(u'\n4、響應:')
        pprint(response)
        return response


class TestLogin(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.base_url = 'http://127.0.0.1:5000'
        cls.username = 'admin'
        cls.password = '123456'
        cls.app = DemoApi(cls.base_url)

    def test_login(self):
        """
        測試登錄
        """
        response = self.app.login(self.username, self.password)
        assert response['code'] == 200
        assert response['msg'] == 'success'

    def test_info(self):
        """
        測試獲取詳情信息
        """
        self.app.login(self.username, self.password)
        response = self.app.info()
        assert response['code'] == 200
        assert response['msg'] == 'success'
        assert response['data'] == 'info'

大功告成剥汤,我們把多個相關接口調(diào)用封裝到一個類中颠放,使用同一個requests Session實例來保持cookies,并且在調(diào)用過程中打印出了日志吭敢,我們所有目標都實現(xiàn)了碰凶,但再看下腳本,又會感覺不太舒服鹿驼,在每個方法里欲低,都要寫一遍print 1、2畜晰、3… 要拼url砾莱、還要很多細節(jié)等等,但其實我們真正需要做的只是拼出關鍵的參數(shù)(url參數(shù)凄鼻、body參數(shù)或者傳入headers信息)腊瑟,可不可以只需定義必須的信息,然后把其它共性的東西都封裝起來呢块蚌,統(tǒng)一放到一個地方去管理闰非?

封裝重復操作
來,我們再整理一下我們的需求:

首先峭范,不想去重復做拼接url的操作财松。

然后,不想每次都去手工打印日志虎敦。

不想和requests session打交道游岳。

只想定義好參數(shù)就直接調(diào)用。

我們先看一下實現(xiàn)后其徙,腳本可能是什么樣:

class DemoApi(object):

    def __init__(self, base_url):
        self.base_url = base_url

    @request(url='login', method='post')
    def login(self, username, password):
        """
        登錄接口
        """
        data = {
            'username': username,
            'password': password
        }

        return {'data': data}

    @request(url='info', method='get')
    def info(self):
        """
        詳情接口
        """
        pass

調(diào)用登錄接口的日志:

******************************************************
1胚迫、接口描述
登錄接口

2、請求url
http://127.0.0.1:5000/login

3唾那、請求方法
post

4访锻、請求headers
{
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "keep-alive",
    "User-Agent": "python-requests/2.7.0 CPython/2.7.10 Darwin/16.4.0"
}

5、body參數(shù)
{
    "password": "123456",
    "username": "admin"
}

6闹获、響應結(jié)果
{
    "code": 200,
    "msg": "success"
}

在這里期犬,我們使用python的裝飾器功能,把公共特性封裝到裝飾器中去實現(xiàn)”芊蹋現(xiàn)在感覺好多了龟虎,沒什么多余的東西了,我們可以專注于關鍵參數(shù)的構(gòu)造沙庐,剩下的就是如何去實現(xiàn)這個裝飾器了鲤妥,我們先理一下思路:

1佳吞、獲取裝飾器參數(shù)

2、獲取函數(shù)/方法參數(shù)

3棉安、把裝飾器和函數(shù)定義的參數(shù)合并

4底扳、拼接url

5、處理requests session贡耽,有則使用衷模,無則新生成一個

6、組裝所有參數(shù)蒲赂,發(fā)送http請求并打印日志
因篇幅限制阱冶,源碼不再列出,有興趣的同學可以查看已經(jīng)實現(xiàn)的源代碼凳宙。

七熙揍、擴展

http接口請求的姿勢我們定義好了,我們還可以做些什么呢氏涩?
1、[x] 非HTTP協(xié)議接口

2有梆、[x] 測試用例編寫

3是尖、[x] 配置文件管理

4、[x] 測試數(shù)據(jù)管理

5泥耀、[x] 工具類編寫

6饺汹、[x] 測試報告生成

7、[x] 持續(xù)集成

8痰催、[x] 等等等等
需要做的還是挺多的兜辞,要做什么不要做什么,或者先做哪個夸溶,我覺得可以根據(jù)以下幾點去判斷:

是否有利于提高團隊生產(chǎn)效率逸吵?

是否有利于提高測試質(zhì)量?

有沒有現(xiàn)成的輪子可以用缝裁?

下面就幾項主要的點進行一下說明扫皱,限于篇幅,不再展開了捷绑。

測試報告

這個應該是大家最關心的了韩脑,畢竟這是測試工作的產(chǎn)出;

目前python的主流單元測試框均有report插件粹污,因此不建議自己再編寫段多,除非有特殊需求的。

pytest:推薦使用pytest-html和allure pytest壮吩。

unittest:推薦使用HTMLTestRunner进苍。

持續(xù)集成

持續(xù)集成推薦使用Jenkins蕾总,運行環(huán)境、定時任務琅捏、觸發(fā)運行生百、郵件發(fā)送等一系列功能均可以在Jenkins上實現(xiàn)。

測試用例編寫

推薦遵守如下規(guī)則:
原子性:每個用例保持獨立柄延,彼此不耦合蚀浆,以降低干擾。

專一性:一個用例應該專注于驗證一件事情搜吧,而不是做很多事情市俊,一個測試點不要重復驗證。

穩(wěn)定性:絕大多數(shù)用例應該是非常穩(wěn)定的滤奈,也就是說不會經(jīng)常因為除環(huán)境以外的因素掛掉摆昧,因為如果在一個測試項目中有很多不穩(wěn)定的用例的話,測試結(jié)果就不能很好的反應項目質(zhì)量蜒程。

分類清晰:有相關性的用例應寫到一個模塊或一個測試類里绅你,這樣做即方便維護,又提高了報告的可讀性昭躺。

測試工具類
這個可以根據(jù)項目情況去做忌锯,力求簡化一些類庫的使用,數(shù)據(jù)庫訪問领炫、日期時間偶垮、序列化與反序列化等數(shù)據(jù)處理,或者封裝一些常用操作帝洪,如隨機生成訂單號等等似舵,以提高腳本編寫效率。

測試數(shù)據(jù)管理
常見的方式有寫在代碼里葱峡、寫在配置文件里(xml砚哗、yaml、json族沃、.py频祝、excel等)、寫在數(shù)據(jù)庫里等脆淹,該處沒有什么好推薦的常空,建議根據(jù)個人喜好,怎么方便怎么來就可以盖溺。

八漓糙、pithy測試框架介紹

pithy意為簡潔有力的,意在簡化自動化接口測試烘嘱,提高測試效率昆禽。

目前實現(xiàn)的功能如下:
1蝗蛙、一鍵生成測試項目

2、http client封裝

3醉鳖、thrift接口封裝

4捡硅、簡化配置文件使用

5、優(yōu)化JSON盗棵、日期等工具使用

編寫測試用例推薦使用pytest壮韭,pytest提供了很多測試工具以及插件,可以滿足大部分測試需求纹因。

安裝

pip install pithy-test
pip install pytest

使用
一鍵生成測試項目

>>>  pithy-cli init
請選擇項目類型,輸入api或者app: api
請輸入項目名稱,如pithy-api-test: pithy-api-test
開始創(chuàng)建pithy-api-test項目
開始渲染...
生成 api/.gitignore                   [√]
生成 api/apis/__init__.py             [√]
生成 api/apis/pithy_api.py            [√]
生成 api/cfg.yaml                     [√]
生成 api/db/__init__.py               [√]
生成 api/db/pithy_db.py               [√]
生成 api/README.MD                    [√]
生成 api/requirements.txt             [√]
生成 api/test_suites/__init__.py      [√]
生成 api/test_suites/test_login.py    [√]
生成 api/utils/__init__.py            [√]
生成成功,請使用編輯器打開該項目

生成項目樹:

>>> tree pithy-api-test
pithy-api-test
├── README.MD
├── apis
│   ├── __init__.py
│   └── pithy_api.py
├── cfg.yaml
├── db
│   ├── __init__.py
│   └── pithy_db.py
├── requirements.txt
├── test_suites
│   ├── __init__.py
│   └── test_login.py
└── utils
    └── __init__.py

4 directories, 10 files

調(diào)用HTTP登錄接口示例

from pithy import request

@request(url='http://httpbin.org/post', method='post')
def post(self, key1='value1'):
    """
    post method
    """
    data = {
        'key1': key1
    }
    return dict(data=data)

# 使用
response = post('test').to_json()     # 解析json字符,輸出為字典
response = post('test').json          # 解析json字符,輸出為字典
response = post('test').to_content()  # 輸出為字符串
response = post('test').content       # 輸出為字符串
response = post('test').get_cookie()  # 輸出cookie對象
response = post('test').cookie        # 輸出cookie對象

# 結(jié)果取值, 假設此處response = {'a': 1, 'b': { 'c': [1, 2, 3, 4]}}
response = post('13111111111', '123abc').json

print response.b.c   # 通過點號取值,結(jié)果為[1, 2, 3, 4]

print response('$.a') # 通過object path取值,結(jié)果為1

for i in response('$..c[@>3]'): # 通過object path取值,結(jié)果為選中c字典里大于3的元素
    print i

優(yōu)化JSON喷屋、字典使用

# 1、操作JSON的KEY
from pithy import JSONProcessor
dict_data = {'a': 1, 'b': {'a': [1, 2, 3, 4]}}
json_data = json.dumps(dict_data)
result = JSONProcessor(json_data)
print result.a     # 結(jié)果:1
print result.b.a   # 結(jié)果:[1, 2, 3, 4]

# 2瞭恰、操作字典的KEY
dict_data = {'a': 1, 'b': {'a': [1, 2, 3, 4]}}
result = JSONProcessor(dict_data)
print result.a     # 1
print result.b.a   # [1, 2, 3, 4]

# 3屯曹、object path取值
raw_dict = {
    'key1':{
        'key2':{
            'key3': [1, 2, 3, 4, 5, 6, 7, 8]
        }
    }
}

jp = JSONProcessor(raw_dict)
for i in jp('$..key3[@>3]'):
    print i

# 4、其它用法
dict_1 = {'a': 'a'}
json_1 = '{"b": "b"}'
jp = JSONProcessor(dict_1, json_1, c='c')
print(jp)

九惊畏、總結(jié)

在本文中恶耽,我們以提高腳本開發(fā)效率為前提,一步一步打造了一個簡易的測試框架陕截,但因水平所限驳棱,并未涉及測試數(shù)據(jù)初始化清理、測試中如何MOCK等話題农曲,前路依然任重而道遠,希望給大家一個啟發(fā)驻债,不足之處還望多多指點乳规,非常感謝。

加入我們合呐,暮的,,交流淌实!群冻辩。。642830685拆祈,領取最新軟件測試大廠面試資料和Python自動化恨闪、接口、框架搭建學習資料放坏!一起學習交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咙咽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子淤年,更是在濱河造成了極大的恐慌钧敞,老刑警劉巖蜡豹,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異溉苛,居然都是意外死亡镜廉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門愚战,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娇唯,“玉大人,你說我怎么就攤上這事凤巨∈永郑” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵敢茁,是天一觀的道長佑淀。 經(jīng)常有香客問我,道長彰檬,這世上最難降的妖魔是什么伸刃? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮逢倍,結(jié)果婚禮上捧颅,老公的妹妹穿的比我還像新娘。我一直安慰自己较雕,他們只是感情好碉哑,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著亮蒋,像睡著了一般扣典。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慎玖,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天贮尖,我揣著相機與錄音,去河邊找鬼趁怔。 笑死湿硝,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的润努。 我是一名探鬼主播关斜,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼任连!你這毒婦竟也來了蚤吹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裁着,沒想到半個月后繁涂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡二驰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年扔罪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桶雀。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡矿酵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矗积,到底是詐尸還是另有隱情全肮,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布棘捣,位于F島的核電站辜腺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏乍恐。R本人自食惡果不足惜评疗,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茵烈。 院中可真熱鬧百匆,春花似錦、人聲如沸呜投。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仑荐。三九已至矩动,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間释漆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工篮迎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留男图,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓甜橱,卻偏偏與公主長得像逊笆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子岂傲,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容