專欄文章 質量保障系統的落地實踐 (三) CI 管理設計 - 整合設計

亦攸發表於2024-05-24

往期文章

專欄文章 質量保障系統的落地實踐 (一) 概覽篇
專欄文章 質量保障系統的落地實踐 (二) 專案管理設計 - 基礎資訊與缺陷資訊設計
專欄文章 質量保障系統的落地實踐 (二) 專案管理設計 - 程式碼資訊設計
專欄文章 質量保障系統的落地實踐 (三) CI 管理設計 - 基礎設計

前言

上期文章中介紹了 CI 管理的基礎設計,概要包括如何定義校驗粒度,以及如何提取校驗粒度的方法,那麼在這篇文章開頭,補充一些這種方案的好處:1、首先,文字解析的方式需要自動化指令碼有一定的編寫規範,而編寫規範又常常是較難推進的,一個團隊內多名成員,如何保證每次的程式碼提交編寫規範都能滿足期望?可以使用 code review、程式碼規範工具等方式被動推進編寫規範,而使用這種方案最直接的好處是,要想自己的資料被完整統計,就需要遵循定義的規範,這是一個主動行為,不是被動的,後置的行為;2、其次,這種方案的適配性很高,與自動化框架不產生耦合,適配任意自動化框架編寫的指令碼;3、若是使用執行結果進行校驗粒度的提取,碰上判斷分支的情況,往往會出現統計資料偏少的情況,而文字解析則可避免這些問題;4、編寫程式碼簡單直接,容易上手。
那麼劣勢在哪呢?
最大的劣勢在於變數的替換結果無法讀取,如 sql 語句:select * from xxx.table where id="${recordId}"這類的動態程式碼,由於文字解析無法侵入執行過程,所以這種方式拿不到具體填充的 recordId。
補充完上述情況後,我們開始後續的設計與討論

指令碼執行

在討論如何整合之前,我們需要有一個基本概念—任何程式碼編寫的自動化框架,均有一個執行入口。而當我們使用程式碼編譯器的時候,往往點選執行按鈕即可執行編寫的程式碼,這個原理是什麼?
那我們舉一個 python 的例子

def print_(name):
  print(f"name is {name}.")

print_("亦攸")

想要執行這個檔案,可以使用編譯器的執行按鈕,如 PYCHARM,而另一種更底層一些的方式是 python3 xxx.py。
以此類推,用 java 編寫的程式碼,想要啟動 java 程式碼,使用:javac MyProgram.java java MyProgram 的方式。
可見無論使用什麼語言編寫的自動化框架,都有一個最底層的執行入口。

如何整合

考慮到將自動化指標與自動化執行整合起來,需要考慮哪些情況?
1、首先我們需要知道某一次執行的結果,是成功,是失敗?
2、其次是這次執行中的校驗粒度如何等等資訊,因為每隔一段時間提交情況都會發生變更。
3、後續統計自動化指令碼完善趨勢時,需要依託每一次執行的結果。
為了達到以上的要求,我們可以定義任務概念,以任務承載以上的資料。
有了承載主體後,接下來我們來聊聊整合設計,這裡需要分成兩種情況:1、公司基礎建設較為完善,有成熟的自動化執行平臺;2、需要我們自己搭建執行環境。
第一種情況,可以透過自動化執行平臺的介面觸發對應的指令碼:

而第二種情況,需要我們自己去觸發自動化指令碼執行,那麼就需要使用到指令碼執行小節的內容了:

由此可見,有沒有成熟的自動化執行平臺其實並不會影響我們構建自動化質量保障體系,有無成熟的執行平臺的差別在於是否需要自行觸發自動化指令碼。

資料設計

接下來我們看看資料表結構設計:

# CI管理-任務管理-任務資訊
class CITaskInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"歸屬組織節點")
    name = models.CharField(max_length=150, verbose_name=u"任務名稱")
    app_code = models.CharField(max_length=150, verbose_name=u"應用code")
    status = models.IntegerField(default=CITaskStatusEnum.INIT.value, verbose_name=u"任務執行狀態")
    env = models.IntegerField(verbose_name=u"歸屬環境(測試/預發/生產)")
    trigger_type = models.IntegerField(verbose_name=u"觸發型別")
    error = models.TextField(max_length=250, null=True, blank=True, verbose_name=u"異常原因")
    report_url = models.CharField(max_length=250, null=True, blank=True, verbose_name=u"執行報告")

    class Meta:
        db_table = "CI_Task_Info"
        verbose_name = "CI管理-CI任務資訊"
        verbose_name_plural = verbose_name

# CI管理-任務管理-執行記錄資訊表
class CITaskExecutionInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"歸屬組織節點")
    task_id = models.IntegerField(verbose_name=u"task_id")
    app_code = models.CharField(max_length=50, verbose_name=u"應用code")
    domain = models.CharField(max_length=150, verbose_name=u"介面域名")
    path = models.CharField(max_length=255, verbose_name=u"介面路徑")
    owner_name = models.CharField(max_length=11, verbose_name=u"負責人姓名")
    owner_phone = models.CharField(max_length=11, verbose_name=u"負責人手機號")
    success = models.BooleanField(default=False, verbose_name=u"是否透過")
    trigger_type = models.IntegerField(verbose_name=u"觸發型別")
    env = models.IntegerField(verbose_name=u"執行環境")

    class Meta:
        db_table = "CI_Task_Execution_Info"
        verbose_name = "CI管理-CI任務執行記錄"
        verbose_name_plural = verbose_name

# CI管理-任務管理-api資訊表
class CITaskApiInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"歸屬組織節點")
    task_id = models.IntegerField(verbose_name=u"task_id")
    app_code = models.CharField(max_length=50, verbose_name=u"服務code")
    api_name = models.CharField(max_length=150, verbose_name=u"api名稱")
    api_analysis_prefix = models.CharField(max_length=50, verbose_name=u"api解析字首")
    api_analysis_postfix = models.CharField(max_length=50, verbose_name=u"api解析字尾")
    domain = models.CharField(max_length=150, verbose_name=u"域名")
    path = models.CharField(max_length=255, verbose_name=u"介面路徑")
    owner_name = models.CharField(max_length=11, verbose_name=u"負責人姓名")
    owner_phone = models.CharField(max_length=11, verbose_name=u"負責人手機號")
    env = models.IntegerField(verbose_name=u"歸屬環境(測試/預發/生產)")
    trigger_type = models.IntegerField(verbose_name=u"任務觸發方式")

    class Meta:
        db_table = "CI_Task_Api_Info"
        verbose_name = "CI管理-CI任務api資訊"
        verbose_name_plural = verbose_name

# CI管理-任務管理-校驗粒度資訊表
class CITaskKeywordInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"歸屬組織節點")
    task_id = models.IntegerField(verbose_name=u"task_id")
    app_code = models.CharField(max_length=50, verbose_name=u"服務code")
    api_name = models.CharField(max_length=150, verbose_name=u"api名稱")
    api_analysis_prefix = models.CharField(max_length=50, verbose_name=u"api解析字首")
    api_analysis_postfix = models.CharField(max_length=50, verbose_name=u"api解析字尾")
    keyword_name = models.CharField(max_length=150, verbose_name=u"keyword名稱")
    keyword_analysis_prefix = models.CharField(max_length=50, verbose_name=u"keyword解析字首")
    keyword_analysis_postfix = models.CharField(max_length=50, verbose_name=u"keyword解析字尾")
    domain = models.CharField(max_length=150, verbose_name=u"域名")
    path = models.CharField(max_length=255, verbose_name=u"介面路徑")
    owner_name = models.CharField(max_length=11, verbose_name=u"負責人姓名")
    owner_phone = models.CharField(max_length=11, verbose_name=u"負責人手機號")
    sentence_count = models.FloatField(verbose_name=u"校驗粒度")
    env = models.IntegerField(verbose_name=u"歸屬環境(測試/預發/生產)")
    trigger_type = models.IntegerField(verbose_name=u"任務觸發方式")

    class Meta:
        db_table = "CI_Task_Keyword_Info"
        verbose_name = "CI管理-CI任務keyword資訊"
        verbose_name_plural = verbose_name

以上的資料表涉及資料均可透過解析檔案逐步提取出來,透過 CITaskKeywordInfo 聚合每一位成員在每次任務下的具體資料:

# CI管理-任務管理-歸屬人總覽資訊表
class CITaskMemberSummaryInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"歸屬組織節點")
    task_id = models.IntegerField(verbose_name=u"task_id")
    app_code = models.CharField(max_length=50, verbose_name=u"服務code")
    owner_name = models.CharField(max_length=11, verbose_name=u"負責人姓名")
    owner_phone = models.CharField(max_length=11, verbose_name=u"負責人手機號")
    env = models.IntegerField(verbose_name=u"歸屬環境(測試/預發/生產)")
    total_api_count = models.IntegerField(verbose_name=u"歸屬介面總量")
    covered_api_count = models.IntegerField(verbose_name=u"覆蓋歸屬介面總量")
    covered_api_rate = models.FloatField(verbose_name=u"歸屬介面覆蓋率")
    total_sentence_count = models.IntegerField(verbose_name=u"校驗粒度總量")
    average_sentence_count = models.FloatField(verbose_name=u"平均粒度(校驗粒度總量/歸屬介面總量)")
    trigger_type = models.IntegerField(verbose_name=u"觸發型別")

    class Meta:
        db_table = "CI_Task_Member_Summary_Info"
        verbose_name = "CI管理-CI任務歸屬人總覽資訊"
        verbose_name_plural = verbose_name

# 獲取校驗粒度記錄
    member_granularity_records = CITaskKeywordInfo.objects.filter(
        department_id=department_id, task_id=ci_task_id, env=env, date_delete=None). \
        order_by("owner_phone", "dimension")
    for member_granularity_info in member_granularity_records:
        owner_name = member_granularity_info.owner_name
        owner_phone = member_granularity_info.owner_phone
        app_code = member_granularity_info.app_code
        dimension = member_granularity_info.dimension

        # 以app_code、歸屬人、統計維度聚合資料
        combine_key = f"{app_code} {owner_name} {owner_phone} {dimension}"
        member_granularity_map[combine_key].append(member_granularity_info)

    for combine_key, date_sequence in member_granularity_map.items():
        app_code, owner_name, owner_phone, dimension = combine_key.split()

        # 計算平均校驗粒度
        total_owner_dimension_api_count = len(date_sequence)
        total_owner_dimension_owner_sentence = sentence_pass_count = 0
        for date_item in date_sequence:
            sentence_count = date_item.sentence_count
            total_owner_dimension_owner_sentence += sentence_count

        if total_owner_dimension_api_count == 0:
            average_sentence_count = 0
        else:
            average_sentence_count = round(total_owner_dimension_owner_sentence /
                                           total_owner_dimension_api_count, 2)
      .......

具體的實現程式碼,大家可依據實際情況進行開發,這個系列的文章主要介紹的是設計思路

結語

至此,與 CI 質量保障相關的設計大綱已經分享完成,有了這套體系後,配合統計圖表,可以直觀看出每一位 QA 人員在自動化方面的產出與變化趨勢,為業務質量保駕護航。感謝觀看至此的同行們,請多批評指正,謝謝。

相關文章