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