Writing good Unit Tests

Writing unit tests are easier to do then it was many years ago, and yet we still struggle to write good unit tests. My assumption is that there is a fundamental misunderstanding on what testing is, and the principles behind each testing method.

This article is not going to discuss the principles behind the commonly used testing methods, or each of the testing methods themselves. I am going to focus on unit tests, as I consider it the most important automated testing method.

Some bad practices I have witnessed and definitely not limited to;

  • Using randomised libraries for passing to code under testing or system under test (SUT).
  • Working around language limitations i.e., Making private methods testable. Python and Typescript/JavaScript are good examples of this bad practice.
  • Treating E2E tests are unit tests
  • Fixture data shared across tests.

Obviously this is not a comprehensive list, but I hope it highlights some simple ways in which people misuse unit tests.

So let me briefly go over why some of these are bad practices and then go on to what unit tests are, their properties and an example of a good unit test.

Random libraries for Testing

In our first example of bad practices, a library is used to generate random values for our SUT. This seems nice right, now we can be sure our code works well, its going to be tested under many situations. This all seems fine, until, someone pushes to CI and the build breaks. The code was never touched, the tests were not changed and yet the test fails. Perhaps its not even 1 test, its many tests.

Countless times I have seen this happen when we run into situations just like this.

I’ll try re-running it

And guess what? It now works, problem solved. I myself am guilty of this practice. The issue here is we have a flaky test or rather an Erratic test, what makes it worse, is its done intentionally. We have not introduced any confidence to our system, if anything we have reduced it.

Working around language limitations

I am being quite specific here for example Python and Typescript/JavaScript has no true concept of private methods as of this writing.

class Contact {
    constructor(private readonly firstName, private readonly lastName) {}

    private generateName(): string {
        return `${this.firstName} ${this.lastName}`; 
    }
    
    getName(): string {
        return this.generateName();
    }
}

In this rather simplistic example we have a private method generateName, which just joins the first and last name. Now we have a test which might do something like this.

describe("Contact", () => {
    it("will give name", () => {
        const contact = new Contact("First", "Last");
        contact["generateName"] = jest.fn()
            .mockReturnValue("Quick Testing");
        expect(contact.getName()).toEqual("Quick Testing");
    });
})

The test works, we have worked around the issue of our method being private due the language lacking accessor visibility, and Typescript being built on Javascript making classes just a syntactic sugar on an object literal. I won’t list the problems here, I think at least the fact we are accessing a private method in a workaround method due language design/limitations should be enough.

So we have seen a few issues, lets go over what a unit test is.

What is a Unit Test

The simplest definition is.

A unit test is a piece of code (usually a method) that invokes another piece of code and checks the correctness of some assumption afterwards. If the assumption turns out to be wrong, the unit test has failed.

This is rather vague, so lets extend it a bit more.

A unit test is a piece of code that tests a small unit of work. The unit of work may call other methods, and once the unit of work is complete the test checks the correctness. The test should call no outside dependencies.

Hopefully this is a little easier to understand, there are some additional constraints so lets discuss the properties of a good unit test, to help us understand this.

  • It should be automated and repeatable
  • It should be easy to implement
  • Anyone should be able to run it at the push of a button
  • It should run quickly
  • It should be consistent in results i.e., if nothing changes between runs.
  • It should have full control of the unit under test.
  • It should be fully isolated
  • Keep assertions to a minimum (ideally 1)
  • When it fails it should be easy to detect what was expected and determine how to resolve

Some of these properties hit on some points discussed as problems.

It should be consistent in results i.e., if nothing changes between runs.

Random values being used in tests, obviously fails this. If the values are changing each time a test is run and nothing is changed, its difficult to get consistent results. We want consistent results as that increases confidence in our tests and our system. It also fails the next bit.

It should have full control of the unit under test.

If your tests is generating random values, you dont have control of the unit test as you do not have control of the random string being produced (otherwise its not random). But this property is good to understand, and most people following SOLID principles will have an easy time ensuring this is possible.

Keep assertions to a minimum (ideally 1)

For many this is highly debatable, but for me I find the tests are generally easier to explain and its clearer what should be going on. It also ensures we focus the type of unit test it is. Is it a structural test or a behavioural test. I will explain more on this in another article.

What does it look like?

So we have the basics of what properties are required for a unit test to be good. Lets see a quick example.

class ContactRepository {
    constructor(private readonly client: DocumentClient) {}

    public async retrieveContacts(owner: string): Promise<Contact[]> {
        const query: DocumentClient.QueryInput = {
            // ... dynamodb syntax here
        };

        const result = await this.client.query(query).promise();

        return result.Items.map(item => doStuffToObjectToMapToContact(item) as Contact);
    }
}

And our tests might be like this

describe("ContactRepository", () => {
    let client: DocumentClient;
    
    let queryPromise: jest.Mock;
    let query: jest.Mock;
    
    beforeEach(() => {
        queryPromise = jest.fn();
        query = jest.fn().mockReturnValue({promise: queryPromise});
        
        client = {query};
    });
    
    describe("query throws exception", () => {
        beforeEach(() => {
            queryPromise.mockRejectedValue(new Error());
        });
        
        it("will throw exception", async () => {
            const repository = new ContactRepository(client);
            await expect(repository.retrieveContacts("some owner"))
                .rejects.toThrowError(Error);
        });
    });
    
    describe("query returns items", () => {
        beforeEach(() => {
            queryPromise.mockResolvedValue({Items: []});
        });
        
        it("will return empty list", async () => {
            const repository = new ContactRepository(client);
            const result = await repository.retrieveContacts("some owner");
            
            expect(result).toHaveLength(0);
        });
    });
});

These tests could be clearer in terms of explaining what happens, but the basic concept follows all the properties.

  • Its repeatable, no random values
  • It was easy to implement, and extend I should add
  • It should be easy to run, I haven’t shown but most javascript systems will have a npm/yarn test command
  • Since we mocked the dependencies this should be fast to run. Think single digit milliseconds.
  • Again we have full control due to dependency being injected
  • Its isolated, all setup code is linked to a test via a beforeEach and no test is dependent on the other
  • We have a single assertion per test
  • As we have a single assertion, its easy to spot what failed.

We could improve on some of these, for example the last most assertion libraries allow a more descriptive reason on failure.

Conclusion

I feel as though there is a lot here, and yet have barely scratched the surface. But let’s discuss further in another article.

Type systems and Exceptions

I have thought about this problem for some time, but it was not until doing a lot of Typescript within an AWS serverless environment that it really hit home, it reminded me of this article.

So what is the problem?

The problem is that we use exceptions to do a lot of error handling, passing these exceptions to clients and hope that we either document it, or the programmer of the client knows to understand this could have an exception and catch. Now our system is exception driven. Wouldn’t it be better if there was another way? Let’s walk through a simple example.

Let’s say you have a repository that interfaces with AWS DynamoDB, an external dependency to your system. You might have the following code to retrieve some data

class ContactRepository {
    constructor(private readonly client: DocumentClient) {}

    async public retrieveContacts(owner: string): Promise<Contact[]> {
        const query: DocumentClient.QueryInput = {
            // ... dynamodb syntax here
        };

        const result = await this.client.query(query).promise();
        
        return result.Items.map(item => doStuffToObjectToMapToContact(item) as Contact);
    }
}

I have cut a lot of code out and did a cast, which with more code probably wouldn’t be required, but the main point is here we have a connection to DynamoDB, an external dependency. Anything could go wrong here;

  • Query could be invalid, and we receive an Exception.
  • Query may not return Items (it could be undefined)
  • Our function could throw an exception, depending on if we validate the contents of the result set.

There are possibly more places this could fail. We are then forcing all clients to understand the inner workings of this class to know what exceptions it might expect, we may need to document it as a block comment. Now we could simplify the exceptions it could handle like so;

class ContactRepository {
    constructor(private readonly client: DocumentClient) {}

    async public retrieveContacts(owner: string): Promise<Contact[]> {
        const query: DocumentClient.QueryInput = {
            // ... dynamodb syntax here
        };

        try {
            const result = await this.client.query(query).promise();

            return result.Items.map(item => doStuffToObjectToMapToContact(item) as Contact);
        } catch (e) {
            throw new ContactRepositoryException("My message");
        }
    }
}

You may not create your own type for this and just use the base Error class (of course different languages have a different base, but the idea remains the same regardless). So we have simplified the type of exceptions the client to this class needs to handle. Unfortunately we have solved nothing here. We still have the following issues (language may be dependent on some);

  • Clients do not need to catch the exception as its not enforced (This maybe language dependent).
  • Using throw forces us to either document and hope the IDE shows the documented exceptions thrown.
  • Documenting of course becomes stale quickly as its decoupled from the code.

Can we solve all 3 problems? Yes, and this is the key thing to having a typed language with Generics support (the solution proposed is possible without generics).

interface Value<T> {
    type: 'value';
    value: T;
}

interface Exception<E extends Error> {
    type: 'error';
    error: E;
}

type Result<T, E extends Error> = Value<T> | Exception<E>;

This by itself is incomplete, we need some way to encapsulate this logic.

class ResultResponse<T, E extends Error> {
    constructor(public result: Result<T, E>) {}
    
    static value<T>(value: T): ResultResponse<T, any> {
        return new ResultResponse<T, any>({
            type: 'value',
            value
        })
    }
    
    static error<E extends Error>(error: E): ResultResponse<any, E> {
        return new ResultResponse<any, E>({
            type: 'error',
            error
        })
    }
}

This is a minimal solution, for more of a concept you can view this Gist

Now we can modify our repository class to be like this.

class ContactRepository {
    constructor(private readonly client: DocumentClient) {}

    async public retrieveContacts(owner: string): Promise<ResultResponse<Contact[], Error>> {
        const query: DocumentClient.QueryInput = {
            // ... dynamodb syntax here
        };

        try {
            const result = await this.client.query(query).promise();

            return ResultResponse.value(result.Items.map(item => doStuffToObjectToMapToContact(item) as Contact));
        } catch (e) {
            return ResultResponse.error(e);
        }
    }
}

We can do a lot more with this concept, and I recommend viewing the Gist. This problem solves the fact that;

  • Client is now not required to catch an exception.
  • We are not throwing an exception to the client, its caught and wrapped.
  • We have documented this via a type and the client knows it could have a value or an exception.

This can be implemented in most languages that support Generics and have exceptions. This solution of course is not bulletproof. You actually need to ensure you know where possible exceptions can occur, some times this is not obvious. I like to ensure that

  • Any call to an external dependency i.e., HTTP, Database or other are strong candidates for this.
  • Any result from an external dependency is validated to my required type. Validation should throw an exception, and the potential result given the client to know how to deal with it e.g., No item might be ok as we can continue, or we may stop execution.

Conclusion

What I like about this is we have lent on the typing system making our system easier to code and should hopefully prevent silly errors from creeping into our application. This is not foolproof, as we can only apply this in situations we recognise.

New Year Goals

The concept of new year goals has always demonstrated to me at least from watching others that they are doomed to fail, and yet here I am with my own “new year goals”. Unlike some at least my “new year goals”, aren’t set because of the new year itself but rather due to the fact I had considerable time off during that period to reflect and see what I wanted. I also didn’t start on January 1, but rather January 3.

Before I go through my goals, lets be clear. I am not really doing a goal. That is I want to do X, but rather I am habit forming in order to get to X.

Learning Italian

Goals

I have 2 big reasons for learning Italian and they all stem from the fact I live in Italy. My reasons are:

  1. I am unable speak with people here without my wife being present (this is not 100% true, very basic things I can pick up)
  2. I want Italian citizenship

So how am I going about this?

  1. I wake up early; that is before 6am.
  2. I read an Italian grammar book as I know my grammar is horrible and without understanding grammar I can learn the words, but I know I will struggle to progress.
  3. Duolingo, not only is this competitive, but its repetitive.
  4. Read and study the A1, and A2 books I have. I need to obtain a B1 level certificate for citizenship.
  5. When I am done I see my streak as you can see on the calendar above. I mark it off.

Isn’t this just a goal? Yes but its also habit, the habit has a cue, a routine and a reward. Cue, wake up early, routine steps 2 to 4 and, reward 5.

Of course going forward I need to extend this to achive my goal, and system which is ensuring I am speaking with people.

Exercising

I realised at the end of last year I was seriously unfit, I struggled to carry my daughter for longer periods of 10mins walking. I was getting back and neck pain, and generally felt sluggish (getting off that couch was like a marathon).

So what am I to do?

My goal here is really to keep up with my daughter who is getting older (seemingly fast), but also to ensure I don’t feel like I am falling apart.

  1. After I finish my Italian hour, go an exercise
  2. Alternate what I do each day, 1 day is upper body 2nd day is lower body
  3. I spend about 30mins on this
  4. Mark what I have done on my calendar, take a photo of progress.

The last seems a bit strange, the photo is for me, its just to give my a glimpse when I look back from where I was to where I currently am to show that I am making progress.

Again I have a cue, as soon as I finish my Italian hour, begin exercising. My routing is dependent on what day it is , and I have a reward, my my progress by crossing it off on the calendar AND taking a photo so I can see progress over time.

Blogging

I want to get better at writing, and many times I have had a goal of writing a blog, and always failed. How will this be different?

Well my goal was always to blog. I now want to say I am a writer (not that I want to publish). I will accomplish this via

  1. Setting an alarm for 8.30 am Monday (every time I work)
  2. Write a blog post within 1.5hrs I have assigned myself
  3. Publish blog

This is my first blog post to ensure I keep this system alive, I hope it sticks like the others.

Again I have a cue, an alarm for Mondays at 8.30 am when I am working. A routine, write the post within 1.5hrs. A reward, publish it to my blog.

The idea is over time I will read my posts, potentially submit to social channels to gain audience and learn to write better.

Reading

I have to admit out of the other “goals” this is more goal oriented, there is a specific thing I want to achieve . This is due to the fact I have a habit on this already. I want to read 30 books this year. This actually seems very attainable, as last year I read 50 books. I set the goal to 30 books as I want to read different types of books some of which I know will be much slower reads, I also will not have several weeks in which I can just sit and read due to not working (I had between December 19 to Febrary 17th break between jobs).

My habit has been formed as you can see the in below screenshot.

My reading progress

I hope with these systems in place I can reach my goals, and form new habits that will allow me to change the goals but not the underlying systems.

Year in Reflection

It’s hard to imagine it’s been a year since I said goodbye to the company that brought me to Europe, where I met my wife and started my family. During this past year I have;

  • Been in Australia during one of the worst bushfires seasons to have hit the country and possibly in recorded history
  • Sold my home in the Netherlands
  • Moved my family to Italy
  • Started a new job, which is also my first time remote (pre-covid)
  • Discovered Italian Bureaucracy
  • Witnessed one of the greatest pandemics in living history
  • Started building a new house
  • Started to learn a language

Probably many other things along the way, but these are the big points of this year, and let’s just say what a year it’s been looking back on that list.

Usually when I do my year in reflection I like to look at what I learnt, so let’s see what I’ve learnt and why.

Communication

It’s hard to start with something else but this. Being a native English speaker living in a country and more importantly where I live in that country, English speakers could probably be counted as a small football team . Needing to learn a language to be an effective member of the community, is a difficult task, especially when needing to look after the family, and work. My tips are so far;

  • Find something that motivates you.
  • Talk with people (thankfully my wife is a native speaker so no excuses here)
  • Be comfortable failing and being frustrated. Being frustrated with my reduced capacity to communicate in Italian has been one of my biggest learning points and something I need to master more.

Feedback is another important aspect to communication. I believe during my professional career I have gotten much better with this, but I did have another learning point.

Ensure when giving feedback to remove yourself from distractions

Unfortunately I was trying to be helpful, and due to being distracted looking after my child and attempting to give feedback on a proposal. My directness came through a little too harshly. This put the recipient of the feedback on the defensive, and made my feedback all but useless.

The real learning here is of course is being focussed on the feedback you are giving, whilst removing yourself from other distractions to help ensure you are focussed.

Technical Debt

I’m always learning more about technical debt, but the last 2 years have made me focus on the real ways to solve this . This year it has come in quite hard. The core part of the product my company offers suffers from serious technical debt. Unfortunately the engineering team is small, and large backlogs of work. We also have the pressure on improving our offering to ensure we can meet company goals.

It’s difficult to sell to management that you want to spend 3-6 months or longer on improving what customers already have and are paying for, its even more difficult to sell it with saying, and no more new features will be developed.

The basic idea is to ensure when you build your road map / next quarter goals or whatever you call it at your company , is to ensure your solving your technical debt solves a problem for your company. Tie it with a monetary value and the company will have a tough time ignoring it.

This was best shown in solving one part of the technical debt on the main project. It does not scale, its deployment is put together with duck tape, glue and sweat. We could show that we lost sales due to our lack of scalability , bigger customers wanted to know, found out and went elsewhere. This got approved, and of course our deployment problem is tied to it.

Not all technical debt is as easily solvable as the above, but you must find ways to demonstrate it if you need to have a big project. Some times there is no clear monetary value, and it needs to be seen as small mini projects, which are distributed.

An example here is our issue with our frontend, its currently 2 deployments one being Angular v1 with React thrown in , the other is React but thankfully closer to being the latest.

The way to solve this is in small increments;

  • Get a basic strategy, ours is strangler pattern on the Angular v1 project and using Microfrontends
  • Inform others, ideally those in manager positions of the choice and how it should work
  • Get someone to start it in a project that touches the key area. My team did this as a new greenfield project.
  • Convince other teams to follow suit, and ensure you are there to help out.

I have so far done the first 3, the last one is due to resource constraints in that another team hasn’t picked this up. The other thing to note, this strategy is very slow, so don’t get frustrated if it can take months to years to gain solid traction.

Final note

As I’ve said it was quite a year with many things being done, I hope next year is slightly less eventful, safer and focussed on stability a.k.a no more moving 🙂.

Being Prepared for an Interview

I’ve recently gotten back into conducting interviews at the company I work at. I have conducted numerous software engineering interviews in my career (more than some, less than many others). Something during my time interviewing candidates, has shown that many candidates do not take the time to prepare for an interview.

It strikes me rather strange that candidates are ill prepared for interviews. The interview process from the candidates point of view can be daunting, and most of the time during the interview you can be quite nervous. Being nervous means you may;

  • Say something incorrectly
  • Not remember something you actually know
  • Not explain a concept coherently

Preparing for an interview does not mean that nerves go away, but they should be somewhat reduced. With nerves reduced, you can focus properly on what the interviewer wants during an interview. Remember each communication is a reflection of you, so remain professional.

But most importantly preparing for an interview is ensuring you present yourself in the best possible manner. After all the interview is to take the next step in your career, so make it a good step.

What can I do to prepare myself?

Ideally you have been preparing yourself for a long time by doing;

  • Upskilling
  • Mock Interviews
  • Actual interviews without actively looking

Unfortunately not everyone can continually do this, so my recommendations to being prepared when you are interviewing are as follows.

Talk with the recruiter

Good recruiters are happy enough to give information to candidates, most companies have some sort of recruitment software that allows people within the interview committee to see communications. You should continually be in contact with them to understand;

  • What is the interview going to be about?
  • Who am I interviewing with?
  • Where in the process am I?

These are basic questions, but will help frame how you prepare and what to expect in the interview.

I did a coding assignment

This is not some hacker rank coding challenge, but rather an assignment, which you usually have a time frame to submit being 1 week. These assignments are internal time boxed on actual work time between 2-4hrs, there might be some that go over this, but don’t spend more time than this.

Things to remember when submitting are;

  • Keep communication clear
  • Give some basic rationale behind some decisions made

If you are asked to do another interview, this interview will most likely touch the submission you did so prepare yourself for that, and of course. Talk with the recruiter to know what you should do within the interview.

Talking with a non-technical person

Some companies may want you to talk with people outside of recruitment and engineering. So do not expect anything technical in this interview, but be prepared for behavioural type questions.

You should prepare yourself to handle some of these types of questions

  • Past career
  • Objectives in your career
  • What success you have created/been apart of
  • What failures you’ve been apart of, and how you have learnt from them.

Try to aim yourself to answer these in story mode. You should try to structure your answer to follow a pattern, such as;

  • Nugget — Start focusing the story
  • Situation — Give basic details on what you did
  • Action — What actions did you take, phrase like a list of bullets
  • Result — Demonstrate why it worked/not worked.

I hope this advice is helpful, I have followed this in various forms, and have seen it work quite well, I’ve also not followed, and it showed. Remember that the interview is important, prepare like it is, and hopefully the next job is the right one to continue your career path.