2019 Python接口自動化測試框架實戰開發(一)

說明:該篇博客是博主一字一碼編寫的,實屬不易,請尊重原創,謝謝大家!

目錄

一丶敘述

二丶接口基礎知識

三丶接口測試工具

四丶Fiddler的使用

五丶unittest使用

六丶mock服務入門到實戰

七丶接口自動化框架設計到開發


一丶敘述

1.項目介紹

整個項目分爲四個部分:接口基礎丶接口開發丶Unittest與接口測試結合以及接口自動化框架從設計到開發

接口基礎包括:HTTP接口 / 常見接口 / 接口工具 / 接口基礎知識

接口開發:通過Django來開發get/post接口

Unittest與接口測試結合:unittest應用 / 斷言 / requests引入 / HTMLTestRunner / case的管理

接口自動化框架從設計到開發:如何設計框架 / 封裝工具類 / 重構基類 / 錯誤調試 / 結果收集以及處理 / 解決數據依賴 / 結果統計及報告發送

項目整體思路:通過對接口數據文檔的讀寫操作,來獲取文檔中case的所有數據,然後通過requests模塊來發送請求獲取的響應數據,通過返回的響應數據中的某個標誌性字段的值來判斷是否測試成功或者失敗,最後將測試的結果數據寫入到測試文檔或者是html頁面又或者是將結果以郵件的形式發送到指定郵箱,這是整個大框架思路,要完成這一系列自動化的測試框架,則需要有一定的python代碼基礎,博主這裏只是粗略的敘述了思路,有很多地方就不細說了比如數據依賴等就請大家慢慢的閱讀吧

2.測試報告效果預覽

  • unittest和HTMLTestRunner結合生成報告(新版本的)

  •  unittest和HTMLTestRunner結合生成報告(經典版本的)

  •  測試報告郵件通知

二丶接口基礎知識

1.什麼是接口

連接前後端以及移動端,通俗來說就是前端和後端之間的橋樑,比如網站需要去調用銀行丶微信及支付寶的接口來完成業務需求

2.接口的種類

外部接口和內部接口;內部接口又分爲上層服務與下層服務以及同級服務

3.接口的分類

請求方式:post丶get丶delete丶put

4.爲什麼要做接口測試

原因:不同端的工作進度肯定是不一致的,那麼就需要對最開始開發出來的接口進行測試;對於項目來說縮短項目週期,提高開發效率以及提高系統的健壯性

5.接口測試流程

需求討論——需求評審——場景設計——用例設計——數據準備——執行

6.爲什麼要設計測試用例

  • 理清思路,避免側漏
  • 提高測試效率
  • 跟進測試進度
  • 告訴領導做過
  • 跟進重複重複性工作

7.用例設計分類

功能用例測試:測試功能是否正常丶測試功能是否按照接口文檔實現

邏輯用例設計:是否存在依賴業務,例如有些操作是需要用戶登錄成功的狀態下才能進行的操作

異常測試用例設計:參數異常以及數據異常;參數異常包括關鍵字參數丶參數爲空丶多參數丶少參數丶錯誤參數,數據異常包括關鍵字數據丶數據爲空丶長度不一致丶錯誤數據

安全測試用例設計:cookie丶header丶唯一識別碼

三丶接口測試工具

1.接口測試工具分類

  • 抓取接口工具

httpwatch:集成於IE和Firefox瀏覽器中,在其他瀏覽器中無法使用,查看數據也比較麻煩

wireshark:只要是經過電腦的所有請求都會去抓取,導致數據量比較龐大,看數據也比較麻煩

fiddler:輕量級抓包工具,功能比較全,只會記錄http請求不會像wireshark工具記錄tcp和udp等請求

  • 測試接口工具:

loadrunner:不僅僅是性能測試工具,由於該工具幾乎都是基於http請求,所以也可以用來測試接口

fiddler:它除了可以抓包還可以向接口發送各種請求

soapui:接口和自動化測試工具,功能也比較強大

jmeter:跟loadrunner一樣不僅僅是做性能測試,也可以對接口進行測試

postman:谷歌旗下的接口測試工具

四丶Fiddler的使用

1.抓取不同類型接口數據(http以及https)

  • 查看windows本機的IP

  • 配置fiddler

  • 需要保證要抓取的手機與電腦保持同一網段,博主這裏使用逍遙模擬器模擬安卓手機,修改手機網絡

  • 在高級選項中設置手動代理IP爲windows本機IP地址,端口設置與fiddler抓取端口保持一致

  • 再安卓手機中打開知乎app,抓取知乎app的http服務的數據

  • 現在的移動app都是基於https請求的,所以需要在fiddler中設置https請求

  • 然後在手機端瀏覽器中訪問windows電腦IP+port,進行網絡安全證書的下載安裝

  • 點擊下面一個下載證書

  • 然後設置密碼即可

  • 證書安裝成功後,重新打開知乎app,則成功抓取https請求的數據

  • 在知乎app中隨便對一文章進行評論,抓取該app評論接口

2.數據模擬以及過濾規則

  • 如下圖進行選擇要過濾的hosts類型,並在輸入框添加要過濾的hosts即可

  • 對知乎上的一篇文章進行回答後,獲取https://api.zhihu.com/answers接口,查看發送的post請求數據中的content字段內容也就是博主回答的內容

  • 然後進行數據模擬,也就是點擊fiddler軟件上的replay對https://api.zhihu.com/answers接口進行post請求數據的而二次發送,由於知乎這邊設定對一個問題只能進行一次回答,所以知乎服務器返回的json數據提示我們失敗,同時也說明對接口進行二次數據發送成功,只是規則邏輯失敗

3.如何模擬接口響應數據

  • 首先第一步,訪問知乎app熱榜,在fiddler軟件中獲取接口查看服務器響應的json格式數據,從服務器返回的json數據看出熱榜標題字段名爲title_area

  • 然後選擇服務器返回的數據類型爲TextView,點擊.View in Notepad即打開數據記事本,如下圖在記事本中找到title_area字段的內容,該字段內容進行了將中文轉換爲一串字符串

  • 將記事本中的title_area字段的數據修改爲this is a test for cdtaogang

  • 點擊文件——另存爲保存到桌面 

  • 回到fiddler中,左側選中熱榜接口,右側選中AutoResponder,在此窗口下點擊Add Rule將左側的接口添加進去,在右側下方導入保存在桌面的zhihu_hot.htm文件,最後點擊sava保存

  • 回到知乎app中刷新當前熱榜頁面,則成功返回修改的熱榜標題

4.使用fiddler進行評論接口測試

  • 對一篇文章進行評論,抓取評論接口,因爲get請求的接口測試太簡單,所以博主這裏選擇評論接口即POST請求方式

  • 右擊評論接口選擇copy複製接口的url地址

  • 右側選擇Composer,將複製的評論接口url粘貼到地址欄,並選擇POST請求方式

  • 因爲評論接口涉及到用戶身份驗證也就是登錄後才能進行評論的,所以需要將comments接口中request headers請求頭中的所有請求數據以及請求數據中的TextView的值進行復制

請求頭數據

請求體數據

  • 將上面複製的請求頭和請求體數據分別粘貼到如下輸入框中,點擊Execute執行發送,然後在左側則出現了另一個comments接口數據

  • 查看該comments接口,服務器返回的響應數據中與第一個comments接口一致,說明接口測試成功

五丶unittest使用

1.unittest簡單使用

  • 在IDE中使用python的環境隨便創建個py文件,需要注意的是該py文件的名字不能是test.py,否在運行時會出錯,unittest包是python自帶的不需要下載安裝,代碼如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/17 13:10'

import unittest


class TestMethod(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("Method before class execution")

    @classmethod
    def tearDownClass(cls):
        print("Method after class execution")

    def setUp(self):
        print("------setUp------")

    def tearDown(self):
        print("------tearDown------")

    def test_01(self):
        print("First test method")

    def test_02(self):
        print("The second test method")


if __name__ == '__main__':
    unittest.main()
  • 直接run運行以上代碼

2.unittest和request重構封裝

說明:使用requests模塊對接口url地址發送請求,通過unittest測試框架進行case測試

  • 首先博主在逍遙安卓模擬器中下載了一個看書app,通過fiddler對app上的某一接口進行獲取,之所以選擇對此app進行接口測試,是因爲該app的所有接口全是POST請求

  • 在PyCharm下新建工程目錄,目錄下創建base包,在包下創建一個demo.py文件以及test_method.py文件,用於使用unittest框架來測試以上app接口

  • 在demo.py文件中,使用requests get以及post方法進行了封裝,主要是根據傳遞的參數method來對get以及post方法進行分別調用而已,具體實現如下
import requests


class RunMain:
	def send_get(self,url,data):
		res = requests.get(url=url,data=data).json()
		return res
		
	def send_post(self,url,data):
		res = requests.post(url=url,data=data).json()
		return res

	def run_main(self,url,method,data=None):
		res = None
		if method == 'GET':
			res = self.send_get(url,data)
		else:
			res = self.send_post(url,data)
		return res
  • 在test_method.py文件中則創建測試類以及test方法,在test方法中調用demo.py中的run_main方法,即使用requests模塊向傳遞的接口url地址和請求方式以及請求體發送對應的請求,這裏使用setUp方法則是利用其優先調用而對RunMain類進行實例化
import unittest
import json
import HtmlTestRunner
from .demo import RunMain


class TestMethod(unittest.TestCase):
    def setUp(self):
        self.run = RunMain()

    def test_01(self):
        url = 'http://api.ishugui.com/asg/portal/call/265.do'
        data = {
            "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",
            "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",
            "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",
            "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",
            "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"
        }
        res1 = self.run.run_main(url, "POST", json.dumps(data))
        print(res1)


    def test_02(self):
        url = 'http://api.ishugui.com/asg/portal/call/265.do'
        data = {

        }
        res2 = self.run.run_main(url, 'POST', data)

        print(res2)


if __name__ == '__main__':
    unittest.main()
  • 運行test_method模塊,查看測試接口,test_02則是錯誤測試

3.unittest中assert的使用

  • 首先根據返回的結果字典dict數據中的status狀態值來判斷測試是否通過或者失敗,邏輯很基礎就不細說了
class TestMethod(unittest.TestCase):
    def setUp(self):
        self.run = RunMain()

    def test_01(self):
        url = 'http://api.ishugui.com/asg/portal/call/265.do'
        data = {
            "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",
            "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",
            "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",
            "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",
            "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"
        }
        res1 = self.run.run_main(url, "POST", json.dumps(data))
        # print(type(res1))
        # print(res1['pub'])
        # print(type(res1['pub']))
        if res1['pub']['status'] == 0:
            print("測試通過")
        else:
            print("測試失敗")
        print(res1)


    def test_02(self):
        url = 'http://api.ishugui.com/asg/portal/call/265.do'
        data = {

        }
        res2 = self.run.run_main(url, 'POST', data)
        if res2['pub']['status'] == 0:
            print("測試通過")
        else:
            print("測試失敗")
        print(res2)


if __name__ == '__main__':
    unittest.main()
  • 運行以上代碼,查看結果與預期一樣

  • 將if判斷代碼更換成unittest模塊中的assert斷言進行判斷,這裏使用assertEqual方法來判斷兩個值是否相等,當兩個值相等則返回OK,當不相同時返回assertEqual方法msg變量自定義的值
class TestMethod(unittest.TestCase):
    def setUp(self):
        self.run = RunMain()

    def test_01(self):
        url = 'http://api.ishugui.com/asg/portal/call/265.do'
        data = {
            "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",
            "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",
            "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",
            "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",
            "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"
        }
        res1 = self.run.run_main(url, "POST", json.dumps(data))
        # print(type(res1))
        # print(res1['pub'])
        # print(type(res1['pub']))
        # if res1['pub']['status'] == 0:
        #     print("測試通過")
        # else:
        #     print("測試失敗")
        self.assertEqual(res1['pub']['status'], 0, "測試失敗")
        print(res1)


    def test_02(self):
        url = 'http://api.ishugui.com/asg/portal/call/265.do'
        data = {

        }
        res2 = self.run.run_main(url, 'POST', data)
        # if res2['pub']['status'] == 0:
        #     print("測試通過")
        # else:
        #     print("測試失敗")
        self.assertEqual(res2['pub']['status'], 0, "測試失敗")
        print(res2)


if __name__ == '__main__':
    unittest.main()
  • 測試查看結果,斷言失敗,測試結果如下很清晰

4.unittest中case的管理及運用

  • 在測試一些接口時,有些接口的返回數據需要在下一個接口進行使用,所以需要定義全局變量,方便每個case都能夠得着,當在test_01中定義全局變量userid,然後在test_02中進行打印

  • 在unittest中,是按照字母數字來進行case先後執行順序的,將test_01改爲test_03後,運行代碼後,會提示test_02中的userid未定義,原因是程序先去執行了test_02這個case,所以出現該提示是正常的

  • 當在測試代碼中有很多case時,我想跳過某個case,則在該case方法上定義unittest的skip方法裝飾器,並需要傳遞此方法名作爲實參進行傳遞

  • 除了在if __name__ == '__main__'中使用unittest.main方法執行所有的case以外,還可以將要測試的case添加到unittest.TestSuite集合中執行想要執行的case,若想要全部都執行則需要一個一個的添加

5.unittest和HTMLTestRunner結合生成報告(博主這裏給大家展現兩種)

第一種:比較新版本的htmltestrunner報告

  • 然後將下載好的whl文件放在你的項目環境的Scripts目錄下

  • 最後在Terminal終端或者cmd終端中進入以上目錄,執行如下命令即可

  • 安裝成功後,即在以下路徑中可以找到安裝的HTMLTestRunner的包了

  • 在if __name__ == '__main__'中只需要調用HtmlTestRunner模塊中的HtmlTestRunner類,向該類傳遞報告標題參數值即可,其他均默認,需要注意的時啓動文件run爲當前的py文件,如果是Unittests開頭的啓動文件,則不會運行if __name__ == '__main__'下的代碼,只會執行unittest框架的setUp以及test開頭的case代碼

  • 運行test_method.py文件,成功在base目錄下創建reports目錄,並在該目錄下生成對應時間的測試報告

  • 打開reports目錄下生成的html測試報告,查看測試內容,與預期設定一樣,test_02失敗test_03成功,說明一下報告中的亂碼爲中文

第二種:比較經典版本的htmltestrunner報告

  • 爲了方便演示效果,博主在testItems項目目錄下,創建base2的模塊,將base模塊下的demo.py和test_method.py文件拷貝到base2目錄下並將test_method.py命令爲test_method2.py免得搞混淆,然後在base2目錄下新建HTMLTestRunner.py文件用於存放其源碼,目錄結構如下

第94行, 將import StringIO修改成import io
第539行,將self.outputBuffer = StringIO.StringIO()修改成self.outputBuffer = io.StringIO()
第642行,將if not rmap.has_key(cls):修改成if not cls in rmap:
第631行,將print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改成print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
第766行,將uo = o.decode('latin-1')修改成uo = e
第775行,將ue = e.decode('latin-1')修改成ue = e
  • 在test_method2模塊中首先需要從base2模塊中去導入HTMLTestRunner文件,然後if __name__ == '__main__'中,需要創建一個文件源,同樣是調用HTMLTestRunner模塊中的HTMLTestRunner類,不同的是需要將創建的文件源傳遞給實例屬性stream變量

  • 運行test_method2.py,成功在上一級report目錄下生成html_report.html報告文件

  • 打開html_report.html測試報告,測試結果與代碼設定一致

六丶mock服務入門到實戰

1.mock簡介

mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法,mock是在測試過程中,對於一些不容易構造/獲取的對象,創建一個mock對象來模擬對象的行爲即就是模擬fiddler返回接口響應數據的一個過程。

2.mock安裝

  • 在終端使用pip進行安裝即可

3.在case中通過底層函數實現mock

  • 在test_method模塊中導入mock,然後在test_03函數中通過以下代碼設置返回的return_value的值爲請求的data數據
mock_data = mock.Mock(return_value=data)
print(mock_data)
  • run運行Unittests in test_method.py,打印出Mock id的值

  • 將調用run_main方法的值設定爲mock_data,即print(res1)則表示打印請求的data數據的值,因爲res1的數據不再是接口返回的響應數據,則arrest斷言是會提示報錯的,這是正常的

4.重構封裝mock服務

  • 在base目錄下創建mock_demo.py文件,構造一個mock_test方法,該方法就是將test_03方法中self.run.run_main = mock.Mock(return_value=data) 和 res1 = self.run.run_main(url, "POST", json.dumps(data))方法的調用進行了封裝成爲test_02和test_03方法通用的一個方法,上一步驟中的代碼mock_data = mock.Mock(return_value=data) 和self.run.run_main = mock_data,即就相當於self.run.run_main = mock.Mock(return_value=data)而已,都是python基本的調用封裝基礎知識,mock_demo.py中的代碼如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/20 16:26'
from mock import mock
import json

def mock_test(mock_method, request_data, url, method, response_data):
    """
    :param mock_method:
    :param request_data:
    :param url:
    :param method:
    :param response_data:
    :return: res
    """
    mock_method = mock.Mock(return_value=response_data)
    print('mock_method:', mock_method)
    res = mock_method(url, method, json.dumps(request_data))
    return res
  • 那麼在test_03方法中,如下進行調用即可
res1 = mock_test(self.run.run_main, data, url, 'POST', 'ssssssss')
print('res1:', res1)
  • 運行Unittests in test_method.py,查看運行結果和博主設定一樣成功返回自定義的response_data數據

七丶接口自動化框架設計到開發

1.如何設計一個接口自動化測試框架

根據接口地址丶接口類型丶請求數據丶預期結果來進行設計,對於需要登錄後才能進行操作的接口那麼則需要進行header cookie等數據的傳遞,自動化測試的難點就是數據依賴。

2.python操作excel獲得內容

  • 首先python操作excel,需要安裝兩個包,分別是xlrd和xlwt這兩個庫,xlrd這個庫是負責讀取excel數據的,而xlwt庫是負責向excel寫入數據的

  • 在項目目錄下創建utils工具包,在該包下創建op_excel.py文件,在該文件中通過導入xlrd包,對excel表的數據進行讀取操作

3.重構操作excel函數

  • 根據上一步驟讀取excel表的內容代碼後,進行了一個簡單的封裝,提高代碼的通用性,過程相當的簡單,實現代碼如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/20 17:33'
import xlrd

data = xlrd.open_workbook("../test_data/rs.xls")
tables = data.sheets()[0]  # 獲取表格數據對象
print(tables.nrows) # 打印表格行數
print(tables.cell_value(0,0))  # 打印excel表格數據,需要傳遞數據所在的座標(x,y)
print(tables.cell_value(0,1))
print("*"*50+"封裝前後數據對比"+"*"*50)


class operationExcel(object):
    def __init__(self, file_path="../test_data/rs.xls", sheet_id=0):
        self.file_path = file_path
        self.sheet_id = sheet_id
        self.data = self.get_data()

    def get_data(self):
        data = xlrd.open_workbook(self.file_path)
        tables = data.sheets()[self.sheet_id]
        return tables

    def get_rows(self):
        """獲取單元格的排數"""
        return self.data.nrows

    def get_cell_value(self, x=0, y=0):
        """獲取某個單元格的數據"""
        return self.data.cell_value(x, y)


if __name__ == '__main__':
    print(operationExcel().get_rows())
    print(operationExcel().get_cell_value())
    print(operationExcel().get_cell_value(0,1))
  • 運行op_excel.py文件後,結果與封裝之前代碼結果一致,表示重構封裝代碼成功

4.學習操作json文件

  • 自定義一個登錄的json文件名爲login.json,文件內容如下,存放在test_data目錄下

  • 在utils工具包下創建op_json.py文件,在文件中對login.json文件內容進行讀取操作,代碼如下

5.重構json工具類

  • 將上一步操作json的代碼進行封裝
class operationJson(object):
    def __init__(self, file_path="../test_data/login.json"):
        self.file_path = file_path
        self.data = self.get_data()

    def get_data(self):
        with open(self.file_path) as f:
            data = json.load(f)
            return data

    def get_key_words(self, key=None):
        if key:
            return self.data[key]
        else:
            return self.data


if __name__ == '__main__':
    print(operationJson().get_key_words())
    print(operationJson().get_key_words("login"))
    print(operationJson().get_key_words("login")['username'])
  • 運行op_json.py文件,結果與封裝之前代碼結果一致,表示重構封裝代碼成功

6.封裝獲取常量方法

  • 首先打開excel表格,查看需要獲取的字段有哪些

  • 對excel表的字段進行獲取,在項目目錄下創建名爲data的python包,在該包下創建data_conf.py,代碼就是簡單的獲取對應的變量值,具體如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/21 9:29'


class global_var:
    id = '0'  # id
    module = '1'  # 模塊
    url = '2'  # url
    run = '3'  # 是否運行
    request_type = '4'  # 請求類型
    request_header = '5'  # 是否攜帶header
    case_depend = '6'  # case依賴
    response_data_depend = '7'  # 依賴的返回數據
    data_depend = '8'  #  數據依賴
    request_data = '9'  # 請求數據
    expect_result = '10'  # 預期結果
    reality_result = '11'  # 實際結果


def get_id():
    return global_var.id

def get_module():
    return global_var.module

def get_url():
    return global_var.url

def get_run():
    return global_var.run

def get_request_type():
    return global_var.request_type

def get_request_header():
    return global_var.request_header

def get_case_depend():
    return global_var.case_depend

def get_response_data_depend():
    return global_var.response_data_depend

def get_data_depend():
    return global_var.data_depend

def get_request_data():
    return global_var.request_data

def get_expect_result():
    return global_var.expect_result

def get_reality_result():
    return global_var.reality_result

7.封裝獲取接口數據

  • 在data目錄下創建data_get.py文件,在該文件中對excel表數據以及json數據結合上一步封裝的常量方法整合後的實現,代碼如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/21 10:01'
from utils.op_excel import operationExcel
from utils.op_json import operationJson
from data import data_conf

class getData(object):
    def __init__(self):
        self.op_excel = operationExcel()

    def get_case_lines(self):
        """獲取表格行數"""
        return self.op_excel.get_rows()

    def get_is_run(self, x):
        """獲取case是否運行"""
        flag = None
        y = data_conf.get_run()
        run_value = self.op_excel.get_cell_value(x, y)
        if run_value == 'yes':
            flag = True
        else:
            flag = False
        return flag

    def get_is_header(self, x):
        """是否攜帶header"""
        y = data_conf.get_request_header()
        header = self.op_excel.get_cell_value(x, y)
        if header == 'yes':
            return data_conf.get_header_value()
        else:
            return None

    def get_request_method(self, x):
        """獲取請求方式"""
        y = data_conf.get_request_type()
        request_method = self.op_excel.get_cell_value(x, y)
        return request_method

    def get_request_url(self, x):
        """獲取請求地址"""
        y = data_conf.get_url()
        request_url = self.op_excel.get_cell_value(x, y)
        return request_url

    def get_request_data(self, x):
        """獲取請求數據"""
        y = data_conf.get_request_data()
        request_data = self.op_excel.get_cell_value(x, y)
        if request_data == '':
            return None
        return request_data

    def get_data_for_json(self, x):
        """通過excel中的關鍵字去獲取json數據"""
        op_json = operationJson()
        data = op_json.get_key_words(self.get_request_data(x))
        return data

    def get_expect_data(self, x):
        """獲取預期結果數據"""
        y = data_conf.get_expect_result()
        expect_data = self.op_excel.get_cell_value(x, y)
        if expect_data == '':
            return None
        return expect_data

8.post、get基類的封裝

  • 在base包下創建run_method.py文件,在文件中重新編寫對get丶post請求方式的代碼封裝,具體如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/21 11:19'
import requests


class RunMain(object):

    def get_main(self, url, data=None, header=None):
        res = None
        if header is not None:
            res = requests.get(url=url, data=data, headers=header).json()
        else:
            res = requests.get(url=url, data=data).json()
        return res

    def post_main(self, url, data, header=None):
        res = None
        if header is not None:
            res = requests.post(url=url, data=data, headers=header).json()
        else:
            res = requests.post(url=url, data=data).json()
        return res


    def run_main(self, url, method, data=None, header=None):
        res = None
        if method.lower() == 'post':
            res = self.post_main(url, data, header)
        elif method.lower() == 'get':
            res = self.get_main(url, data, header)
        else:
            return "what ?????"
        return res

9.主流程封裝及錯誤解決調試

  • 首先在testItems項目目錄下新建一個名爲main的python包,在該包下創建名爲run_test的py文件,該文件爲主程序啓動文件,代碼的邏輯就是將前面封裝的方法進行了調用核心就是讀取excel表的數據,通過讀取到的數據,發送請求,其中包括某一些變量的判斷,根據該判斷然後到json數據中獲取請求的數據,最後就這麼的簡單,代碼如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/21 11:57'
from base.run_method import RunMain
from data.data_get import getData


class RunTest(object):
    def __init__(self):
        self.runmain = RunMain()
        self.data = getData()

    def run(self):
        res = None
        row_counts = self.data.get_case_lines()  # 獲取excel表格行數
        # print(row_counts) 5
        for row_count in range(1, row_counts):
            # print(row_count) 1,2,3,4
            url = self.data.get_request_url(row_count)  # y行不變遍歷獲取x列的請求地址
            method = self.data.get_request_method(row_count)  # y行不變遍歷獲取x列的請求方式
            is_run = self.data.get_is_run(row_count)  # y行不變遍歷獲取x列的是否運行
            data = self.data.get_data_for_json(row_count)  # y行不變遍歷獲取x列的請求數據,這裏面時三次調用,依次分別是get_data_for_json丶get_key_words丶get_request_data
            header = self.data.get_is_header(row_count)
            print('url:', url)
            print('method:', method)
            print('is_run:', is_run)
            print('data:', data)
            print('header:', header)

            if is_run:
                res = self.runmain.run_main(url,method,data,header)
                print("*"*60+"分割線"+"*"*60)
        return res


if __name__ == '__main__':
    print('res:', RunTest().run())
  • 運行run_test,成功的將excel以及json數據正確打印出來,返回res服務器返回結果,需要說明的是excel表中的所有數據都不是真實存在的,包括json文檔數據也是,這裏主要是測試整個框架的正確性讀取excel以及json文檔數據,並正確的發送請求獲得相應數據

  • 運行結果出現紅色的內容,是由requests模塊發送請求的安全請求警告,如不想顯示此警告,可以在run_method.py發送請求核心代碼進行禁用,禁用代碼如下

  • 重新運行run_test,安全請求警告不再顯示

  • 根據代碼運行結果,對比excel表以及json數據文檔內容,數據正確無誤

10. 返回數據格式處理以及調錯

  • 爲了測試返回的接口的響應數據,博主這裏在excel文檔以及json文檔中添加了一條數據

  • 因爲在excel文檔中小說的接口不攜帶header所以在向接口發送請求數據核心代碼塊,進行了如下修改,因爲在excel文檔中的最後一個接口時真實的,所以只需要對最後一個接口url返回的字典類型的響應數據進行轉換成json格式的數據,並按照關鍵字進行排序

  • 運行run_test,在最後一個接口中成功打印出我們想要的數據

11.獲取接口返回狀態

  • 在發送請求數據核心代碼中,進行打印返回的狀態碼status_code即可,最後一個接口比較特殊,返回的響應數據中沒有status_code,所以需要對返回的json數據中的status進行判斷,並向其返回數據中添加我們所要的status_code的值

  • 運行代碼,當返回的狀態碼爲404表示接口不存在,只要是存在響應數據,則status_code爲200,必須說明一點的就是status_code爲200不一定表示接口存在,有些大型網站會對其域名下不存在的接口返回200的錯誤頁面顯示,所以在測試文檔中會體現預期結果和實際結果兩項數據需要一致才能表示測試通過

12.通過預期結果判斷case是否執行成功

  • 進行接下來的測試,博主這裏重新準備了另一個excel表來進行測試,需要對json文件中的數據進行添加,在excel表Book-05中的請求數據book5關鍵字對應的json文件的數據故意爲空,可以對測試結果有一個對比

  • 在數據獲取核心類中定義了一個方法來獲取excel表模塊字段的數據

  • 回過頭在啓動文件中獲取模塊名預期結果並進行打印

  • 運行啓動文件,查看運行結果

  • 在utils目錄下,創建common_util.py文件,在該文件代碼中通過啓動文件傳遞過來的數據來判斷excel表預期結果數據的status狀態碼與res結果中的status狀態碼是否一致,一致表示測試通過,不一致則失敗,代碼如下
# -*- coding: utf-8 -*-
__author__ = 'cdtaogang'
__date__ = '2019/6/21 18:43'
import json

class CommonUtil(object):
    def is_contains(self, expect, reality):
        flag = None
        reality = json.loads(reality)
        expect = json.loads(expect)
        if expect['status'] == reality['pub']['status']:
            flag = True
        else:
            flag = False
        return flag
  • 在啓動文件中需要註釋掉res響應數據,利於查看測試結果,還需要在啓動文件調用is_contains方法來根據其返回值判斷測試是否通過

  • 運行啓動文件,查看測試結果

13.將測試結果寫入到excel中

  • 首先在op_excel.py中定義一個方法,該方法實現讀取excel的數據,並進行copy複製,然後再write方法將數據寫入到座標位置

  • 然後在data_get.py中需要定義一個方法,在該方法中核心邏輯爲獲取y座標的值

  • 最後在啓動文件中,調用data_get模塊中的write_reality_data方法,並將剩餘的x座標的值以及data數據傳遞給最終的核心方法write_reality_result_data來完成對excel表中的實際結果數據的寫入

  • 將excel表進行關閉後,運行啓動文件,再次打開excel表,實際結果數據寫入正確,之所以需要關閉excel是避免提示提示錯誤,無法寫入保存數據

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章