TDD in action : Debouncer in Go

Introduction

Writing tests in Go

  1. In the same folder as the file under test, write a new file following this pattern : yourFile_test.go;
  2. In this test file, create a new function with the specific name and signature : func TestXxx(*testing.T)
  3. Manage your test the way you need to, using the API provided by the testing.T object;
  4. Run the go test command
  5. Analyse the result to see if the tests succeed

The context

How does a debouncer look like ?

// Wrapping the debounced treatment
myDebouncedFunction := debounce(func() {
// The treatment...
}, 500)
// Example 1 - Simple call
myDebouncedFunction() // The method will be called in 500ms
// Example 2 - Multiple calls
myDebouncedFunction() // Method's execution cancelled by the next call to myDebouncedFunction
time.Sleep(100 * time.Millisecond)
myDebouncedFunction() // Method's execution cancelled by the next call to myDebouncedFunction
time.Sleep(100 * time.Millisecond)
myDebouncedFunction() // The method will only be called once in 500ms

TDD

  1. Write a test with a dreamed API, the way you think the program should work. This test should not go green yet;
  2. Write the simpliest, dumbest implementation to make the test succeed. This way, you will know if the following modifications break the promise;
  3. Refactor the code (and the test if needed) in order to really solve the problem raised by the test. You have restrain yourself to the scope of the test, do not refactor or optimise the code to solve future features just now.

Calling the provided method after the timer has ended

func TestShouldCallTheFunctionAfterTheProvidedTime(t *testing.T) {
called := false
debouncedMethod := Debounce(func() {
called = true
}, 1)
debouncedMethod() time.Sleep(10 * time.Millisecond) if called == false {
t.Error("The method was not called")
}
}
func Debounce(function func(), executeAfter int) func() {
return func() {
function()
}
}

Calling the method only after the timer

func TestShouldNotCallTheFunctionBeforeTheProvidedTime(t *testing.T) {
called := false
debouncedMethod := Debounce(func() {
called = true
}, 10)
debouncedMethod() time.Sleep(1 * time.Millisecond) if called == true {
t.Error("The method was called too early")
}
}
func Debounce(function func(), executeAfter int) func() {
return func() {
go (func() {
time.Sleep(time.Duration(executeAfter) * time.Millisecond)
function()
})()
}
}
func Debounce(function func(), executeAfter int) func() {
duration := time.Duration(executeAfter) * time.Millisecond
t := time.NewTimer(duration)
t.Stop()
// [...]
// [...]
go (func() {
for {
select {
case <-t.C:
log.Println("Executing the debounced method")
go function()
}
}
})()
// [...]
return func() {
log.Println("Reset the debouncer timer")
t.Reset(duration)
}

The last tests

func TestShouldCallTheFunctionOnlyOnceAfterTheProvidedTime(t *testing.T) {
executionCount := 0
debouncedMethod := Debounce(func() {
executionCount++
}, 1)
debouncedMethod()
debouncedMethod()
debouncedMethod()
debouncedMethod()
time.Sleep(5 * time.Millisecond) if executionCount != 1 {
t.Errorf("The method was not called only once, called %v time(s)", executionCount)
}
}
func TestShouldBeAbleToCallTheFunctionAgainAfterTheTimer(t *testing.T) {
executionCount := 0
debouncedMethod := Debounce(func() {
executionCount++
}, 5)
debouncedMethod() time.Sleep(10 * time.Millisecond) debouncedMethod() time.Sleep(10 * time.Millisecond) if executionCount != 2 {
t.Errorf("The method was not called twice, called %v time(s)", executionCount)
}
}

Conclusion

func Debounce(function func(), executeAfter int) func() {
duration := time.Duration(executeAfter) * time.Millisecond
t := time.NewTimer(duration)
t.Stop()
go (func() {
for {
select {
case <-t.C:
log.Println("Executing the debounced method")
go function()
}
}
})()
return func() {
log.Println("Reset the debouncer timer")
t.Reset(duration)
}
}

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

The Good the Bad and the VertX

Image Creation, Swapping, and Combining using Python

My travel with Powershell.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Thomas Ferro

Thomas Ferro

More from Medium

Implement Domain-Driven Design (DDD) in Golang

When Should You Apply CQRS Software Architecture Pattern?

Dependency Injection Vs Dependency Inversion Vs Inversion of Control, Let’s set the Record Straight

Kotlin SpringBoot App in Minikube Windows Cluster