<p><strong>TLDR</strong>: What's the best way of unit-testing a function that uses a database as its datasource but that the datasource does not have to be a database (that is, the function isn't aware of where the data is coming from. e.g. it can be an object, etc.)?</p>
<p>=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=</p>
<p>I'm trying to find out what's the best practice for unit-testing functions that use a database as its datasource - which is passed through its parameter - but that the datasource does not have to be a database (e.g. it can be an object, etc.). The reason I add the clause, "that the datasource does not have to be a database," is because I don't want to use a database during unit-tests. So, how do I write a function that is database-agnostic?</p>
<p>One possible approach is to provide an "env" parameter that holds the datasource, like so (GoLang):</p>
<pre><code>type Env struct {
DataSource interface{}
}
func FunctionToTest(env Env) {
switch et := (env.DataSource).(type) {
case UserDatasource:
userSource := (env.DataSource).(UserDatasource)
user := userSource.getUser()
default:
// Throw error
}
}
func FunctionToTest2(env Env) {
switch et := (env.DataSource).(type) {
case CredentialsDatasource:
credentialSource := (env.DataSource).(CredentialsDatasource)
password := credentialSource.getPassword()
default:
// Throw error
}
}
</code></pre>
<p>The issue with this is that it seems "hacky" and it feels like there exists a better solution. The reason I want to learn how to do this is so that I can write unit-tests that mock the database.</p>
<p>I appreciate any and all input.</p>
<p>Thanks in advance!</p>
<hr/>**评论:**<br/><br/>jasrags: <pre><p>Why not just write an actual interface for your db functions then you can pass in a mock/dummy version in your tests?</p></pre>softwaregav: <pre><p>You would want to use an interface here. A common pattern used to abstract away where the data comes from is the repository pattern. <a href="http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/" rel="nofollow">Here</a> is an article where you can read about the repository pattern and other tips for developing well-written Go applications.</p>
<pre><code>package user
type User struct {
ID int64
Name string
}
type Repository interface {
Find(id int64) (*User, error)
Store(u *User) error
}
func ChangeUserName(id int64, name string, r Repository) error {
u, err := r.Find(id)
if err != nil {
return err
}
u.Name = name
err = r.Store(u)
return err
}
</code></pre>
<p>Now you are able to pass in any struct with the methods matching your <code>user.Repository</code> interface. For example:</p>
<pre><code>package mysql
type DB struct {
*sql.DB
}
func New(db *sql.DB) *DB {
return DB{db}
}
func (d *DB) Find(id int64) (*user.User, error) {
// mysql stuff to find user
}
func (d *DB) Store(u *user.User) error {
// mysql stuff to store user
}
</code></pre>
<p>then ...</p>
<pre><code>package main
func main() {
// ...
// conn := code to open *sql.DB connection
db := mysql.New(conn)
err := user.ChangeUserName(1, 'bob', db)
// ...
}
</code></pre>
<p>You can now also use a mock to test your function.</p>
<pre><code>package mocks
type UserRepository struct {
Users []*user.User
ShouldError bool
}
func (r *UserRepository) Find(id int64) (*user.User, error) {
if r.ShouldError {
return nil, errors.New("")
}
for _, u := range r.Users {
if u.ID == id {
return u
}
}
return errors.New("user not found")
}
func (r *UserRepository) Store(u *user.User) error {
if r.ShouldError {
return errors.New("")
}
r.Users = append(r.Users, u)
return nil
}
</code></pre>
<p>then to test...</p>
<pre><code>func Test_ChangeUserName_Stores_Changed_User(t *testing.T) {
u := &user.User{ID: 1, Name: 'Bob'}
r := mocks.UserRepository{Users: []*user.User{u}}
err := user.ChangeUserName(1, 'Fred', r)
// ...
}
</code></pre></pre>ChristophBerger: <pre><p>The blog post you referenced to actually inspired me to do a simplified writeup of the basics of <a href="https://appliedgo.net/di" rel="nofollow">using dependency injection in Go</a> a while ago. It is basically what you laid out in the code above - an interface to define operations, a „real“ package and a „mock“ package, and the required injection happening in main() or in the test function, respectively. Bam, decoupling done.</p></pre>
How to Write Database-Agnostic Functions in GoLang to Make Them Easier to Unit-Test?
blov · · 484 次点击这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传