Golang Code Examples

Embedded Adapter and Testify

12 Aug 2014

Description

This example demonstrates how to use the testify golang testing package to test an embedded adapter pattern.

We import testify/assert as well as testify/suite.

We use the AnimalAdapterTestSuite to define some "global" variables that are used in each our our tests.

SetupTest assigns values to the AnimalAdapterTestSuite struct.

TestAnimals uses assert.Equal to verify that our Animal objects are working properly.

TestMultiAnimals tests the embedded adapter pattern and use of polymorphism.


Golang Features

This golang code sample demonstrates the following go language features:

  • iterating ranges
  • pointers
  • structs
  • methods
  • slice of interfaces


3rd Party Libraries

We use testify a Go code (golang) set of packages that provide many tools for testifying that your code will behave as you intend.

  • testify

Code Example

animals_test.go

package animals

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/suite"
)

// Define the suite, and absorb the built-in basic suite functionality from testify
// - including a T() method which returns the current testing context
type AnimalAdapterTestSuite struct {
    suite.Suite
    HumanObj *Human
    ClownfishObj *ClownFish
    SharkObj *Shark
    BirdObj *Bird
}

// Make sure that AnimalAdapterTestSuite struct data is set before each test
func (suite *AnimalAdapterTestSuite) SetupTest() {
    suite.HumanObj = new(Human)
    suite.HumanObj.Name = "Dr. Philip Sherman"
    suite.ClownfishObj = new(ClownFish)
    suite.ClownfishObj.Name = "Nemo"
    suite.SharkObj = new(Shark)
    suite.SharkObj.Name = "Bruce"
    suite.BirdObj = new(Bird)
    suite.BirdObj.Name = "Nigel"
}

// In order for 'go test' to run this suite, we need to create a normal test function and pass our suite to suite.Run
func TestAnimalAdapterTestSuite(t *testing.T) {
    suite.Run(t, new(AnimalAdapterTestSuite))
}

// All methods that begin with "Test" are run as tests within a suite.
func (suite *AnimalAdapterTestSuite) TestAnimals() {

    assert.Equal(suite.T(), suite.HumanObj.Name, "Dr. Philip Sherman")
    suite.HumanObj.Move()

    assert.Equal(suite.T(), suite.ClownfishObj.Name, "Nemo")
    suite.ClownfishObj.Swim()

    assert.Equal(suite.T(), suite.SharkObj.Name, "Bruce")
    suite.SharkObj.Swim()

    assert.Equal(suite.T(), suite.BirdObj.Name, "Nigel")
    suite.BirdObj.Fly()
}

// Test the embedded adapter pattern and use of polymorphism
func (suite *AnimalAdapterTestSuite) TestMultiAnimals() {
    clownFishAdapter := ClownFishAdapter{suite.ClownfishObj}
    sharkAdapter := SharkAdapter{suite.SharkObj}

    birdAdapter := BirdAdapter{suite.BirdObj}
    assert.Equal(suite.T(), suite.BirdObj.Name, "Nigel")

    birdAdapter.Move()

    m := MultiAnimal{[]Animal{suite.HumanObj, &clownFishAdapter, &sharkAdapter, &birdAdapter}}
    m.Move()
}

adapter_common.go

package animals

type Animal interface {
    Move()           // any type that implements an area method is considered an Animal
}

type MultiAnimal struct {
    animals []Animal  // animals field is a slice of interfaces
}

func (m *MultiAnimal) Move() {
    for _, animal := range m.animals {  // iterate through animals ("_" indicates that index is not used)
        animal.Move()                   // execute polymorphic MOve method for this Animal
    }
}


type ClownFishAdapter struct {
    *ClownFish
}
func (this *ClownFishAdapter) Move() {
    this.Swim()
}

type SharkAdapter struct {
    *Shark
}
func (this *SharkAdapter) Move() {
    this.Swim()
}

type BirdAdapter struct {
    *Bird
}
func (this *BirdAdapter) Move() {
    this.Fly()
}

mammal.go

package animals

import (
    "fmt"
)

type Mammal interface { Move () }
type Human struct {
    Name string
}
func (this *Human) Move() {
    fmt.Println(this.Name + " is walking...")
}

fish.go

package animals

import (
    "fmt"
)

type ClownFish struct {
    Name string
}
func (this *ClownFish) Swim() {
    fmt.Println(this.Name + " is swimming...")
}

type Shark struct {
    Name string
}
func (this *Shark) Swim() {
    fmt.Println(this.Name + " is swimming...")
}

bird.go

package animals

import (
    "fmt"
)

type Bird struct {
    Name string
}

func (this *Bird) Fly() {
    fmt.Println(this.Name + " is flying...")
}

Notes

adapter_common.go defines:

  • Animal interface, which includes the Move() function
  • MultiAnimal object, which includes the animals field which is a slice of interfaces
  • Various Animal adapters are defined that adapts the interface of each object into the Animal interface ***

*** This is the Adapter design pattern in practice, which makes otherwise incompatible objects able to work together.

Testify

We use testify to help test the embedded adapter pattern.

testify features include:

  • Easy assertions
  • Mocking
  • HTTP response trapping
  • Testing suite interfaces and functions

We could have used composition rather than embedding in our adapter objects, where we'd use an animal field rather than embedding the animal object in our structs.

So, this ...

type ClownFishAdapter struct {
    ClownFishObj *ClownFish
}

... instead of this:

type ClownFishAdapter struct {
    *ClownFish
}

Favour object composition over class inheritance

Go has no inheritance. However, reuse can be achieved through embedding or composition.

Embedding has some disadvantages:

  • does not allow for method-level control
  • affects the public interface of objects
  • methods of embedded objects cannot be hidden

None of those issues adversely affect our example, so we went with the embedded adapter form rather than with composition.


Output



Testing started at 9:22 PM ...

=== RUN TestExampleTestSuite=== RUN TestAnimals
Dr. Philip Sherman is walking...
Nemo is swimming...
Bruce is swimming...
Nigel is flying...
--- PASS: TestAnimals (0.00 seconds)=== RUN TestExample
--- PASS: TestExample (0.00 seconds)=== RUN TestMultiAnimals
Nigel is flying...
Dr. Philip Sherman is walking...
Nemo is swimming...
Bruce is swimming...
Nigel is flying...
--- PASS: TestMultiAnimals (0.00 seconds)
--- PASS: TestExampleTestSuite (0.00 seconds)
PASS
ok      github.com/go_design_pattern/adapter/animals    0.010s

Process finished with exit code 0


References

comments powered by Disqus
The content of this site is licensed under the Creative Commons 3.0License and code is licensed under a BSD license