How unit testing made me a better developer

Developing yourself as a software engineer is important for progression. One tool you should incorporate in your arsenal is unit testing. What are unit tests? Why should I write them? What part do they play in my success or that of my software? Today I’m going to talk about how practicing this principle made me a better developer.

I’ve been in software engineering for quite a while. Good coding practices have existed in the field long before I did but their dissemination started slow. Call it an artifact of circumstance, my surroundings, or something else but the truth remains the same — I didn’t get exposed to unit testing until a while into my career. When I finally learned, however, proper unit tests have made me a better developer.

What are unit tests?

In order to understand how unit testing made me a better developer it is important that you understand what unit tests are.

Software Testing Fundamentals defines it: “UNIT TESTING is a level of software testing where individual units/components of a software are tested… A unit is the smallest testable part of any software.”

Put more simplistically, unit testing is writing code to test your code. While they are only one piece of the Test Pyramid, they are the most important and fundamental piece.

Why should I write unit tests?

You may be the best engineer in your company. That’s ok, you’re still not infallible. Writing unit tests is just as important to protect you from yourself as it is to protect your code from somebody else.

Ray Arnold might have survived if he had unit tests...
Ray Arnold might have survived if he had unit tests…

Scenario A

Let’s consider a hypothetical situation. You write a feature for a major software package. You go through multiple iterations of manual testing, QA signs off on it, and it goes live. Months go by and the software is rock solid. You do a happy dance, you’re the shiz.

One day, however, the software fails. You panic. You check the logs, you download the code, and you try to figure out what happened. Ultimately you determine the problem was data related. In order to fix the issue, you go in and add some additional checks and balances. Finally, you deploy the new version and life goes on. Somewhere in there, your manager asks: “how certain are you this problem won’t happen again?” With a pensive look on your face, you answer: “I honestly don’t know.”

Does this sound familiar. Maybe not this exact scenario but something similar? How might this situation be different had there been unit tests?

Scenario B

How about a different scenario? Senior engineer writes a bunch of code. It’s elegant and slick. Engineer moves on to another project or task and then months down the road comes back to it. Even though they wrote it, they can’t make heads nor tails of what the code is doing and have a hard time getting back into it to make modifications. Or maybe a different developer comes in and is trying to maintain the code. Either way, the end result is the same. The code, while pretty freaking awesome, is hard to follow and you or they are afraid to make modifications to it.

With both scenarios, the engineers are left with fear and doubt. They aren’t comfortable with where they are and can’t make predictions for success. Unit tests–good ones–on the other hand, will help allow predictability and reduce or flat out remove fear and doubt.

Reasons for and benefits of Unit Tests

I don’t want to reinvent the wheel here. I’m going to highlight some reasons and benefits aggregated from two sources [DZone and CODE Magazine].

  • Discipline and Rigor / Design / Quality of Code
  • Does it work?
  • Reduce Cyclomatic Complexity
  • Your Software Is Used Before Delivery
  • Documentation (self-documenting)
  • Measure the Effort Needed to Modify an Existing Feature
  • Enforces Inversion of Control/Dependency Injection Patterns
  • Code Coverage
  • Performance
  • Enables Continuous Integration (CI)
  • Reduce Costs
  • Debugging Process
  • Finds Software Bugs Early

I personally found great value in the “enforces inversion of control/dependency injection patterns”, “debugging process”, and “finds software bugs early”.

Arguments against unit tests

Seeing as I had to go through a maturation process myself, I thought it prudent to talk about some arguments against unit testing. I had some of these reasons myself and I’m glad to say that practicing the principle helped dispel them.

It’s too hard

“Unit testing is too hard or time-consuming.” We might say that because we don’t understand how to actually do it. For example, I had a co-worker who would write tests but commented on how difficult it was to set them up. Taking a look at the code I realized he was right. It *was* too difficult because he was making it difficult. It turned out in this case that he wasn’t writing unit tests at all but rather integration tests.

I don’t have the time

“Everything is an emergency, we can’t afford the time to write unit tests.” This is probably true short-term but realistically, you cannot afford to *not* write unit tests. It is sometimes difficult to recognize their short-term value but I assure you the dividends are exponential long-term.

It increases maintenance

“Unit testing increases maintenance liabilities because they are less resilient against code changes.” Ok, this is a hard one to swallow. If you aren’t practicing good SOLID design principles, this argument is pretty true. If you are, however, then this is a major fallacy. Say it with me: “unit tests are written against the smallest unit of work within my software application.” If your smallest unit of work is too complex then you’re doing it wrong.

It doesn’t guarantee success

“Tests can lead to a false feeling of security. You can write tests and have them all pass and still miss troubles introduced in subsequent refactorings.” I previously mentioned SOLID principles. That needs to be factored in. Granted, you really can write tests and have them all pass while still having failures at the integration tests level. Refer back to the Test Pyramid on this one.

There are plenty more arguments against unit testing but I once again want to assure you that every single argument against it can be countered. In the end, it isn’t worth it to not have them. That has been my personal experience.

How to unit test

This may seem like a pretty silly topic to discuss. I mean… doesn’t everybody know how to unit test? No. No, they don’t. I didn’t and neither did you. Everybody has to learn at some point in time.

It is difficult to talk about unit testing without referring back to SOLID design principles. While it is possible to unit test without following them, you’re just really making your life more difficult than you need to.

Key points here are the dependency injection and mocking. Additionally, as I’ve previously mentioned, you want to test the smallest unit possible. What is that, you ask? Depending on the programming language we’re talking about a method or function. That’s it. Anything else is an integration test.

Consider this completely contrived method:

public SomeResponse GetImage(SomeRequest request)
{
	// loads the file bytes and some metadata
	var someFile = _fileLoader.Load(request.FileName);
	// loads a bunch of user comments or something...
	var someComments = _commentRepository.Get(request.FileId);
	// transforms to the response
	var response = _someResponseAdapter.ToModel(someFile, someComments);
	
	return response;
}

This particular method has three dependencies that presumably I’d have injected into the class. In order to test this method, I don’t actually care about any of these dependencies directly. I only care about how this particular method behaves given certain inputs. I would want to test how it behaves with good and bad inputs alike. Perhaps a bad input would throw a NotFoundException. In fact, as I was writing unit tests I would hopefully realize I should add a null check if (request == null) throw new ArgumentNullException(nameof(request)); and then write a test for that case.

So how do I write the unit test in a way that I don’t care what _fileLoader, _commentRepository, and _someResponseAdapter implementations are? Mocking, that’s now.

What is mocking?

I don’t want to spend too much time here. Contrary to what you might think, mocking is not relentlessly making fun of the dependency. It is substituting or simulating the behavior of the dependency.

In the case of testing, we can mock the behavior by stating the outcome of an action. For example, I might mock _commentRepository that when the ID is 3 it always returns some particular set. I could also make it a lot less restrictive, of course.

There are several mocking frameworks available out in the wild. If you’re a .NET developer you might consider Moq, nSubstitute, Rhino Mocks, or any number of others.

Sample tests

Here are a couple sample tests for the above contrived example (using xUnit w/Moq):

[Fact]
public void GetSomething_Fails_ArgumentNullException()
{
	// arrange
	var fileLoader = Mock.Of<IFileLoader>();
	var commentRepository = Mock.Of<ICommentRepository>();
	var someResponseAdapter = Mock.Of<ISomeResponseAdapter>();
	var request = default(SomeRequest);

	var target = new SomeClass(fileLoader, commentRepository, someResponseAdapter);

	// act / assert
	Assert.Throws<ArgumentNullException>(() => target.GetSomething(request));
}

[Fact]
public void GetSomething_Fails_KeyNotFoundException()
{
	// arrange
	var fileLoader = Mock.Of<IFileLoader>();
	var commentRepository = new Mock<ICommentRepository>();
	commentRepository.Setup(x => x.Get(It.IsAny<int>())).Throws<KeyNotFoundException>();
	var someResponseAdapter = Mock.Of<ISomeResponseAdapter>();
	var request = new SomeRequest { };

	var target = new SomeClass(fileLoader, commentRepository.Object, someResponseAdapter);

	// act / assert
	Assert.Throws<KeyNotFoundException>(() => target.GetSomething(request));
}

[Fact]
public void GetSomething_Success()
{
	// arrange
	var request = new SomeRequest { };
	var response = new SomeResponse { };

	var fileLoader = Mock.Of<IFileLoader>();
	var commentRepository = Mock.Of<ICommentRepository>();
	var someResponseAdapter = new Mock<ISomeResponseAdapter>();
	someResponseAdapter.Setup(x => x.ToModel(It.IsAny<object>(), It.IsAny<object>())).Returns(response);

	var target = new SomeClass(fileLoader, commentRepository, someResponseAdapter.Object);

	// act 
	var result = target.GetSomething(request);

	// assert
	Assert.NotNull(result);
}

I want to point out that these are extremely contrived but they highlight the principles. I am mocking the interfaces and *only* making them do something when I want them to behave a certain way. Each of these tests, then, is pretty minimal. I would write unit tests against implementations of those interfaces as well, but tests for SomeClass are only testing that functionality specifically.

How it made me a better developer

Since I’ve titled the post in such a way I suppose it is only logical I actually give you my own personal experience with it.

I started life as a developer practicing monolithic development. Several of my classes were huge. They did everything. Components had tight coupling between them and nearly every time I refactored code it caused a cascade of changes. That said, I had an extremely low bug-to-code ratio, so I never felt like I was doing anything wrong.

I heard about unit testing pretty early in my career. Unfortunately for me, it appeared a monumental effort for seemingly small returns. I went my merry way with manual testing in monolithic applications.

As time passed and I moved on to a new job I started feeling like there probably was something about this whole “unit test” movement. I began writing tests against my monolithic objects despite the time and work involved. I started seeing benefits as it caught bugs and narrowed scope. It was difficult, it was time-consuming, but it was still beneficial. I thought to myself: “self, there has got to be a better way to do this!”

Found the cadence

Finally, as I moved around I found myself working somewhere with a lot more developers. Several of these developers were consultants and had been around a bit. Thank. Freaking. Goodness. In part due to the number of consultants, this particular job was nitpicky about unit tests. Two things happened here: I experienced a unit test epiphany and started both learning and applying SOLID design principles. My eyes had been opened.

In this case, unit testing helped me properly apply SOLID design principles to my code. I also gained the added benefits that unit tests bring by themselves.

Look, I’m not saying that writing unit tests will make you a God programmer. What I am saying, however, is that it will definitely empower you to write well-tested “bullet-proof” code.

Conclusion

Unit testing is the practice of writing code to test your code. Done properly, a unit test is written against the smallest unit of work within your application (aka, a method). In order to reduce complexity of the test, you mock functionality of dependencies using mocking frameworks such as Moq, nSubstitute, or others.

I highly recommend any and every developer out there establish the habit of writing good unit tests. You will find success in doing so.

Credits

Photo by Markus Spiske temporausch.com from Pexels