当前位置:网站首页>Go unit test mocks the database CRUD

Go unit test mocks the database CRUD

2022-06-21 21:47:00 1024 Q

Catalog

Preface

go-sqlmock

install

Examples of use

miniredis

install

Examples of use

summary

Preface

Recently, in practice, I have also summarized how to use... In a table driven way gock Mock Test external interface calls . And how GORM do mock test , After learning the basics , Later, I will write a separate article to introduce .

This is a Go Part of the language unit testing series 3 piece , How to use go-sqlmock and miniredis Tool execution MySQL and Redis Of mock test .

In the last article 《Go unit testing -- Simulate service requests and interface returns 》 in , We introduced how to use httptest and gock Tools for network testing .

In addition to network dependence , We often use various databases in our development , For example, the common MySQL and Redis etc. . This article will give examples to demonstrate how to write unit tests for MySQL and Redis Conduct mock.

go-sqlmock

sqlmock Is an implementation  sql/driver  Of mock library . It does not need to establish a real database connection to simulate any sql Driver behavior . It is very convenient to use it when writing unit tests mock sql Statement execution result .

install go get github.com/DATA-DOG/go-sqlmock Examples of use

What we use here is go-sqlmock The basic sample code provided in the official document . In the following code , We have achieved a recordStats The function is used to record the relevant data generated when a user browses a product . The specific function is to perform the following two transactions in a transaction SQL operation :

The number of views of the current product in the table +1

stay product_viewers The user browsing the current product is recorded in the table id

// app.gopackage mainimport "database/sql"// recordStats  Record user browsing product information func recordStats(db *sql.DB, userID, productID int64) (err error) { //  Open transaction  //  operation views and product_viewers Two tables  tx, err := db.Begin() if err != nil {  return } defer func() {  switch err {  case nil:   err = tx.Commit()  default:   tx.Rollback()  } }() //  to update products surface  if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil {  return } // product_viewers Insert a piece of data into the table  if _, err = tx.Exec(  "INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)",  userID, productID); err != nil {  return } return}func main() { //  Be careful : There is no need for a real connection in the process of testing  db, err := sql.Open("mysql", "[email protected]/blog") if err != nil {  panic(err) } defer db.Close() // userID by 1 The user browsed productID by 5 Products  if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil {  panic(err) }}

Now we need to create a code for recordStats Function writing unit test , But I don't want to connect to the real database to test during the test . At this time, we can use it as in the following example code sqlmock Tool go mock Database operation .

package mainimport ( "fmt" "testing" "github.com/DATA-DOG/go-sqlmock")// TestShouldUpdateStats sql Execute successful test cases func TestShouldUpdateStats(t *testing.T) { // mock One *sql.DB object , There is no need to connect to the real database  db, mock, err := sqlmock.New() if err != nil {  t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // mock Execute assignment SQL The return result of statement  mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() //  take mock Of DB Object passed into our function  if err = recordStats(db, 2, 3); err != nil {  t.Errorf("error was not expected while updating stats: %s", err) } //  Ensure that the desired results are met  if err := mock.ExpectationsWereMet(); err != nil {  t.Errorf("there were unfulfilled expectations: %s", err) }}// TestShouldRollbackStatUpdatesOnFailure sql Execute the test case of failed rollback func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) { db, mock, err := sqlmock.New() if err != nil {  t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers").  WithArgs(2, 3).  WillReturnError(fmt.Errorf("some error")) mock.ExpectRollback() // now we execute our method if err = recordStats(db, 2, 3); err == nil {  t.Errorf("was expecting an error, but there was none") } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil {  t.Errorf("there were unfulfilled expectations: %s", err) }}

In the above code , A successful test case and a failed rollback test case are defined , Make sure that every logical branch in our code can be tested , It improves the coverage of unit tests while ensuring the robustness of the code .

Perform unit tests , Take a look at the final test results .

* go test -v
=== RUN   TestShouldUpdateStats
--- PASS: TestShouldUpdateStats (0.00s)
=== RUN   TestShouldRollbackStatUpdatesOnFailure
--- PASS: TestShouldRollbackStatUpdatesOnFailure (0.00s)
PASS
ok      golang-unit-test-demo/sqlmock_demo      0.011s

You can see that the results of both test cases are in line with expectations , Unit test passed .

In many applications ORM Tool scenario , You can also use go-sqlmock library mock Test the database operation .

miniredis

Except that it is often used MySQL Outside ,Redis It is often used in daily development . The next section , We'll learn how to do it in unit tests mock Redis Related operations of .

miniredis It's pure. go Implemented for unit testing redis server. It's an easy to use 、 Memory based redis succedaneum , It has real TCP Interface , You can think of it as redis Version of net/http/httptest.

When we include for some Redis Operation code can be used to write unit tests mock Redis operation .

install go get github.com/alicebob/miniredis/v2 Examples of use

Here we use github.com/go-redis/redis Library as an example , Write a program that contains several Redis Operation of the DoSomethingWithRedis function .

// redis_op.gopackage miniredis_demoimport ( "context" "github.com/go-redis/redis/v8" //  Note the import version  "strings" "time")const ( KeyValidWebsite = "app:valid:website:list")func DoSomethingWithRedis(rdb *redis.Client, key string) bool { //  This can be right redis Some logic of operation  ctx := context.TODO() if !rdb.SIsMember(ctx, KeyValidWebsite, key).Val() {  return false } val, err := rdb.Get(ctx, key).Result() if err != nil {  return false } if !strings.HasPrefix(val, "https://") {  val = "https://" + val } //  Set up  blog key  Five seconds expired  if err := rdb.Set(ctx, "blog", val, 5*time.Second).Err(); err != nil {  return false } return true}

The following code is what I use miniredis The library is DoSomethingWithRedis Function to write unit test code , among miniredis Not only support mock frequently-used Redis operation , It also provides many useful help functions , For example, inspection key Whether the value of is equal to the expected value s.CheckGet() And help check key Overdue s.FastForward().

// redis_op_test.gopackage miniredis_demoimport ( "github.com/alicebob/miniredis/v2" "github.com/go-redis/redis/v8" "testing" "time")func TestDoSomethingWithRedis(t *testing.T) { // mock One redis server s, err := miniredis.Run() if err != nil {  panic(err) } defer s.Close() //  Prepare the data  s.Set("q1mi", "liwenzhou.com") s.SAdd(KeyValidWebsite, "q1mi") //  Connect mock Of redis server rdb := redis.NewClient(&redis.Options{  Addr: s.Addr(), // mock redis server The address of  }) //  Call function  ok := DoSomethingWithRedis(rdb, "q1mi") if !ok {  t.Fatal() } //  It can be checked manually redis Whether the value in is compound with the expected value  if got, err := s.Get("blog"); err != nil || got != "https://liwenzhou.com" {  t.Fatalf("'blog' has the wrong value") } //  You can also use the help tool to check  s.CheckGet(t, "blog", "https://liwenzhou.com") //  Overdue inspection  s.FastForward(5 * time.Second) //  Fast forward 5 second  if s.Exists("blog") {  t.Fatal("'blog' should not have existed anymore") }}

Execute execute test , Look at the unit test results :

* go test -v
=== RUN   ;TestDoSomethingWithRedis
--- PASS: TestDoSomethingWithRedis (0.00s)
PASS
ok      golang-unit-test-demo/miniredis_demo    0.052s

miniredis Basically, it supports the vast majority of Redis command , You can learn more about the usage by checking the documentation .

Except for using miniredis Build local redis server In addition to this method , Various pile driving tools can also be used to drive piles for specific methods . What kind of mock The way should be decided according to the actual situation .

summary

How to deal with the dependency of database is the most common problem when writing unit tests for code in daily work development , This article explains how to use it go-sqlmock and miniredis Tools mock Related dependencies .

Next , We will go further , Describes in detail how to write unit tests mock Interface implementation , More about Go database CRUD Mock For testing information, please pay attention to other relevant articles on the software development network !


原网站

版权声明
本文为[1024 Q]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/172/202206211951268425.html