自動化測試平臺設計與實現(二、自動化測試用例物件設計實現、關鍵字物件設計與實現)

jpx發表於2024-08-06

1、模型設計

建立自動化用例,關鍵字模型。其中自動化用例基本內容包含title(目錄展示)、name等常見文字資訊,關鍵字則是實現自動化測試提速的關鍵所在,考慮到業務場景的自動化,就能發現有很多業務步驟是重複的:比如一個管理系統裡面的建立物件,我們將該步驟抽象出來,形成一個關鍵字(keyword),關鍵字儲存url、header等資訊,要錄入到自動化用例testcase模型中,則使用中間表TestCaseKeyword,它會詳細記錄某個自動化用例的(關鍵字-順序),並且關鍵字會帶有詳細的params、body等資訊,也就是可以被全域性變數,或者常量所覆蓋。

我們的project、testcase、keyword設計理念:首先project是用例的集合,每個project內包含name、title不能重複的testcase,至於project之間如果testcase name重複則無所謂。建立testcase,要帶上project資訊,用project_case來標記唯一。testcase一旦建立,就固定屬於某個project,沒必要對testcase做project級別的遷移。因為現在以projectname+testcasetitle唯一標記一個用例,那我們的update操作,就對project內的用例才生效,show_all也是展示同project的用例,search、delete_title_list同樣。

對於keyword和testcase,每個testcase以keyword+data+keyword順序來形成一套業務流程。keyword預設只存一些url、header-key,data-key等,在testcase建立、修改過程中,填入header-value、data-value等形成可用請求,填入order標記本keyword的順序。

斷言:是自動化用例來判斷是否透過的必要元素。斷言設計:給keyword新增斷言配置,在模型裡面應該是個斷言列表,每個元素是一個斷言,每個斷言由(目標值,比較符,被比較值組成),目標值、被比較值可以是int、string,比較符可以是數值型的大於、小於、等於、大於等於、小於等於,字元型的equal:相等、contains:被比較值包含目標值字串、in:目標值字串包含被比較值。 業務邏輯:1、keyword新增時,可以新增斷言到斷言列表,這個時候一個斷言元素的被比較值非必填項、但比較符、目標值都是必填元素;新增keyword也可以不必新增斷言。2、keyword修改時,同理,可以新增、修改、刪除斷言,但斷言元素的被比較值非必填項、但比較符、目標值都是必填元素; 3、新增、修改自動化用例的時候,可以給用例的keyword新增、修改、刪除斷言元素,這個時候斷言元素的被比較值可以填充了。

import functools
import uuid
import random
import string

from django.db import models
from django.core.exceptions import ValidationError
from django.utils import timezone


# Create your models here.
def generate_random_string(except_str, length=10):
    characters = string.ascii_letters + string.digits
    random_chars = ''.join(random.choice(characters) for _ in range(length))
    return f'{except_str}_{random_chars}' if except_str else random_chars


def validate_positive(value):
    if value < 0:
        raise ValidationError('%(value)s is not a positive integer or 0', params={'value': value})


class Project(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.name


class Testcase(models.Model):
    # 透過 related_name 訪問所有關聯的 Testcase 例項:testcases = project.testcases.all()
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='testcases')
    title_default = functools.partial(generate_random_string, "title")
    name_default = functools.partial(generate_random_string, "test")
    title = models.CharField(max_length=50, null=False, blank=False, default=title_default)
    name = models.CharField(max_length=50, null=False, blank=False, default=name_default)
    project_case = models.CharField(max_length=150, unique=True, null=False, blank=False)
    level = models.IntegerField(default=0, validators=[validate_positive])
    precondition = models.CharField(max_length=300, null=True, blank=True, default=None)
    test_precondition = models.CharField(max_length=300, null=True, blank=True, default=None)
    expected_result = models.CharField(max_length=300, null=True, blank=True, default=None)
    TYPE = [
        ("function_case", "功能用例"),
        ("performance_case", "效能用例"),
        ("reliability_case", "可靠性用例"),
    ]
    type = models.CharField(max_length=20, choices=TYPE, default="function_case")
    auto_flag = models.BooleanField(default=False, null=True, blank=True)
    description = models.TextField(blank=True, null=True)
    keywords = models.ManyToManyField("KeyWord", through='TestCaseKeyword')

    class Meta:
        # unique_together 確保在資料庫層面上欄位組合的唯一性約束。在 Testcase 模型中,它確保每個專案中的 title 和 name 的組合是唯一的
        # 可以在資料庫層面上提供額外的安全保障
        unique_together = ('project', 'title', 'name')

    def save(self, *args, **kwargs):
        # 在儲存物件到資料庫之前或之後執行一些操作。在 Testcase 模型中,重寫了 save 方法以自動生成 project_case 欄位的值
        self.project_case = f"{self.project.name}_{self.title}"
        super(Testcase, self).save(*args, **kwargs)

    def __str__(self):
        return f"{self.title}_{self.name}"


class KeyWord(models.Model):
    BODY_TYPES = [
        ('application/x-www-form-urlencoded', 'Application/X-WWW-Form-Urlencoded'),
        ('raw', 'Raw'),
        ('multipart/form-data', 'Multipart/Form-Data'),
    ]
    name_default = functools.partial(generate_random_string, "kw")
    name = models.CharField(max_length=100, unique=True, null=False, blank=False, default=name_default)
    url = models.URLField()
    params = models.JSONField(blank=True, null=True)
    headers = models.JSONField(blank=True, null=True)
    body_type = models.CharField(max_length=50, choices=BODY_TYPES)
    body = models.TextField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.name


class TestCaseKeyword(models.Model):
    test_case = models.ForeignKey(Testcase, on_delete=models.CASCADE)
    keyword = models.ForeignKey(KeyWord, on_delete=models.CASCADE)
    order = models.PositiveIntegerField()
    params = models.JSONField(blank=True, null=True)
    headers = models.JSONField(blank=True, null=True)
    body = models.TextField(blank=True, null=True)

    class Meta:
        ordering = ['order']

    def __str__(self):
        return f"{self.test_case.name} - {self.keyword.name} ({self.order})"

2、 業務分析與設計:

檢視操作

  1. testcase檢視:

    • POST請求,根據operate欄位執行不同操作:
      • create: 建立測試用例。
      • update: 更新測試用例。
      • delete: 刪除測試用例。
      • show_all: 展示所有測試用例。
      • search: 搜尋測試用例。
      • show_testcase: 展示單個測試用例及其關聯的關鍵字。
  2. project檢視:

    • POST請求,根據operate欄位執行不同操作:
      • create: 建立專案。
      • update: 更新專案。
      • delete: 刪除專案。
      • show_all: 展示所有專案。

關鍵函式分析

  1. create_testcase:

    • 建立測試用例並處理關聯的關鍵字。
    • 關鍵字資訊儲存在TestCaseKeyword中間表中。
  2. update_testcase:

    • 更新測試用例及其關聯的關鍵字。
    • 透過比較請求中的關鍵字與當前關鍵字,決定新增、修改或刪除。
  3. delete_testcase:

    • 刪除測試用例及其關聯的關鍵字。
    • 級聯刪除透過on_delete=models.CASCADE實現。
  4. show_all_testcases:

    • 展示所有測試用例,按專案篩選。
  5. search_testcase:

    • title模糊搜尋測試用例。
  6. show_testcase:

    • 展示單個測試用例及其關聯的關鍵字。
  7. create_project:

    • 建立專案,使用序列化器驗證資料。
  8. update_project:

    • 更新專案,根據update_source_name查詢專案。
  9. delete_project:

    • 刪除專案,根據delete_list中的專案名稱批次刪除。
  10. show_all_project:

    • 展示所有專案。

對於用例的keyword修改,大致邏輯:

  • 建立 unchanged_keywords 列表

    • 使用 unchanged_keywords 列表來儲存在 current_keywords_dictrequest_keywords_dict 中都存在,並且值未發生變化的關鍵字。
  • 分類關鍵字

    • to_delete_keywords:請求中不存在的當前關鍵字。
    • to_add_or_update_keywords:請求中新增或修改的關鍵字。
    • unchanged_keywords:在當前關鍵字和請求關鍵字中都存在且未發生變化的關鍵字。
  • 刪除不存在的關鍵字關聯

    • 使用 to_delete_keywords 列表刪除不存在的關鍵字關聯。
  • 新增或更新關鍵字

    • 使用 to_add_or_update_keywords 列表新增或更新關鍵字及其斷言。
  • 重新整理未改動關鍵字的斷言

    • 使用 unchanged_keywords 列表重新整理未改動關鍵字的斷言。

對應請求體結構,統一設計為:

{
    "operate": "create/update/delete/show_all/search",
    "project_name": "projectA",
    "parameters": {
        "title": "title",
        "name": "name",
        "level": "level",
        "precondition": "precondition",
        "test_precondition": "test_precondition",
        "expected_result": "expected_result",
        "type": "type",
        "auto_flag": "auto_flag",
        "update_source_title": "update_source_title",
        "delete_title_list": [],
        "description": "description",
        "keywords": [
            {
                "name": "Example API Request",
                "order": 1,
                "params": {"param1": "value1_param1", "param2": "value1_param2"},
                "headers": {"Authorization": "Bearer value1_token"},
                "body": "value1_body"
            },
            {
                "name": "keyword2",
                "order": 2,
                "params": {"param1": "value2_param1", "param2": "value2_param2"},
                "headers": {"Authorization": "Bearer value2_token"},
                "body": "value2_body"
            }
        ]
    }
}

3、補充斷言設定:斷言既可在keyword建立時新增斷言,keyword修改時新增、修改、刪除斷言;也可在建立用例時,這個時候建立與keyword的聯絡、這個時候重新整理斷言;或者修改用例的時候,既修改了關鍵字,也同步了所有關鍵字的斷言。

tips:

1. python有前向引用:如果在一個模型類中引用了另一個尚未定義的模型類,可能會出現 Unresolved reference 錯誤。

可以在 Testcase 模型中使用前向引用,即在字串中引用 KeyWord 模型。這樣可以解決。但函式可以在程式碼的任何位置定義和呼叫。

前向引用:在欄位定義中使用字串形式的類名引用未定義的模型類。

相關文章