Sentry 開發者貢獻指南 - 測試技巧

為少發表於2022-01-19

作為 CI 流程的一部分,我們在 Sentry 執行了多種測試。
本節旨在記錄一些 sentry 特定的幫助程式,
並提供有關在構建新功能時應考慮包括哪些型別的測試的指南。

獲取設定

驗收和 python 測試需要一組有效的 devservices
建議使用 devservices 來確保所需要的服務正在執行。
如果您還使用本地環境進行本地測試,您將需要使用 --project 標誌將本地測試卷與測試套件卷分開:

# 關閉本地測試服務。
sentry devservices down

# 開啟帶有 test 字首的服務以使用單獨的容器和卷
sentry devservices up --project test

# 驗證測試容器是否正確出現
docker ps --format '{{.Names}}'

# 稍後當您完成執行測試並想再次執行本地伺服器時
sentry devservices down --project test && sentry devservices up

使用 --project 選項時,您可以確認哪些容器正在執行 docker ps
每個正在執行的容器都應該以 test_ 為字首。
有關管理服務的更多資訊,請參閱 devservices docs 部分。

Python 測試

對於 python 測試,我們使用 pytestDjango 提供的測試工具。
在此基礎之上,我們新增了一些基本測試用例(在 sentry.testutils.cases 中)。

端點整合測試是我們大部分測試套件的重點所在。
這些測試幫助我們確保我們的 customersintegrations 和前端應用程式的 API 繼續以預期的方式工作。
您應該努力包含涵蓋各種使用者角色、跨組織/團隊訪問場景以及無效資料場景的測試,因為這些在手動測試時經常被忽略。

執行 pytest

您可以根據更改的範圍使用 pytest 執行單個目錄單個檔案單個測試

# 對整個目錄執行測試
pytest tests/sentry/api/endpoints/

# 對目錄中匹配模式的所有檔案執行測試
pytest tests/sentry/api/endpoints/test_organization_*.py

# 從單個檔案執行測試
pytest tests/sentry/api/endpoints/test_organization_group_index.py

# 執行單個測試
pytest tests/snuba/api/endpoints/test_organization_events_distribution.py::OrganizationEventsDistributionEndpointTest::test_this_thing

# 在匹配子字串的檔案中執行所有測試
pytest tests/snuba/api/endpoints/test_organization_events_distribution.py -k method_name

pytest 的一些常用選項是:

  • -k 通過子字串過濾測試方法/類
  • -s 在執行測試時不要捕獲標準輸出。

有關更多使用選項,請參閱 pytest 文件。

在測試中建立資料

Sentry 還新增了一套 factory 輔助方法,可幫助您構建資料以針對其編寫測試。
sentry.testutils.factories 中的工廠方法可用於我們所有的測試套件類。
使用這些方法來建立所需的組織專案和其他基於 postgres 的狀態。

您還應該使用 store_event() 以類似於應用程式在生產中所做的方式儲存事件。
儲存事件需要您的測試繼承自 SnubaTestCase
使用 store_event() 時,請注意在事件上設定過去timestamp
省略時,timestamp 將使用 'now',這可能會導致由於 timestamp 邊界而無法選擇事件。

from sentry.testutils.helpers.datetime import before_now
from sentry.utils.samples import load_data

def test_query(self):
    data = load_data("python", timestamp=before_now(minutes=1))
    event = self.store_event(data, project_id=self.project.id)

設定選項和功能標誌

如果您的測試是針對帶有功能標記的端點,或者需要設定特定選項。
您可以使用輔助方法將配置資料更改為正確的狀態:

def test_success(self):
    with self.feature('organization:new-thing'):
        with self.options({'option': 'value'}):
            # Run test logic with features and options set.

    # Disable the new-thing feature.
    with self.feature({'organization:new-thing': False}):
        # Run you logic with a feature off.

外部服務

使用 responses 庫為您的程式碼發出的出站 API 請求新增存根響應。
這將幫助您相對輕鬆地模擬成功和失敗的場景。

可靠地使用時間

在編寫與攝取事件相關的測試時,我們必須在事件的約束內操作不能超過 30 天。
因為所有事件都必須是最近的,所以我們不能使用傳統的時間凍結策略在測試中獲得一致的資料。
我們不是選擇任意的時間點,而是從現在開始向後工作,並且有一些輔助函式可以這樣做:

from sentry.testutils.helpers.datetime import before_now, iso_format

five_min_ago = before_now(minutes=5)
iso_timestamp = iso_format(five_min_ago)

這些函式生成 datetime 物件,以及相對於當前的 ISO 8601 格式的 datetime 字串,
使您能夠在已知時間偏移處擁有事件,而不會違反 relay 強加的 30 天限制。

在測試中檢查 SQL 查詢

將以下內容新增到專案根目錄中的 conftest.py 中:

import itertools
from django.conf import settings
from django.db import connection, connections, reset_queries
from django.template import Template, Context

@pytest.fixture(scope="function", autouse=True)
def log_sql():
    reset_queries()
    settings.DEBUG = True

    yield

    time = sum([float(q["time"]) for q in connection.queries])
    t = Template(
        "{% for sql in sqllog %}{{sql.sql|safe}}{% if not forloop.last %}\n\n{% endif %}{% endfor %}"
    )
    queries = list(itertools.chain.from_iterable([conn.queries for conn in connections.all()]))
    log = t.render(Context({"sqllog": queries, "count": len(queries), "time": time}))
    print(log)

現在,在測試期間執行的所有 SQL 都將列印到標準輸出。
建議使用 pytest-k 選擇器縮小輸出範圍。另請注意,您需要通過 -s 來檢視標準輸出。

驗收測試

我們的驗收測試利用 seleniumchromedriver 來模擬使用者使用前端應用程式和整個後端堆疊。
我們在 Sentry 使用驗收測試有兩個目的:

  1. 涵蓋僅通過端點測試或僅使用 Jest 無法涵蓋的工作流程。
  2. 通過我們的視覺迴歸 GitHub Actions 為視覺迴歸測試準備快照。

驗收測試可以在 tests/acceptance 中找到,並使用 pytest 在本地執行。

執行驗收測試

當您執行驗收測試時,webpack 將自動執行以構建靜態資資源。
如果您在建立或修改驗收測試時更改 Javascript 檔案,
則每次更改後都需要 rm .webpack.meta 以觸發靜態資源的重建。

# 執行單個驗收測試。
pytest tests/acceptance/test_organization_group_index.py \
    -k test_with_onboarding

# 執行帶有頭的瀏覽器,以便您可以觀看它。
pytest tests/acceptance/test_organization_group_index.py \
    --no-headless=true \
    -k test_with_onboarding

# 開啟每個 snapshot image
SENTRY_SCREENSHOT=1 VISUAL_SNAPSHOT_ENABLE=1 \
    pytest tests/acceptance/test_organization_group_index.py \
    -k test_with_onboarding

如果您看到:
WARNING: Failed to gather log types: Message: unknown > command: Cannot call non W3C standard command while in W3C mode
則表示 Webpack 未正確編譯資源。

定位元素

因為我們使用 emotion,所以我們的類名通常對瀏覽器自動化沒有用。
相反,我們自由地使用 data-test-id 屬性來定義瀏覽器自動化和 Jest 測試的 hook 點。

處理非同步動作

我們所有的資料都非同步載入到前端,驗收測試需要考慮各種延遲和響應時間。
我們傾向於使用 seleniumwait_until* 特性來輪詢 DOM,直到元素出現或可見。
我們不使用 sleep()

視覺迴歸

畫素很重要,因此我們使用視覺迴歸來幫助捕捉 Sentry 渲染方式的意外變化。
在驗收測試期間,我們捕獲螢幕截圖並將您的拉取請求中的螢幕截圖與批准的基線進行比較。

雖然我們對視覺迴歸有相當廣泛的覆蓋,但仍有一些重要的盲點:

  • 懸停(Hover)卡片與懸停狀態
  • 模態視窗
  • 圖表和資料視覺化

所有這些元件和互動通常不包含在視覺化快照中,您在處理其中任何一個時都應該小心。

處理不斷變化的資料

因為視覺迴歸比較影像快照,而且我們資料的很大一部分處理時間序列資料,
所以我們經常需要用 'fixed' 資料替換基於時間的內容。
您可以使用 getDynamicText 幫助程式為依賴於當前時間或變化
過於頻繁而無法包含在可視快照中的元件/資料提供固定內容。

Jest 測試

我們的 Jest 套件涵蓋為前端元件提供功能和單元測試。
我們更喜歡編寫與元件互動並觀察結果(導航、API 呼叫)的功能測試,
而不是檢查 prop 傳遞和 state 突變。
請參閱 Frontend Handbook 瞭解更多 Jest 測試技巧。

# Run jest in interactive mode
yarn test

# Run a single test
yarn test tests/js/spec/views/issueList/overview.spec.js

API Fixtures

因為我們的 Jest 測試在沒有 API 的情況下執行,
所以我們有各種 fixture 構建器可用於幫助生成 API 響應有效負載。
TestStubs 全域性包括 tests/js/sentry-test/fixtures/ 中的所有 fixture 函式。

您還應該使用 MockApiClient.addMockResponse() 來設定您的元件將進行的 API 呼叫的響應。未能模擬端點將導致測試失敗。

CI 中的 Kafka 測試

Snuba 測試套件 (.github/workflows/snuba-integration-test.yml) 是唯一真正讓 KafkaCI 中執行的測試套件。
如果您有一個需要 Kafka 執行的測試,那麼這些測試需要巢狀在 Snuba 測試資料夾 (tests/snuba/) 下。
如果不這樣做,您的測試將超時並在 GH actions 中被取消。

更多

相關文章