分類:我的測試食譜

壓測工具的新選擇!! Locust load test framework

壓測工具的新選擇!! Locust load test framework

繼上一篇,自建nginx分流後(傳送門),接著的問題就是要如何驗證分流效果與實際上這樣架構的負載吞吐量到底能到多少了。傳統想到壓測,我們就會想到jmeter,但是jmeter的參數配置繁多,而且若要做到可程式化的話其路可能相當曲折;再者,若在我們實體主機上測試的話,極有可能受限於其物理極限,包含頻寬與網卡的能力,因此我搜尋了一下python有沒有load test framework,又可以同時支援分散式的load test

 

結果發現了這套名叫:locust的套件!!其安裝方式相當的簡單:

首先,整份框架都是python寫的,透過pip就可以安裝,因此先來試著打包成映像檔,變成隨時可以使用的容器吧:

測試Script寫起來相當簡潔,我們直接存成locustfile.py,實作HttpLocust、TaskSet後,屆時會在locust的壓測ui介面,讀取到接口

from locust import HttpLocust, TaskSet, task


class WebTest(TaskSet):

    @task
    def get_uuid(self):
        self.client.get("/webtest?action=get_uuid")


class WebsiteTest(HttpLocust):
    task_set = WebTest

Dockerfile的內容:

FROM python:3.6-alpine

COPY docker-entrypoint.sh /

RUN    apk --no-cache add --virtual=.build-dep build-base \
    && apk --no-cache add libzmq \
    && pip install --no-cache-dir locustio==0.8.1 \
    && apk del .build-dep \
    && chmod +x /docker-entrypoint.sh

RUN  mkdir /locust
WORKDIR /locust
EXPOSE 8089 5557 5558

ENTRYPOINT ["/docker-entrypoint.sh"]

docker-entrypoint.sh的內容:

#!/bin/sh
set -e
LOCUST_MODE=${LOCUST_MODE:-standalone}
LOCUST_MASTER_BIND_PORT=${LOCUST_MASTER_BIND_PORT:-5557}
LOCUST_FILE=${LOCUST_FILE:-locustfile.py}

if [ -z ${ATTACKED_HOST+x} ] ; then
    echo "You need to set the URL of the host to be tested (ATTACKED_HOST)."
    exit 1
fi

LOCUST_OPTS="-f ${LOCUST_FILE} --host=${ATTACKED_HOST} --no-reset-stats $LOCUST_OPTS"

case `echo ${LOCUST_MODE} | tr 'a-z' 'A-Z'` in
"MASTER")
    LOCUST_OPTS="--master --master-bind-port=${LOCUST_MASTER_BIND_PORT} $LOCUST_OPTS"
    ;;

"SLAVE")
    LOCUST_OPTS="--slave --master-host=${LOCUST_MASTER} --master-port=${LOCUST_MASTER_BIND_PORT} $LOCUST_OPTS"
    if [ -z ${LOCUST_MASTER+x} ] ; then
        echo "You need to set LOCUST_MASTER."
        exit 1
    fi
    ;;
esac

cd /locust
locust ${LOCUST_OPTS}

單一台load test測試端 啟動指令 (驗證ok):

sudo docker run --name locust-master --hostname locust-master \
--network="webtest" \
-p 8089:8089 -p 5557:5557 -p 5558:5558 \
-v /home/paul/webtest/locust:/locust \
-e ATTACKED_HOST='http://web-test-nginx:10000' \
grubykarol/locust

分散式的測試端配置(待驗證):

啟動指令-master測試端:

docker run --name master --hostname master `
 -p 8089:8089 -p 5557:5557 -p 5558:5558 `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST='http://master:8089' `
 -e LOCUST_MODE=master `
 --rm -d grubykarol/locust

啟動指令-slave測試端:

docker run --name slave0 `
 --link master --env NO_PROXY=master `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST=http://master:8089 `
 -e LOCUST_MODE=slave `
 -e LOCUST_MASTER=master `
 --rm -d grubykarol/locust
docker run --name slave1 `
 --link master --env NO_PROXY=master `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST=http://master:8089 `
 -e LOCUST_MODE=slave `
 -e LOCUST_MASTER=master `
 --rm -d grubykarol/locust

 

容器執行後,打入http://xxx.xxx.xxx.xxx:8090,就可以連到對應的監控網址(這個是最棒的),一開始就會問你模擬的u數,以及你希望多久時間內要衝到該u數

摘要總表:

即時圖表:

錯誤分析:

 

同時,我去驗證了我們mongodb的log,是符合他load test的請求數量

 

以上是這次接觸到新的壓測工具實作的小小記錄,也推薦給大伙

 

參考:

https://locust.io/

https://docs.locust.io/en/stable/

https://medium.com/locust-io-experiments/locust-io-experiments-running-in-docker-cae3c7f9386e

 

 

 

Testing and Deployment 概念圖

Testing and Deployment 概念圖

測試與佈署-自動化

jenkins是另一個主要課題,另一部分,就是環境配置檔的管理…

先前公司的做法是有一個環境配置資料庫,可以讓人員透過介面調整key/value的參數值

當jenkins deploy的時候,會自動replace掉程式中固定的參數(聽說好像是全面性,連程式敘述都會被置換掉)。

這一塊也還沒有想法,目前應該只有在跟git整合上而己,先讓程式可以boxing到docker container就好

若有緣人看到這個架構認為有什麼疑惑或是想法的話,歡迎來噹

Unittest Python – My First Mock Lesson!!How to Mock 3rd party Component!!

Unittest Python – My First Mock Lesson!!How to Mock 3rd party Component!!

過往,我們在寫程式中,常常會去呼叫到參考的函式庫,或是請求外部的服務

這導致在單元測試中,我們會因為不具備其環境或是資源,而陷入難以測試的場景

若以整個系統來看,模組之間也可以說是相對的是一個”單元”吧,最常相依的就是資料!

這種情況,通常我們有的時間的話,可以大費周章的建立數據庫、測試檔案與環境,測試前後準備資料,刪除資料流程。

不過這往往是整測時才會遇到的場景,這邊先不討論。

 

我希望的單元測試是集中著重在自已這端的程式運作是否符合預期!!

管他外部環境怎樣,網路有沒有通、世界是否毀滅都與我暫時無關

mock的技巧就是來拯救我們上述說到的難題與困境,mock是用來模擬相依的外部服務或物件的物件。

也就是我們透過mock來將所有程式外部的相依性解除吧!!這麼說很玄

在python的unittest的函式庫中已有包含

 

這邊直接演示(記錄)程式使用的方式,這個例子有點簡單,但是幾乎完全相依!!

from pywebhdfs.webhdfs import PyWebHdfsClient

class WebHDFSHelper:
    __hdfs_client = None

    def __init__(self, host='', port='', user=''):
        self.__hdfs_client = PyWebHdfsClient(host=host, port=port,
                                             user_name=user)

    def create_dir(self, full_dir_path):
        try:
            #before - do something
            result = self.__hdfs_client.make_dir(full_dir_path)
            #after - do something
        except BaseException:
            #exception - do something
            raise
        return result
	
def test_create_dir_temp(self):
    helper = WebHDFSHelper(host="192.168.65.130", port=50070, user="root")
    result = helper.create_dir( "exchange_test" )
    self.assertEqual(result, True)
    pass

當我們要測試建立目錄這個行為時,我們往往會遇到困難就是我必須去真實連到一個hdfs的環境!!

通常可能開發者本機都會有,或者確保團隊有一些共同的測試環境

但這種相依性會導致,若程式移轉到其他人手上或是環境一變動

測試就會無法運作!

例如:ip變了!

 

這次我們來透過mock的技巧來演示如何將如何模擬這些hdfs服務

@mock.patch('pywebhdfs.webhdfs.PyWebHdfsClient.__init__')
@mock.patch('pywebhdfs.webhdfs.PyWebHdfsClient.make_dir')
def test_create_dir(self, mock_hdfs_client ,mock_init_service):
    mock_init_service.return_value = None
    helper = WebHDFSHelper(host="localhost", port=50070, user="root")
    mock_init_service.assert_called_with(host="localhost", port=50070, user_name="root")


    helper.create_dir( DATA_TEMP_PATH )
    mock_hdfs_client.assert_called_with(DATA_TEMP_PATH)
    pass

首先,我們要先模擬的就是

第一塊WebHDFSHelper下面的初始函式會去依照參數連線到外部的hdfs服務

self.__hdfs_client = PyWebHdfsClient(host=host, port=port,user_name=user)

第二塊是呼叫create_dir的時候的這一行會去呼叫參考物件的make_dir的方法,這個會相依上面那塊初始化後的物件。

result = self.__hdfs_client.make_dir(full_dir_path)

 

所以我們知道這兩塊是我們能獨立單元測試的排除目標

因此我們對應的宣告

@mock.patch(‘pywebhdfs.webhdfs.PyWebHdfsClient.__init__’)
@mock.patch(‘pywebhdfs.webhdfs.PyWebHdfsClient.make_dir’)

並對我們的test_測試函式,注入2組參數

def test_create_dir(self, mock_hdfs_client ,mock_init_service):

前者是我們到時要呼叫make_dir的方法,後者是我們初始化時要mock的行為。

註:這邊mock的patch宣告所對test函式注入的順序為何是倒序,我則不知道

 

接著依我們的場景,mock物件可以做幾件事

1.指定回傳物件,因為是模擬,就可以一併模擬回傳的結果

mock_init_service.return_value = None #若要模擬初始函式,就是回傳值一定是none

2.透過mock物件來確認是否有被呼叫到,連同參數一併帶入,注意參數名稱必須符合原始模擬物件的參數名稱)

mock_init_service.assert_called_with(host=”localhost”, port=50070, user_name=”root”)

 

因此測試如下:

class HDFSHelperTest(unittest.TestCase): 
    @mock.patch('pywebhdfs.webhdfs.PyWebHdfsClient.__init__') 
    @mock.patch('pywebhdfs.webhdfs.PyWebHdfsClient.make_dir') 
    def test_create_dir(self, mock_hdfs_client ,mock_init_service): 
        mock_init_service.return_value = None 
        helper = WebHDFSHelper(host="localhost", port=50070, user="root") 
        mock_init_service.assert_called_with(host="localhost", port=50070, user_name="root") 
        helper.create_dir( DATA_TEMP_PATH ) 
        mock_hdfs_client.assert_called_with(DATA_TEMP_PATH)

我們設個DEBUG中斷點看看,在初始化後,我們的內部變數已經被轉換成一個含有magicmock行為make_dir的物件。

因為初始化這邊沒有其他需要驗證的流程,因此我只去assert是否有照著我給的參數呼叫,這時管他這個環境是不是活著,我只需要知道”若”我給活的環境 就會正常。 若需要驗證例外行為,以這個例子,可以加入更多的例外處理。 這時一樣可以透過mock的return_value raise exception,這是做的到的,然而驗證的東西還是要自行定義,在一般函式環境的話,你可以因為例外給定預設行為,這樣也是有助於提高程式測試的覆蓋率。 例如:

mock_init_service.return_value = None
mock_init_service.side_effect = Exception("環境異常")
helper = WebHDFSHelper( host="localhost", port=50070, user="root" )
mock_init_service.assert_called_with( host="localhost", port=50070, user_name="root" )

如願拋出異常 最後,我們實際測試create_dir吧,第1段,我將他設定為mock_hdfs_client.return_value = True,第2段設定為False

確實會如願的走向1次成功、1次失敗,當然我這邊程式只為了驗證是否如配置去跑,所以只有這樣寫,一般成功、失敗處理上的邏輯差別還是會依原本物件設計的流程來運作 因此要「驗證」的東西與結果,仍然可以依自己的需求去調整與配置。 以上就是python的mock初體驗,以自己的例子還是比較清楚mock技巧的使用場景與方式…有錯的部分請不吝指教,感謝

 

測試程式完整版

from pywebhdfs.webhdfs import PyWebHdfsClient
class WebHDFSHelper:
    def __init__(self, data_path='', host='', port='', user=''):
        try:
            self.__hdfs_client = PyWebHdfsClient(host=host, port=port,
                                                 user_name=user)
        except Exception as err:
            print(str(err))
    
        def create_dir(self, full_dir_path):
            try:
                result = self.__hdfs_client.make_dir(full_dir_path)
                if result:
                    print("成功")
                else:
                    print("失敗")
            except:
                raise
            return result


class HDFSHelperTest(unittest.TestCase):
    @mock.patch('pywebhdfs.webhdfs.PyWebHdfsClient.__init__')
    @mock.patch('pywebhdfs.webhdfs.PyWebHdfsClient.make_dir')
    def test_create_dir(self, mock_hdfs_client, mock_init_service):
        mock_init_service.return_value = None

        mock_init_service.side_effect = Exception("環境異常")
        helper = WebHDFSHelper(host="localhost", port=50070, user="root")
        mock_init_service.assert_called_with(
            host="localhost", port=50070, user_name="root")

        mock_init_service.return_value = None
        mock_init_service.side_effect = None
        helper = WebHDFSHelper(host="localhost", port=50070, user="root")
        mock_init_service.assert_called_with(
            host="localhost", port=50070, user_name="root")
    
        mock_hdfs_client.return_value = True
        helper.create_dir(DATA_TEMP_PATH)
        mock_hdfs_client.assert_called_with(DATA_TEMP_PATH)
    
        mock_hdfs_client.return_value = False
        helper.create_dir(DATA_TEMP_PATH)
        mock_hdfs_client.assert_called_with(DATA_TEMP_PATH)
    pass
WP Facebook Auto Publish Powered By : XYZScripts.com