Sentry 開發(fā)者貢獻指南 - 測試技巧
作為 CI 流程的一部分,我們在 Sentry 運行了多種測試。本節(jié)旨在記錄一些 sentry 特定的幫助程序, 并提供有關在構建新功能時應考慮包括哪些類型的測試的指南。
獲取設置
驗收和 python 測試需要一組有效的 devservices。建議使用 devservices 來確保所需要的服務正在運行。如果您還使用本地環(huán)境進行本地測試,您將需要使用 --project 標志將本地測試卷與測試套件卷分開:
# 關閉本地測試服務。
sentry devservices down
# 打開帶有 test 前綴的服務以使用單獨的容器和卷
sentry devservices up --project test
# 驗證測試容器是否正確出現(xiàn)
docker ps --format '{{.Names}}'
# 稍后當您完成運行測試并想再次運行本地服務器時
sentry devservices down --project test && sentry devservices up
使用 --project 選項時,您可以確認哪些容器正在運行 docker ps。每個正在運行的容器都應該以 test_ 為前綴。有關管理服務的更多信息,請參閱 devservices docs 部分。
??https://develop.sentry.dev/services/devservices/??
Python 測試
對于 python 測試,我們使用 pytest 和 Django 提供的測試工具。在此基礎之上,我們添加了一些基本測試用例(在 sentry.testutils.cases 中)。
??https://docs.pytest.org/en/latest/??
端點集成測試是我們大部分測試套件的重點所在。這些測試幫助我們確保我們的 customers、integrations 和前端應用程序的 API 繼續(xù)以預期的方式工作。您應該努力包含涵蓋各種用戶角色、跨組織/團隊訪問場景以及無效數(shù)據(jù)場景的測試,因為這些在手動測試時經(jīng)常被忽略。
運行 pytest
您可以根據(jù)更改的范圍使用 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 文檔。
??http://doc.pytest.org/en/latest/usage.html??
在測試中創(chuàng)建數(shù)據(jù)
Sentry 還添加了一套 factory 輔助方法,可幫助您構建數(shù)據(jù)以針對其編寫測試。 sentry.testutils.factories 中的工廠方法可用于我們所有的測試套件類。使用這些方法來建立所需的組織、項目和其他基于 postgres 的狀態(tài)。
您還應該使用 store_event() 以類似于應用程序在生產(chǎn)中所做的方式存儲事件。存儲事件需要您的測試繼承自 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)
設置選項和功能標志
如果您的測試是針對帶有功能標記的端點,或者需要設置特定選項。您可以使用輔助方法將配置數(shù)據(jù)更改為正確的狀態(tài):
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 庫為您的代碼發(fā)出的出站 API 請求添加存根響應。這將幫助您相對輕松地模擬成功和失敗的場景。
可靠地使用時間
在編寫與攝取事件相關的測試時,我們必須在事件的約束內(nèi)操作不能超過 30 天。因為所有事件都必須是最近的,所以我們不能使用傳統(tǒng)的時間凍結(jié)策略在測試中獲得一致的數(shù)據(jù)。我們不是選擇任意的時間點,而是從現(xiàn)在開始向后工作,并且有一些輔助函數(shù)可以這樣做:
from sentry.testutils.helpers.datetime import before_now, iso_format
five_min_ago = before_now(minutes=5)
iso_timestamp = iso_format(five_min_ago)
這些函數(shù)生成 datetime 對象,以及相對于當前的 ISO 8601 格式的 datetime 字符串, 使您能夠在已知時間偏移處擁有事件,而不會違反 relay 強加的 30 天限制。
在測試中檢查 SQL 查詢
將以下內(nèi)容添加到項目根目錄中的 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)
現(xiàn)在,在測試期間執(zhí)行的所有 SQL 都將打印到標準輸出。建議使用 pytest 的 -k 選擇器縮小輸出范圍。另請注意,您需要通過 -s 來查看標準輸出。
驗收測試
我們的驗收測試利用 selenium 和 chromedriver 來模擬用戶使用前端應用程序和整個后端堆棧。我們在 Sentry 使用驗收測試有兩個目的:
涵蓋僅通過端點測試或僅使用 Jest 無法涵蓋的工作流程。
通過我們的視覺回歸 GitHub Actions 為視覺回歸測試準備快照。
驗收測試可以在 tests/acceptance 中找到,并使用 pytest 在本地運行。
運行驗收測試
當您運行驗收測試時,webpack 將自動運行以構建靜態(tài)資資源。如果您在創(chuàng)建或修改驗收測試時更改 Javascript 文件, 則每次更改后都需要 rm .webpack.meta 以觸發(fā)靜態(tài)資源的重建。
# 運行單個驗收測試。
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 點。
處理異步動作
我們所有的數(shù)據(jù)都異步加載到前端,驗收測試需要考慮各種延遲和響應時間。我們傾向于使用 selenium 的 wait_until* 特性來輪詢 DOM,直到元素出現(xiàn)或可見。我們不使用 sleep()。
視覺回歸
像素很重要,因此我們使用視覺回歸來幫助捕捉 Sentry 渲染方式的意外變化。在驗收測試期間,我們捕獲屏幕截圖并將您的拉取請求中的屏幕截圖與批準的基線進行比較。
雖然我們對視覺回歸有相當廣泛的覆蓋,但仍有一些重要的盲點:
懸停(Hover)卡片與懸停狀態(tài)
模態(tài)窗口
圖表和數(shù)據(jù)可視化
所有這些組件和交互通常不包含在可視化快照中,您在處理其中任何一個時都應該小心。
處理不斷變化的數(shù)據(jù)
因為視覺回歸比較圖像快照,而且我們數(shù)據(jù)的很大一部分處理時間序列數(shù)據(jù), 所以我們經(jīng)常需要用 'fixed' 數(shù)據(jù)替換基于時間的內(nèi)容。您可以使用 getDynamicText 幫助程序為依賴于當前時間或變化 過于頻繁而無法包含在可視快照中的組件/數(shù)據(jù)提供固定內(nèi)容。
Jest 測試
我們的 Jest 套件涵蓋為前端組件提供功能和單元測試。我們更喜歡編寫與組件交互并觀察結(jié)果(導航、API 調(diào)用)的功能測試, 而不是檢查 prop 傳遞和 state 突變。請參閱 Frontend Handbook 了解更多 Jest 測試技巧。
??https://develop.sentry.dev/frontend/#testing??
# 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 函數(shù)。
您還應該使用 MockApiClient.addMockResponse() 來設置您的組件將進行的 API 調(diào)用的響應。未能模擬端點將導致測試失敗。
CI 中的 Kafka 測試
Snuba 測試套件 (.github/workflows/snuba-integration-test.yml) 是唯一真正讓 Kafka 在 CI 中運行的測試套件。如果您有一個需要 Kafka 運行的測試,那么這些測試需要嵌套在 Snuba 測試文件夾 (tests/snuba/) 下。如果不這樣做,您的測試將超時并在 GH actions 中被取消。