Golang에서 품질 시간 기반 테스트를 작성하는 방법
Echo에서 PR을 열고 일부 환경에서는 테스트가 통과했지만 다른 환경에서는 실패했음을 확인했습니다. 그것을 이해하려고 노력한 후에 나는 패턴이 없다는 것을 알았습니다. 실패는 완전히 무작위적이고 예측할 수 없습니다. 소스 코드를 연구하고 특정 장치의 결함을 찾아내려고 했지만 모든 것이 완벽해 보였습니다. 유일한 변수인 시간을 연구하려고 했던 때였습니다. 내 테스트는 호스트 컴퓨터의 시계에 의존하고 시스템 틱은 CPU가 부담을 겪을 때 지연될 수 있기 때문에 내 테스트는 각 틱 동안의 지연으로 인해 CPU 성능이 낮은 시스템에서 실패할 가능성이 높습니다.
다음은 업데이트된 PR에 대한 링크입니다. 직접 진행 상황을 확인하고 싶을 경우:
속도 제한을 위한 미들웨어 추가
#1724
iambenkay
에 게시됨
새로운 기능
이 기능은 미들웨어 즉, 사용자가 선택한 모든 저장소로 구성할 수 있는 저장소 독립적 속도 제한 미들웨어를 구현합니다.
다음은 특정(틀에 얽매이지 않는) redis 저장소가 속도 제한 미들웨어와 통합될 수 있는 방법에 대한 더미 스니펫입니다.
type RedisStore struct {
client redis.Client
}
// Store config must implement Allow for rate limiting middleware
func (store *RedisStore) Allow(identifier) bool {
// run logic here that decides if user should be permitted
return true
}
func main(){
e := echo.New()
redisStore := RedisStore{
client: redis.Client{}
}
limiterMW := middleware.RateLimiter(redisStore)
e.Use(limiterMW)
}
이 기능은 미들웨어 즉, 사용자가 선택한 모든 저장소로 구성할 수 있는 저장소 독립적 속도 제한 미들웨어를 구현합니다.
다음은 특정(틀에 얽매이지 않는) redis 저장소가 속도 제한 미들웨어와 통합될 수 있는 방법에 대한 더미 스니펫입니다.
type RedisStore struct { client redis.Client } // Store config must implement Allow for rate limiting middleware func (store *RedisStore) Allow(identifier) bool { // run logic here that decides if user should be permitted return true } func main(){ e := echo.New() redisStore := RedisStore{ client: redis.Client{} } limiterMW := middleware.RateLimiter(redisStore) e.Use(limiterMW) }
I threw in an InMemory implementation for people like me who want to get on the go fast.
func main(){
e := echo.New()
var inMemoryStore = middleware.RateLimiterMemoryStore{
rate: 1,
burst: 3,
}
limiterMW := middleware.RateLimiterWithConfig(RateLimiterConfig{
Store: &inMemoryStore,
SourceFunc: func(ctx echo.Context) string {
return ctx.RealIP()
},
})
e.Use(limiterMW)
}
closes #1721
So after I established that the system ticker cannot be trusted to provide a consistent tick rate enough to verify that my rate limiter works as intended, I set off to trick my tests by finding a stable time model that does not depend on the system ticker. The rest of this blog will cover the specifics.
Firstly, you'd need to wrap all usages of standard time helpers with custom functions that resolve to them. In my case the only main helper I needed to wrap was time.Now
.
var now func() time.Time
now = func() time.Time {
return time.Now()
}
이제 소스 코드에서 이제 래핑된 도우미의 모든 항목을 사용자 지정 도우미로 바꿉니다. 이것의 목표는 기본 소스 코드에서 원래 헬퍼를 사용할 수 있지만 해당 테스트에서 쉽게 조롱할 수 있도록 하는 것입니다.
func (store *RateLimiterMemoryStore) Allow(identifier string) bool {
store.mutex.Lock()
limiter, exists := store.visitors[identifier]
if !exists {
limiter = new(Visitor)
limiter.Limiter = rate.NewLimiter(store.rate, store.burst)
limiter.lastSeen = now() // instead of time.Now()
store.visitors[identifier] = limiter
}
limiter.lastSeen = now() // instead of time.Now()
store.mutex.Unlock()
if now().Sub(store.lastCleanup) > store.expiresIn {
store.cleanupStaleVisitors()
}
return limiter.AllowN(now() /* instead of time.Now() */, 1)
}
이제 테스트 컨텍스트에서 표준 시간에서 사용자 지정 계산 시간으로 반환되는 항목
now()
을 변경해야 합니다.func TestRateLimiterMemoryStore_Allow(t *testing.T) {
var inMemoryStore = NewRateLimiterMemoryStore(RateLimiterMemoryStoreConfig{rate: 1, burst: 3, expiresIn: 2 * time.Second})
testCases := []struct {
id string
allowed bool
}{
{"127.0.0.1", true}, // 0 ms
{"127.0.0.1", true}, // 220 ms burst #2
{"127.0.0.1", true}, // 440 ms burst #3
{"127.0.0.1", false}, // 660 ms block
{"127.0.0.1", false}, // 880 ms block
{"127.0.0.1", true}, // 1100 ms next second #1
{"127.0.0.2", true}, // 1320 ms allow other ip
{"127.0.0.1", false}, // 1540 ms no burst
{"127.0.0.1", false}, // 1760 ms no burst
{"127.0.0.1", false}, // 1980 ms no burst
{"127.0.0.1", true}, // 2200 ms no burst
{"127.0.0.1", false}, // 2420 ms no burst
{"127.0.0.1", false}, // 2640 ms no burst
{"127.0.0.1", false}, // 2860 ms no burst
{"127.0.0.1", true}, // 3080 ms no burst
{"127.0.0.1", false}, // 3300 ms no burst
}
for i, tc := range testCases {
t.Logf("Running testcase #%d => %v", i, time.Duration(i)*220*time.Millisecond)
// Should have been a time.Sleep here but we manually increase the value of the date that the now function returns using the iterator from the for loop.
now = func() time.Time {
return time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Add(time.Duration(i) * 220 * time.Millisecond)
}
allowed := inMemoryStore.Allow(tc.id)
assert.Equal(t, tc.allowed, allowed)
}
}
내 테스트를 위해 시간 변경을 시뮬레이션하기 위해
time.Sleep
또는 시스템 티커에 의존하지 않는다는 것을 알 수 있습니다. 반복 횟수를 기반으로 for 루프에서 다음에 유연하게 계산하고 있습니다. 이 방법으로 테스트는 값으로 작업하기 전에 시스템 티커가 실제로 틱할 때까지 문자 그대로 기다릴 필요가 없으며 시간이 계산되기 때문에 실제로 테스트는 완료하는 데 필요한 실제 라이브 시간(초)을 기다릴 필요가 없습니다. 테스트 속도와 CPU에 구애받지 않는 유연성을 제공합니다. 이제 CPU의 클럭 속도에 관계없이 통과할 시간 기반 테스트를 작성할 수 있습니다.Reference
이 문제에 관하여(Golang에서 품질 시간 기반 테스트를 작성하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/iambenkay/how-to-write-quality-time-based-tests-in-golang-3e08텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)