在测试从API调用的数据库过程时,当它按顺序运行时,它似乎在~3秒内一致地运行。但是我们注意到,当多个请求同时进入时,这可能需要更长时间,导致超时。我试图重现“一次几个请求”案例作为go test
。
我尝试了-parallel 10
go测试标志,但the timings在~28s时是相同的。
我的benchmark function有什么问题吗?
func Benchmark_RealCreate(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
name := randomdata.SillyName()
r := gofight.New()
u := []unit{unit{MefeUnitID: name, MefeCreatorUserID: "user", BzfeCreatorUserID: 55, ClassificationID: 2, UnitName: name, UnitDescriptionDetails: "Up on the hills and testing"}}
uJSON, _ := json.Marshal(u)
r.POST("/create").
SetBody(string(uJSON)).
Run(h.BasicEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Contains(b, r.Body.String(), name)
assert.Equal(b, http.StatusOK, r.Code)
})
}
}
那么我怎么能实现我的目标呢?
在多个实例中,-parallel
标志不用于运行相同的测试或基准并行。
-parallel n Allow parallel execution of test functions that call t.Parallel. The value of this flag is the maximum number of tests to run simultaneously; by default, it is set to the value of GOMAXPROCS. Note that -parallel only applies within a single test binary. The 'go test' command may run tests for different packages in parallel as well, according to the setting of the -p flag (see 'go help build').
因此,基本上如果您的测试允许,您可以使用-parallel
并行运行多个不同的测试或基准测试功能,但在多个实例中不能使用相同的测试或基准测试功能。
通常,并行运行多个基准函数会破坏对函数进行基准测试的目的,因为在多个实例中并行运行它通常会使基准测试失真。
但是,在您的情况下,代码效率不是您想要测量的,您希望测量外部服务。所以go的内置测试和基准测试设施并不适合。
当然,当我们的其他测试和基准运行时,我们仍然可以使用这种“基准”自动运行的便利性,但是你不应该强迫它进入传统的基准测试框架。
首先想到的是使用for循环来启动所有尝试调用可测试服务的n
goroutines。这样做的一个问题是,这只能在开始时确保n
并发goroutine,因为当调用开始完成时,其余的并发性将会越来越少。
为了克服这一点并真正测试n
并发调用,你应该有一个n
工作者的工作池,并不断向这个工作池提供工作,确保始终有n
并发服务调用。有关工作池实现,请参阅Is this an idiomatic worker thread pool in Go?
总而言之,用n
工人开辟一个工人池,让goroutine在任意时间(例如30秒或1分钟)向其发送工作,并测量(计数)已完成的工作。基准测试结果将是一个简单的划分。
另请注意,仅出于测试目的,甚至可能不需要工作池。你可以使用一个循环来启动n
goroutines,但要确保每个启动goroutine一直调用服务,而不是在一次调用后返回。
您的示例代码混合了几个东西。你为什么在那里使用assert
?这不是一个测试它是一个基准。如果assert
方法很慢,那么你的基准测试就是。
您还将并行执行从代码中移出到test命令中。您应该尝试使用并发来发出并行请求。这里有一个可能性如何开始:
func executeRoutines(routines int) {
wg := &sync.WaitGroup{}
wg.Add(routines)
starter := make(chan struct{})
for i := 0; i < routines; i++ {
go func() {
<-starter
// your request here
wg.Done()
}()
}
close(starter)
wg.Wait()
}
https://play.golang.org/p/ZFjUodniDHr
我们在这里开始一些goroutines,等待starter
关闭。因此,您可以在该行之后直接设置您的请求。该函数等待所有请求完成后我们正在使用WaitGroup。
但重要的是:Go只支持并发。因此,如果您的系统没有10个核心,那么10个goroutine将不会并行运行。因此,请确保您有足够的核心可用。
有了这个开始,你可以玩一点。您可以开始在基准测试中调用此函数。你也可以玩goroutines的数量。
正如文档所示,parallel
标志允许并行运行多个不同的测试。您通常不希望并行运行基准测试,因为这会同时运行不同的基准测试,从而抛弃所有基准测试的结果。如果要对并行流量进行基准测试,则需要在测试中编写并行流量生成。你需要决定如何使用b.N
这是你的工作因素;我可能会将它用作总请求数,并编写测试不同并发负载水平的基准或多个基准,例如:
func Benchmark_RealCreate(b *testing.B) {
concurrencyLevels := []int{5, 10, 20, 50}
for _, clients := range concurrencyLevels {
b.Run(fmt.Sprintf("%d_clients", clients), func(b *testing.B) {
sem := make(chan struct{}, clients)
wg := sync.WaitGroup{}
for n := 0; n < b.N; n++ {
wg.Add(1)
go func() {
name := randomdata.SillyName()
r := gofight.New()
u := []unit{unit{MefeUnitID: name, MefeCreatorUserID: "user", BzfeCreatorUserID: 55, ClassificationID: 2, UnitName: name, UnitDescriptionDetails: "Up on the hills and testing"}}
uJSON, _ := json.Marshal(u)
sem <- struct{}{}
r.POST("/create").
SetBody(string(uJSON)).
Run(h.BasicEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {})
<-sem
wg.Done()
}()
}
wg.Wait()
})
}
}
请注意,我删除了最初的ResetTimer
;在调用基准函数之前,计时器不会启动,因此将其作为函数中的第一个操作调用是没有意义的。它适用于您在基准测试循环之前进行耗时设置但不希望包含在基准测试结果中的情况。我也删除了断言,因为这是一个基准,而不是测试;断言用于测试中的有效性检查,仅用于在基准测试中抛出时序结果。
我是新手,但为什么不尝试使用标准并行测试来创建函数并运行它?
func Benchmark_YourFunc(b *testing.B) {
b.RunParralel(func(pb *testing.PB) {
for pb.Next() {
YourFunc(staff ...T)
}
})
}
一件事是基准测试(测量时间码需要运行)另一件事是负载/压力测试。
如上所述的-parallel标志是允许一组测试并行执行,允许测试集执行得更快,而不是并行执行一些测试N次。
但是很容易实现你想要的(执行相同的测试N次)。 Bellow是一个非常简单(非常快速和肮脏)的例子,只是为了澄清/展示重点,这使得这个非常具体的情况得以完成:
要测试的类:
package math
import (
"fmt"
"time"
)
func Average(xs []float64) float64 {
total := float64(0)
for _, x := range xs {
total += x
}
fmt.Printf("Current Unix Time: %v\n", time.Now().Unix())
time.Sleep(10 * time.Second)
fmt.Printf("Current Unix Time: %v\n", time.Now().Unix())
return total / float64(len(xs))
}
测试功能:
package math
import "testing"
func TestAverage(t *testing.T) {
t.Parallel()
var v float64
v = Average([]float64{1,2})
if v != 1.5 {
t.Error("Expected 1.5, got ", v)
}
}
func TestTeardownParallel(t *testing.T) {
// This Run will not return until the parallel tests finish.
t.Run("group", func(t *testing.T) {
t.Run("Test1", TestAverage)
t.Run("Test2", TestAverage)
t.Run("Test3", TestAverage)
})
// <tear-down code>
}
然后做一个go测试你应该看到:
X:\>go test
Current Unix Time: 1556717363
Current Unix Time: 1556717363
Current Unix Time: 1556717363
然后10秒钟
...
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717383
PASS
ok _/X_/y 20.259s
最后两行是因为TestAverage也被执行了。
这里有趣的一点是:如果从TestAverage中删除t.Parallel(),它将全部按顺序执行:
X:> go test
Current Unix Time: 1556717564
Current Unix Time: 1556717574
Current Unix Time: 1556717574
Current Unix Time: 1556717584
Current Unix Time: 1556717584
Current Unix Time: 1556717594
Current Unix Time: 1556717594
Current Unix Time: 1556717604
PASS
ok _/X_/y 40.270s
这当然可以变得更加复杂和可扩展......