如果在 15 分鐘內進行了 3 次失敗的嘗試,我需要鎖定用戶。該帳戶將在一段時間后自動解鎖。現在我將引數 - 最大嘗試次數、鎖定視窗持續時間和鎖定期限作為引數傳遞給實作該功能的類。即使引數值為 2s 或 3s 也會導致單元測驗套件執行完成大約 30 秒。
在這些場景中是否有任何特定的方法或策略來減少測驗執行時間?
uj5u.com熱心網友回復:
有幾個選項:
- 使用測驗替身并注入
IClock測驗可以控制的。 - 使用比秒更小的時間解析度。也許以毫秒而不是秒為單位定義視窗和隔離期。
- 將邏輯寫成一個或多個純函式。
純函式本質上是可測驗的,所以讓我對此進行擴展。
純函式
為了確保我們使用的是純函式,我將在 Haskell 中撰寫測驗和被測系統。
我將假設存在一些檢查單次登錄嘗試是否成功的函式。這是如何作業的,這是一個單獨的問題。我將模擬這樣一個函式的輸出:
data LoginResult = Success | Failure deriving (Eq, Show)
換句話說,登錄嘗試要么成功,要么失敗。
您現在需要一個函式來確定給定登錄嘗試和先前狀態的新狀態。
評估登錄
起初我認為這會更復雜,但 TDD 的好處是,一旦你撰寫了第一個測驗,你就會意識到簡化的潛力。很快,我意識到只需要一個這樣的函式:
evaluateLogin :: (UTCTime, LoginResult) -> [UTCTime] -> [UTCTime]
這個函式需要一個當前LoginResult的時間和它產生的時間(作為一個元組:)(UTCTime, LoginResult),以及以前的失敗日志,并回傳一個新的失敗日志。
經過幾次迭代,我撰寫了這個行內 HUnit 引數化測驗:
"evaluate login" ~: do
(res, state, expected) <-
[
((at 2022 5 16 17 29, Success), [],
[])
,
((at 2022 5 16 17 29, Failure), [],
[at 2022 5 16 17 29])
,
((at 2022 5 16 18 6, Failure), [at 2022 5 16 17 29],
[at 2022 5 16 18 6, at 2022 5 16 17 29])
,
((at 2022 5 16 18 10, Success), [at 2022 5 16 17 29],
[])
]
let actual = evaluateLogin res state
return $ expected ~=? actual
我發現有用的邏輯是,只要有測驗失敗,該evaluateLogin函式就會將失敗時間添加到失敗日志中。另一方面,如果登錄成功,則會清除失敗日志:
evaluateLogin :: (UTCTime, LoginResult) -> [UTCTime] -> [UTCTime]
evaluateLogin ( _, Success) _ = []
evaluateLogin (when, Failure) failureLog = when : failureLog
但是,這并不能告訴您有關用戶的隔離狀態的任何資訊。另一個功能可以解決這個問題。
隔離狀態
以下引數化測驗是多次迭代的結果:
"is locked out" ~: do
(wndw, p, whn, l, expected) <-
[
(ndt 0 15, ndt 1 0, at 2022 5 16 19 59, [], False)
,
(ndt 0 15, ndt 1 0, at 2022 5 16 19 59, [
at 2022 5 16 19 54,
at 2022 5 16 19 49,
at 2022 5 16 19 45
],
True)
,
(ndt 0 15, ndt 1 0, at 2022 5 16 19 59, [
at 2022 5 16 19 54,
at 2022 5 16 19 49,
at 2022 5 16 18 59
],
False)
,
(ndt 0 15, ndt 1 0, at 2022 5 16 19 59, [
at 2022 5 16 19 54,
at 2022 5 16 19 52,
at 2022 5 16 19 49,
at 2022 5 16 19 45
],
True)
,
(ndt 0 15, ndt 1 0, at 2022 5 16 20 58, [
at 2022 5 16 19 54,
at 2022 5 16 19 49,
at 2022 5 16 19 45
],
False)
]
let actual = isLockedOut wndw p whn l
return $ expected ~=? actual
這些測驗推動了以下實作:
isLockedOut :: NominalDiffTime -> NominalDiffTime -> UTCTime -> [UTCTime] -> Bool
isLockedOut window quarantine when failureLog =
case failureLog of
[] -> False
xs ->
let latestFailure = maximum xs
windowMinimum = addUTCTime (-window) latestFailure
lockOut = 3 <= length (filter (windowMinimum <=) xs)
quarantineEndsAt = addUTCTime quarantine latestFailure
isStillQuarantined = when < quarantineEndsAt
in
lockOut && isStillQuarantined
由于它是一個純函式,它可以完全根據輸入確定性地計算隔離狀態。
決定論
以上功能均不依賴于系統時鐘。相反,您將當前時間 ( when) 作為輸入值傳遞,函式會根據輸入計算結果。
這不僅易于單元測驗,還使您能夠執行模擬(測驗本質上是模擬)并計算過去的結果。
uj5u.com熱心網友回復:
時間是一種輸入
如果您不將時間視為輸入值,請考慮它直到您這樣做——這是一個重要的概念——約翰·卡馬克
安排您的設計,以便您可以檢查決定做什么的復雜邏輯,而不是實際執行它。
設計實際執行的部分,使它們“如此簡單,顯然沒有缺陷”。偶爾檢查該代碼,或者作為系統測驗的一部分 - 這些測驗仍然可能會“緩慢”,但它們不會妨礙(因為您找到了一種更有效的方法來緩解“決策和反饋之間的差距”)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/477578.html
