围棋java
最近有人要求我为一个项目提供帮助,以建议如何实现一些其他事件处理功能。 我浏览了一下源代码,并提出了对现有接口实现的更改,以及其他一些点点滴滴。 一切都很简单,所以我想。 后来,一位开发人员对我说,他宁愿不更改该特定代码段,因为这是至关重要的代码段,而且他不知道如果更改该代码还会破坏什么。 那是当闹钟响起时,这是一个令人担忧的声明。
我认为,其中之一是您应该始终有信心将软件拆开并进行更改,重新开发和修复所需的任何部分。 使您具有这种信心的是单元测试。 如果您修改错误,则测试将失败,并且您会知道自己犯了一个错误并且可以修复问题。 单元测试还意味着,当您继续前进时,其他开发人员也可以放心地将您的软件拆散。
鉴于此陈述,我必须找出正在发生的事情,因此我对软件进行了审查,并发现了这样一个事实,即单元测试不多,实际上某些区域根本没有任何单元测试……但是他们确实有大量的集成测试。 奇怪。 发生了什么事?
他们只是可怜的程序员吗? 他们没有意识到您可以在Go中编写单元测试吗? 开发人员是否太天真了以至于他们认为自己不需要测试?
更深入地研究源代码,这些问题的答案是:
不,他们不是 ,
是的,他们做到了 , 不他们不是 。
正如我在本博客开头所说的那样,我被要求建议添加一些事件处理功能,这是解决问题的关键。 典型的事件处理程序具有以下程序流:
- 收到讯息
- 解析消息
- 用它来读取数据库中的内容
- 做一些业务逻辑
- 再做一些业务逻辑
- 写一些东西到数据库
- 对写入结果进行一些逻辑
- 将其他内容写入数据库
- 整理
…而且他们的事件处理程序是相当典型的问题是他们的代码访问数据库并且没有任何单元测试,因为他们不知道如何模拟数据库连接。 如果您来自像我这样的Java背景,您可能会熟悉各种可用的模拟框架:Mockito,Easymock和Powermock等。在Go世界中,流行的,无处不在的模拟框架实际上并没有可用,尽管它们确实可以存在,许多Go程序员认为它们是不必要的。 这不是因为他们很懒(我希望),而是因为如果您知道怎么做,那么在Go中模拟任何东西就很容易了。
该博客演示了如何在Go事件处理程序中模拟数据库连接,为此,我们需要的第一件事是一些数据库访问代码。
// The real DB Connection details.
type RealConnection struct {
host string // The DB host to connect to
port int32 // The port
URL string // DB access URL / connection string
driver *SomeDriver // a pointer to the underlying 3rd party database driver
// other stuff may go here
}
// This is any old DB read func, you'll have lots of these in your application - probably.
func (r *RealConnection) ReadSomething(arg0, arg1 string) ([]string, error) {
fmt.Printf("This is the real database driver - read args: %s -- %s",arg0,arg1)
return []string{}, nil
}
// This is any old DB insert/ update function.
func (r * RealConnection) WriteSomething(arg0 []string) error {
fmt.Printf("This is the real database driver - write args: %v",arg0)
return nil
}
// Group together the methods in one or more interfaces, gathering them along functionality lines and
// keeping the interface small.
type SomeFunctionalityGroup interface {
ReadSomething(arg0, arg1 string) ([]string, error)
WriteSomething(arg0 []string) error
}
在这里,我假设如果您使用的是第三方Go数据库软件包,那么您会将其包装在自己的数据库访问代码中。 这将阻止第三方代码泄漏到整个应用程序中,并提供标准“ N”层设计的数据库层。
这里要注意的第一件事是dbaccess
代码在RealConnection
结构中为数据库连接RealConnection
。 在现实世界中,其中可能包含数据库主机名和其他连接详细信息。
真正的数据库包需要做的下一件事是一些真正的数据库访问方法。 在这里,我将RealConnection
用作接收器。
最后要注意的是,我遵循Go推荐的偏爱小型接口的建议,将相关的数据库访问方法收集到一个接口中。 这是SomeFunctionalityGroup
接口。
创建数据库之后,我们接下来需要的是事件处理程序。
// The event handler struct. Models event attributes
type EventHandler struct {
name string // The name of the event
actor dbaccess.SomeFunctionalityGroup // The interface for our dbaccess fucntions
}
// This creates a event handler instance, using whatever name an actor are passed in.
func NewEventHandler(actor dbaccess.SomeFunctionalityGroup, name string) EventHandler {
return EventHandler{
name: name,
actor: actor,
}
}
// This is a sample event handler - it reads from the DB does some imaginary business logic and writes the results back
// to the DB.
func (eh *EventHandler) HandleSomeEvent(action string) error {
fmt.Printf("Handling event: %s\n", action)
value, err := eh.actor.ReadSomething(action, "arg1")
if err != nil {
fmt.Printf("Use the logger to log your error here. The read error is: %+v\n", err)
return err
}
// Do some business logic here
if len(value) == 2 && value[0] == "Hello" {
value[1] = "World"
}
// Now write the result back to the database
err = eh.actor.WriteSomething(value)
if err != nil {
fmt.Printf("Use the logger to log your error here. The write error is: %+v\n", err)
}
return err
}
我创建了一个EventHandler
结构和一个NewEventHandler
函数,因为我假设代码将处理不同数量的事件流,并且我们需要为每个事件处理程序。 需要注意的关键点是,我的EventHandler
结构以actor
属性的形式引用了SomeFunctionalityGroup
接口。 如果这是单个事件处理程序,则还可以使用一个简单的全局函数,只要它可以访问SomeFunctionalityGroup
实现即可。
type EventHandler struct {
name string // The name of the event
actor dbaccess.SomeFunctionalityGroup // The interface for our dbaccess fucntions
}
事件处理程序本身读取数据库,执行一些业务逻辑并写入数据库,而这些读取和写入正是我们需要模拟的,我们通过创建MockConnection
结构来做到这一点。
type MockConnection struct {
fail bool // Set this to true to mimic a DB read / write failure
}
它可能包含一些属性,这些属性可能会迫使您的单元测试失败并导致程序流程失败。 在这个简单的示例中,我们具有fail
布尔值。
下一步是使用MockConnection
实现SomeFunctionalityGroup
接口:
// This is the mock database read function
func (r *MockConnection) ReadSomething(arg0, arg1 string) ([]string, error) {
fmt.Printf("This is the MOCK database driver - read args: %s -- %s\n",arg0,arg1)
if r.fail {
fmt.Println("Whoops - there's been a database write error")
return []string{}, dummyError
}
return []string{"Hello", ""}, nil
}
// This is mock database write function
func (r * MockConnection) WriteSomething(arg0 []string) error {
fmt.Printf("This is the MOCK database driver - write args: %v\n",arg0)
if r.fail {
fmt.Println("Whoops - there's been a database write error")
return dummyError
}
return nil
}
从这里开始,我们可以创建许多单元测试:
// Test calling the event handler with a dummy database connection.
func TestEventHandlerDB_happy_flow(t *testing.T) {
testCon := MockConnection{
fail: false,
}
eh := NewEventHandler(&testCon,"Happy")
err := eh.HandleSomeEvent("Action")
if err != nil {
t.Errorf("Failed - with error: %+v\n", err)
}
}
// Test calling the event handler with a dummy database connection, for the failure flow.
func TestEventHandlerDB_fail_flow(t *testing.T) {
testCon := MockConnection{
fail: true,
}
eh := NewEventHandler(&testCon,"Fail")
err := eh.HandleSomeEvent("Action 2")
if err == nil {
t.Errorf("Failed - with error: %+v\n", err)
}
}
测试所需要做的就是创建一个MockConnection
,然后实例化一个EventHandler
然后调用HandleSomeEvent(...)
并检查其结果。
这个想法非常灵活:您可以添加检查,例如,验证使用给定次数的正确参数调用方法,或者还可以设置
MockConnection
结构,以便为您的模拟数据库ReadSomething(...)
和WriteSomething(...)
方法提供返回值。 一个更完整的结构可能看起来像:
// A MockConnection mimics a real database connection - but allows us to mock the connection and follow happy and fail code paths
type MockConnection struct {
readFail bool // Set this to true to mimic a DB read failure
writeFail bool // Set this to true to mimic a DB read failure
readReturn []string // The return from the ReadSomething(...) method
readCallCount int32 // The number of database reads expected
writeError error // A specific write error type expected.
}
一旦您知道如何进行设置,并且比设置等效的集成测试容易得多,那么进行设置就非常简单。 对我来说,无论使用哪种语言,创建可靠的,可测试的应用程序的规则都是相同的。 如果您对一般测试感兴趣,那么还有很多其他博客涉及此主题,包括
首字母缩写词 , 为什么要编写单元测试等。
该博客中使用的源代码可以在Github上找到:
https://github.com/roghughe/gsamples/tree/master/mockingsample
翻译自: https://www.javacodegeeks.com/2017/10/mocking-in-go.html
围棋java