Sentry 開發者貢獻指南 – 測試技巧
- 2022 年 1 月 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
測試,我們使用 pytest 和 Django
提供的測試工具。
在此基礎之上,我們添加了一些基本測試用例(在 sentry.testutils.cases
中)。
端點集成測試是我們大部分測試套件的重點所在。
這些測試幫助我們確保我們的 customers
、integrations
和前端應用程式的 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
來查看標準輸出。
驗收測試
我們的驗收測試利用 selenium
和 chromedriver
來模擬用戶使用前端應用程式和整個後端堆棧。
我們在 Sentry
使用驗收測試有兩個目的:
- 涵蓋僅通過端點測試或僅使用
Jest
無法涵蓋的工作流程。 - 通過我們的視覺回歸
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
點。
處理非同步動作
我們所有的數據都非同步載入到前端,驗收測試需要考慮各種延遲和響應時間。
我們傾向於使用 selenium
的 wait_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
) 是唯一真正讓 Kafka
在 CI
中運行的測試套件。
如果您有一個需要 Kafka
運行的測試,那麼這些測試需要嵌套在 Snuba
測試文件夾 (tests/snuba/
) 下。
如果不這樣做,您的測試將超時並在 GH actions
中被取消。