我认为在rabbitmq/amqp091-go
connection.go年内:
/*
Channel opens a unique, concurrent server channel to process the bulk of AMQP
messages. Any error from methods on this receiver will render the receiver
invalid and a new Channel should be opened.
*/
func (c *Connection) Channel() (*Channel, error) {
return c.openChannel()
}
但你有:
type Connection interface {
Channel() (Channel, error)
Close() error
}
I would prefer using MyConnection
or MyChannel
, just to avoid any confusion.
And using a dial
function returning (MyConnection, error)
instead of (*amqp.Connection, error)
: That would allow you to use either the real amqp
connection or your mock connection in tests by assigning the dial function to return your mock implementation.
You can then use type assertions within your actual code to handle cases where you need to work with the real amqp091
structs.
For example:
func doSomethingWithChannel(channel MyChannel) {
if realChannel, ok := channel.(*amqp.Channel); ok {
// Do something with realChannel
} else {
// Handle mockChannel
}
}
这允许您通过模拟Connection
和Channel
接口来编写单元测试,同时仍然能够在实际代码中使用真正的amqp091
struct .
然而..这意味着您的测试代码在您的应用程序代码中泄漏,这不是最佳实践.
One way to avoid this is to have your production and test code both implement the same interface, and then in your actual code, you only interact with this interface.
This way, the actual code does not know anything about whether it is dealing with the real implementation or the mock, and you do not have any test code leaking into your actual application.
package main
import (
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
)
// Define the interfaces
type MyChannel interface {
Cancel(consumer string, noWait bool) error
}
type MyConnection interface {
Channel() (MyChannel, error)
Close() error
}
// Consumer struct
type Consumer struct {
conn MyConnection
channel MyChannel
tag string
done chan error
}
func (c *Consumer) Consume(url string) error {
var err error
c.conn, err = dial(url)
if err != nil {
return err
}
c.channel, err = c.conn.Channel()
return err
}
// Use function variable for dial so it can be overridden in tests
var dial = func(url string) (MyConnection, error) {
conn, err := amqp.Dial(url)
if err != nil {
return nil, err
}
return &AMQPConnection{conn}, nil
}
// Wrappers around real amqp types to implement your interfaces
type AMQPConnection struct {
*amqp.Connection
}
func (a *AMQPConnection) Channel() (MyChannel, error) {
ch, err := a.Connection.Channel()
if err != nil {
return nil, err
}
return &AMQPChannel{ch}, nil
}
type AMQPChannel struct {
*amqp.Channel
}
func (a *AMQPChannel) Cancel(consumer string, noWait bool) error {
return a.Channel.Cancel(consumer, noWait)
}
func main() {
c := &Consumer{}
err := c.Consume("amqp://guest:guest@localhost:5672/")
fmt.Println(err)
}
// In a separate _test.go file
type mockConnection struct{}
func (m *mockConnection) Channel() (MyChannel, error) {
return &mockChannel{}, nil
}
func (m *mockConnection) Close() error {
return nil
}
type mockChannel struct{}
func (m *mockChannel) Cancel(consumer string, noWait bool) error {
return nil
}
在测试中,您可以覆盖dial
函数以返回模拟实现:
func TestConsumer(t *testing.T) {
dial = func(url string) (MyConnection, error) {
return &mockConnection{}, nil
}
// rest of your test code
}
That way, your production code does not have any knowledge of the test code, and it interacts only through the interface, which is implemented by both the real and mock versions.
The test code can inject the mock implementation by overriding the dial
function.
要模拟the closeChannel
method进行测试,实际上不需要模拟该特定方法,而是需要模拟通常会调用它的 struct 和方法.
closeChannel
是Connection
struct 的未导出方法,这意味着不能从包外部直接访问它.此方法是amqp091-go
库的内部逻辑的一部分.
但是,您仍然可以通过模拟与该方法交互的公共方法和 struct 来测试依赖于该方法的行为.
在您的测试文件中:
type mockConnection struct {
// ... you can keep state here if needed to simulate connection behavior ...
}
func (m *mockConnection) Channel() (MyChannel, error) {
// Simulate behavior of opening a new channel
return &mockChannel{}, nil
}
func (m *mockConnection) Close() error {
// Simulate behavior of closing the connection, which would internally call closeChannel
return nil
}
type mockChannel struct{}
func (m *mockChannel) Close() error {
// Simulate behavior of closing the channel, which would internally call closeChannel
return nil
}
func TestSomething(t *testing.T) {
// Use the mockConnection in place of the real connection
conn := &mockConnection{}
// Your test logic here, e.g., opening and closing channels, and asserting the expected behavior
}
Your mock implementations simulate the behavior of the real Connection
and Channel
types.
Although this does not let you test the internal closeChannel
method directly, it does allow you to test the behavior that relies on it by using the public interface.
This approach adheres to the best practices of unit testing, where you should test the public interface of a unit and not its internal implementation details.