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