我正在尝试在 Go 中为现有服务实现单元测试,该服务使用连接池结构和来自现有库的连接结构(调用这些 LibraryPool 和 LibraryConnection) 连接到外部服务。
为了使用这些,主代码中的服务函数使用池的一个唯一的全局实例,它有一个 GetConnection() 方法,如下所示:
// Current Main Code
var pool LibraryPool // global, instantiated in main()
func someServiceFunction(w http.ResponseWriter, r *http.Request) {
// read request
// ...
conn := pool.GetConnection()
conn.Do("some command")
// write response
// ...
}
func main() {
pool := makePool() // builds and returns a LibraryPool
// sets up endpoints that use the service functions as handlers
// ...
}
我想在不连接到外部服务的情况下对这些服务功能进行单元测试,因此我想模拟 LibraryPool 和 LibraryConnection。为此,我正在考虑将主要代码更改为如下内容:
// Tentative New Main Code
type poolInterface interface {
GetConnection() connInterface
}
type connInterface interface {
Do(command string)
}
var pool poolInterface
func someServiceFunction(w http.ResponseWriter, r *http.Request) {
// read request
// ...
conn := pool.GetConnection()
conn.Do("some command")
// write response
// ...
}
func main() {
pool := makePool() // still builds a LibraryPool
}
在测试中,我将使用这些接口(interface)的模拟实现 MockPool 和 MockConnection,全局 pool 变量将使用 模拟池。我将在 TestMain() 内的 setup() 函数中实例化这个全局 pool功能。
问题是在新的主代码中,LibraryPool 没有正确实现poolInterface,因为GetConnection() 返回 connInterface 而不是 LibraryConnection(即使 LibraryConnection 是 connInterface 的有效实现)。
进行此类测试的好方法是什么?顺便说一句,主要代码也很灵活。
最佳答案
好吧,我会尝试通过完整解释我如何看待这个设计来回答。如果这太多而不是重点,请提前道歉..
例如,假设我们想做一些像将人插入数据库一样简单的事情。
包 person 将只包含 person 结构
package person
type Person struct{
name string
}
func New(name string) Person {
return Person{
name: name,
{
}
关于数据库,假设你使用sql,我建议制作一个名为sql的包来处理repo。 (如果你使用 postgress,请使用'postgress package ...)。
personRepo 将获取将在 main 中初始化的 dbConnection 并实现 DBAndler。只有连接会直接与数据库“对话”,存储库的主要目标是成为数据库的网关,并以应用程序的方式说话。 (连接与应用无关)
package sql
type DBAndler interface{
exec(string, ...interface{}) (int64, error)
}
type personRepo struct{
dbHandler DBHandler
}
func NewPersonRepo(dbHandler DBHandler) &personRepo {
return &personRepo{
dbHandler: dbHandler,
}
}
func (p *personRepo) InsertPerson(p person.Person) (int64, error) {
return p.dbHandler.Exec("command to insert person", p)
}
服务将在初始化程序中将此存储库作为依赖项(作为接口(interface))获取,并将与其交互以完成业务逻辑
package service
type PersonRepo interface{
InsertPerson(person.Person) error
}
type service struct {
repo PersonRepo
}
func New(repo PersonRepo) *service {
return &service{
repo: repo
}
}
func (s *service) AddPerson(name string) (int64, error) {
person := person.New(name)
return s.repo.InsertPerson(person)
}
您的传输处理程序将使用作为依赖项的服务进行初始化,并且他将处理 http 请求。
package http
type Service interface{
AddPerson(name string) (int64, error)
}
type handler struct{
service Service
}
func NewHandler(s Service) *handler {
return &handler{
service: s,
}
}
func (h *handler) HandleHTTP(w http.ResponseWriter, r *http.Request) {
// read request
// decode name
id, err := h.service.AddPerson(name)
// write response
// ...
}
在 main.go 中你会把所有东西联系在一起:
主要包
func main() {
pool := makePool()
conn := pool.GetConnection()
// repo
personRepo := sql.NewPersonRepo(conn)
// service
personService := service.New(personRepo)
// handler
personHandler := http.NewPersonHandler(personService)
// Do the rest of the stuff, init the http engine/router by passing this handler.
}
请注意,每个包结构都使用 interface 初始化,但返回一个 struct,并且接口(interface)是在使用它们的包中声明的,而不是在使用它们的包中声明的实现它们。
这使得对这些包进行单元测试变得容易。例如,如果您想测试服务,则无需担心 http 请求,只需使用一些实现服务所依赖的接口(interface)(PersonRepo)的“模拟”结构,就可以了。
好吧,我希望它对你有一点帮助,一开始它可能看起来很困惑,但你很快就会发现这看起来像是一大段代码,但当你需要添加功能或切换db 驱动程序等。我建议您阅读 go 中的域驱动设计,以及六角拱。
编辑:
此外,通过这种方式您传递到服务的连接,服务不会导入和使用全局数据库池。老实说,我不知道为什么它如此普遍,我想它有它的优点并且对某些应用程序更好,但一般来说我认为让你的服务依赖于某个接口(interface),而不真正知道发生了什么,是很多更好的做法。
关于unit-testing - 当A的方法在Go中返回B时模拟对象A和B,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53769424/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案