Aug 18, 2018

[Go] code refactor for unit-test

Due to the fast layout of prototype, the init. engineer(i.e tactical tornado style) who wrote the code didn't have unit testing in mind(in golang the idiom's bit different), the team will refactor the code a bit in order to fill the holes of unit tests.

Before refactoring, there are two ground baseline to be considered.
1. minimum change of the code thus to support interface unit test.
2. honor golang interface idiom.

Here are some good examples we found in etcd source code for unit test layout :-)

https://github.com/coreos/etcd/blob/master/etcdserver/raft.go
https://github.com/coreos/etcd/blob/master/etcdserver/raft_test.go

We adapted embedded interface and CRTP style for grouping functions into interface as the minimum element of unit-testing.

e.g code:
https://github.com/buddhavs/golang_unit_test_example

example.go
package main

import (
 "errors"
 "fmt"
)

type (
 // Service ...
 Service struct {
  Mq
  Storage
  Status
 }

 // Mq ...
 Mq interface {
  process(bool) error
 }

 // Storage ...
 Storage interface {
  snapshot() error
  backup() error
 }

 // Status ...
 Status interface {
  update()
  updatedb()
  check()
 }

 service struct {
  s *Service
 }

 mq struct {
  service
 }

 storage struct {
  service
 }

 status struct {
  service
 }
)

// NewService ...
func NewService() *Service {
 mq := &mq{}
 storage := &storage{}
 status := &status{}

 service := Service{
  Mq:      mq,
  Storage: storage,
  Status:  status}

 mq.s = &service
 storage.s = &service
 status.s = &service

 return &service
}

func (p *mq) process(b bool) error {
 fmt.Println("Processing...")

 err := p.s.backup()
 if err != nil {
  fmt.Println("Backup error...")
 }

 err = p.s.snapshot()
 if err != nil {
  fmt.Println("Snapshot error...")
 }

 switch b {
 case true:
  return nil
 default:
  return errors.New("Process error")
 }
}

func (p *storage) backup() error {
 fmt.Println("Backing up...")
 p.s.update()
 p.s.updatedb()
 p.s.check()
 return nil
}

func (p *storage) snapshot() error {
 fmt.Println("Snap shotting XD...")
 return nil
}

func (p *status) update() {
 fmt.Println("Updaing status...")
}

func (p *status) updatedb() {
 fmt.Println("Updaing Db...")
}

func (p *status) check() {
 fmt.Println("Checking status...")
}

func main() {
 service := NewService()
 service.process(true)
}

example_test.go
package main

import "testing"

// NewMockService ...
func NewMockService() *Service {
 mq := &mq{}
 storage := &mockStorage{}
 status := &mockStatus{}

 service := Service{
  Mq:      mq,
  Storage: storage,
  Status:  status}

 mq.s = &service
 storage.s = &service
 status.s = &service

 return &service
}

type (
 mockStorage struct {
  service
 }

 mockStatus struct {
  service
 }
)

func (p *mockStorage) backup() error {
 return nil
}

func (p *mockStorage) snapshot() error {
 return nil
}

func (p *mockStatus) update() {
}

func (p *mockStatus) updatedb() {
}

func (p *mockStatus) check() {
}

func TestStatusExpectFailed(t *testing.T) {
 service := NewMockService()
 err := service.process(false)
 if err != nil {
  t.Error(err)
 }
}

func TestStatus(t *testing.T) {
 service := NewMockService()
 err := service.process(true)
 if err != nil {
  t.Error(err)
 }
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.