头条创作挑战赛 在做集成测试的时候,每次测试前,如果通过docker重启一个干净的容器是不是免去了数据清理的苦恼。https:github。comtestcontainerstestcontainersgo和https:github。comorydockertest可以解决我们的苦恼,它们很相似都是调用docker的api实现镜像的拉取和容器的启动关闭。然后我们可以基于容器做对应的集成测试。 由于每次拉取镜像和启动docker代价比较大,比较耗时,我们一般在单测的入口TestMain方法里做初始化,也就是一个模块进行一次容器初始化。由于单测case之间没有数据的清理,因此我们每个单测结束后都需要注意清理和还原数据。整体来说dockertesttestcontainersgo原理和使用方法比较类似。下面我们体验一下用法,首先我们需要启动dockerdockerversionVersion:20。10。12 dockertestpackagedockertesttestimport(databasesqlfmtlogostestinggithub。comgosqldrivermysqlgithub。comorydockertestv3)vardbsql。DBfuncTestMain(mtesting。M){usesasensibledefaultonwindows(tcphttp)andlinuxosx(socket)pool,err:dockertest。NewPool()iferr!nil{log。Fatalf(Couldnotconstructpool:s,err)}usespooltotrytoconnecttoDockererrpool。Client。Ping()iferr!nil{log。Fatalf(CouldnotconnecttoDocker:s,err)}pullsanimage,createsacontainerbasedonitandrunsitresource,err:pool。Run(mysql,5。7,〔〕string{MYSQLROOTPASSWORDsecret})iferr!nil{log。Fatalf(Couldnotstartresource:s,err)}exponentialbackoffretry,becausetheapplicationinthecontainermightnotbereadytoacceptconnectionsyetiferr:pool。Retry(func()error{varerrerrordb,errsql。Open(mysql,fmt。Sprintf(root:secret(localhost:s)mysql,resource。GetPort(3306tcp)))iferr!nil{returnerr}returndb。Ping()});err!nil{log。Fatalf(Couldnotconnecttodatabase:s,err)}code:m。Run()Youcantdeferthisbecauseos。Exitdoesntcarefordeferiferr:pool。Purge(resource);err!nil{log。Fatalf(Couldnotpurgeresource:s,err)}os。Exit(code)}funcTestSomething(ttesting。T){varCREATETABLECREATETABLEstudent(sidINT(10)NOTNULLAUTOINCREMENT,snameVARCHAR(64)NULLDEFAULTNULL,ageINT(10)DEFAULTNULL,PRIMARYKEY(sid))ENGINEInnoDBDEFAULTCHARSETutf8;varINSERTDATAINSERTINTOstudent(sid,sname,age)VALUES(?,?,?);varQUERYDATASELECTFROMstudent;db。Query(createdatabasetest;)db。Query(usetest;),err:db。Exec(CREATETABLE)fmt。Println(err)db。Exec(INSERTDATA,1,唐僧,30)查询数据rows,err:db。Query(QUERYDATA)iferr!nil{fmt。Println(err)}forrows。Next(){varnamestringvaridintvarageintiferr:rows。Scan(id,name,age);err!nil{fmt。Println(err)}fmt。Printf(sisd,name,age)}} testcontainersgopackageexp1import(contextfmttestingtimegithub。comgoredisredisv8github。comgoogleuuidgithub。comstretchrtestifyrequiregithub。comtestcontainerstestcontainersgogithub。comtestcontainerstestcontainersgowait)typeredisContainerstruct{testcontainers。ContainerURIstring}funcsetupRedis(ctxcontext。Context)(redisContainer,error){req:testcontainers。ContainerRequest{Image:redis:6,ExposedPorts:〔〕string{6379tcp},WaitingFor:wait。ForLog(Readytoacceptconnections),}container,err:testcontainers。GenericContainer(ctx,testcontainers。GenericContainerRequest{ContainerRequest:req,Started:true,})iferr!nil{returnnil,err}mappedPort,err:container。MappedPort(ctx,6379)iferr!nil{returnnil,err}hostIP,err:container。Host(ctx)iferr!nil{returnnil,err}uri:fmt。Sprintf(redis:s:s,hostIP,mappedPort。Port())returnredisContainer{Container:container,URI:uri},nil}funcTestIntegrationSetGet(ttesting。T){iftesting。Short(){t。Skip(Skippingintegrationtest)}ctx:context。Background()redisContainer,err:setupRedis(ctx)iferr!nil{t。Fatal(err)}t。Cleanup(func(){iferr:redisContainer。Terminate(ctx);err!nil{t。Fatalf(failedtoterminatecontainer:s,err)}})YouwilllikelywanttowrapyourRedispackageofchoiceinaninterfacetoaidinunittestingandlimitlockinthroughtoutyourcodebasebutthatsoutofscopeforthisexampleoptions,err:redis。ParseURL(redisContainer。URI)iferr!nil{t。Fatal(err)}client:redis。NewClient(options)deferflushRedis(ctx,client)t。Log(pingingredis)pong,err:client。Ping(ctx)。Result()require。NoError(t,err)t。Log(receivedresponsefromredis)ifpong!PONG{t。Fatalf(receivedunexpectedresponsefromredis:s,pong)}Setdatakey:fmt。Sprintf({user。s}。favoritefood,uuid。NewString())value:CabbageBiscuitsttl,:time。ParseDuration(2h)errclient。Set(ctx,key,value,ttl)。Err()iferr!nil{t。Fatal(err)}GetdatasavedValue,err:client。Get(ctx,key)。Result()iferr!nil{t。Fatal(err)}ifsavedValue!value{t。Fatalf(Expectedvalues。Gots。,savedValue,value)}fmt。Println(key,savedValue)}funcflushRedis(ctxcontext。Context,clientredis。Client)error{returnclient。FlushAll(ctx)。Err()} 两个包中的例子都列举了常用的中间件的用法,可以参考下https:golang。testcontainers。orgexamplesredishttps:github。comorydockertesttreev3examples