I am writing a Go package that handles communication with BoltDB as a part of some larger project. I have 2 methods for interacting with DB.
func Lookup(path string) (string, bool) {}
func RegisterUrl(path, url string) error {}
Lookup makes a request to database to retrieve corresponding url, whereas RegisterUrl writes path:url key-value pair to DB.
I would like to ask for a review of my implementation and some advice on how to implement unit tests.
Unit Testing
I've included the skeleton for unit tests that I would like to have for above 2 functions. However I have trouble figuring what is the best way to implement them.
- I wouldn't like to test methods using the "prod" database/bucket, rather then that I would prefer to swap prod DB to test DB in the
database_test.go
file. - I thought about making dbName and bucketName public variables in
database.go
and then change them in _test file for test db, but this seems like I would be breaking the encapsulation. - I also considered passing test database/bucket to functions as functional options, but this seems like it will make code more complicated and I am not sure if this is the best approach.
I would be happy to hear your advice.
database.go
// Package database handles communication with BoltDB.
package database
import (
"log"
"time"
"github.com/boltdb/bolt"
)
var dbOptions *bolt.Options = &bolt.Options{
Timeout: 1 * time.Second,
}
const (
dbName = "urls.db"
bucketName = "UrlFromPath"
)
func init() {
// Make sure that the database exists.
db, err := bolt.Open(dbName, 0600, dbOptions)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Make sure that UrlFromPath bucket exists.
err = db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
if err != nil {
return err
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
// Lookup checks if `path` key exists in database and returns related `url` value if found. If not
// found second return value `ok` is set to false.
func Lookup(path string) (url string, ok bool) { // TODO: return
// Open connection to DB.
db, err := bolt.Open(dbName, 0600, dbOptions)
if err != nil {
log.Fatalf("Cannot connect to DB: %s", err)
return "", false
}
defer db.Close()
// Retreive url from DB.
var url_bytes []byte
err = db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketName))
url_bytes = b.Get([]byte(path))
return nil
})
if err != nil {
return "", false
}
if url_bytes != nil {
return string(url_bytes), true
} else {
return "", false
}
}
// RegisterUrl saves provided key:value (path:url) pair to database.
func RegisterUrl(path, url string) error {
// Open connection to DB.
db, err := bolt.Open(dbName, 0600, dbOptions)
if err != nil {
return err
}
defer db.Close()
// Save path and url.
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketName))
err := b.Put([]byte(path), []byte(url))
return err
})
if err != nil {
return err
}
log.Printf("Database: %s, %s registered.", path, url)
return nil
}
database_test.go
package database
import "testing"
func setup() {
// Init test db.
// Init test bucket.
}
func cleanup() {
// Remove test db.
}
func TestLookup(t *testing.T) {
// T1. Path exists in db. Correct url is retrieved.
// T2. Path doesn't exist in db. false is returned.
}
func TestRegisterUrl(t *testing.T) {
// T1. Add path:url to db. Correct path:url pair has been added to db.
}
```
1 Answer 1
I think the code could benefit a struct for the database instead of global state. You can use a New method instead of init() where you pass in the params, which would differet from test and production.
I also demonstrated that you can keep the connection open and reuse it within the struct. If you prefer a lazy approach, you can omit this and create a the connection in each call if you want.
type Database struct {
bucketName string
boltDB *bolt.DB
}
func New(dbName, bucketName string, opts *bolt.Options) (db *Database, error) {
db, err := bolt.Open(dbName, 0600, opts)
return &Database{
boltDb: db,
bucketName: bucketName
}, nil
}
func(db *Database) Lookup(path string) (url string, ok bool) { // lookup logic }
func(db *Database) RegisterUrl(path, url string) error { //register logic}
```