I’m reading TDD By Example by Kent Beck right now and one of the Red Bar Patterns really resonated with me. It’s called the Learning Test.

Here’s the scenario. You have a new library or framework that you’re implementing. Instead of just diving into TDD’ing the implementation, you can write some learning tests. These are tests where you validate how you expect the 3rd party tool to work for you. I see this as testing the contract between your application and the 3rd party tool.

Here’s an example that I did using github.com/aclindsa/ofxgo.

Now this library does a lot and I really only care about one small part. This library can interact with OFX servers but my current reason for using it is to parse OFX files that have already been downloaded to the local file system.

package main

import (
    . "github.com/onsi/gomega"
    "testing"
)

func TestReadLocalOFX(t *testing.T) {
    g := NewGomegaWithT(t)  
}

So after looking through some of the functions it looks like I want to use this ParseResponse() function.

I’ll want to assert something after parsing to verify that it worked. Looking at the example in the README it looks like I can parse how many banks are contained in the OFX file.

func TestReadLocalOFX(t *testing.T) {
    g := NewGomegaWithT(t)

    g.Expect(len(banks)).To(Equal(1))
}

Now I need to learn how to get that banks variable.

From ofxgo I don’t need to initialize anything, I can just directly call ParseResponse() and pass in an open file. That seems like a good first step; parse the OFX file!

import (
    "github.com/aclindsa/ofxgo"
    . "github.com/onsi/gomega"
    "testing"
)

func TestReadLocalOFX(t *testing.T) {
    g := NewGomegaWithT(t)

    resp, err := ofxgo.ParseResponse(ofxFile)

    g.Expect(len(banks)).To(Equal(1))
}

I’m going to need an OFX file for testing as well. So I head over to my banks site and download a OFX/QFX file and put it in the same directory as my test. I can now open the file from my test:

func TestReadLocalOFX(t *testing.T) {
    g := NewGomegaWithT(t)

    ofxFile, err := os.Open("ofxFile.qfx")
    g.Expect(err).ShouldNot(HaveOccurred())

    resp, err := ofxgo.ParseResponse(ofxFile)
    g.Expect(err).ShouldNot(HaveOccurred())

    g.Expect(len(banks)).To(Equal(1))
}

Now I can get the banks variable from the *Reponse from ParseResponse().

func TestReadLocalOFX(t *testing.T) {
    g := NewGomegaWithT(t)

    ofxFile, err := os.Open("ofxFile.qfx")
    g.Expect(err).ShouldNot(HaveOccurred())

    resp, err := ofxgo.ParseResponse(ofxFile)
    g.Expect(err).ShouldNot(HaveOccurred())

    banks := resp.Bank
    g.Expect(len(banks)).To(Equal(1))
}

And now if I run my test it should work!

=== RUN   TestReadLocalOFX
--- FAIL: TestReadLocalOFX (0.00s)
....    
        Expected error:
            <*errors.errorString | 0xc000091050>: {
                s: "OFX SECURITY header not NONE",
            }
            OFX SECURITY header not NONE
        not to have occurred
FAIL
FAIL    ofxreader   0.018s

It failed! Already this test is teaching me something about this library. It’s expecting this OFX SECURITY header to be set to NONE. Looking at my OFX I see this:

OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:TYPE1
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE

It says security is TYPE1. I decide to search the repo for this error and find this: https://github.com/aclindsa/ofxgo/blob/3e8a9c5a5382dccc0fd89b4d869e2202a48e8a66/response.go#L90-L92

This library only works if the security header is set to NONE. I’ll want to add this learning to my test so I don’t forget.

import (
    "github.com/aclindsa/ofxgo"
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
    "io/ioutil"
    "strings"
    "testing"
)

func TestReadLocalOFX(t *testing.T) {
    g := NewGomegaWithT(t)

    By("setting OFX security header to NONE")
    file, err := ioutil.ReadFile("ofxFile.qfx")
    g.Expect(err).ShouldNot(HaveOccurred())
    newOFXFile := strings.Replace(string(file), "SECURITY:TYPE1", "SECURITY:NONE", 1)

    ofxFile := strings.NewReader(newOFXFile)

    resp, err := ofxgo.ParseResponse(ofxFile)
    g.Expect(err).ShouldNot(HaveOccurred())

    banks := resp.Bank
    g.Expect(len(banks)).To(Equal(1))
}

And now if I run the test:

=== RUN   TestReadLocalOFX
STEP: setting OFX security header to NONE
--- PASS: TestReadLocalOFX (0.01s)
PASS
ok      ofxreader   0.022s

It passes! That’s one learning test for this library.

I’m hoping to bring learning tests to my team at work because I think it’ll help spread knowledge among all team members and become useful reference points.