Unit Testing - The Freedom to fail ❌

Unit Testing - The Freedom to fail ❌

In software engineering space, we often face a situation where we developers, demand for full app revamp for numerous reasons. The codebase becomes fragile. More regression bugs popping up after every release. It takes a long time to add new features and even to resolve small bugs. Everyone is afraid of making changes to the giant beast classes. While revamping looks like a viable option in the developer's eyes, the businesses cannot hold on to building new features. Sometimes, it is impossible to afford regressions in the existing features since the customers are already used to the product.

Now, Is it possible to revamp once in every two or three years? The very obvious answer is a big NO! Now, what exactly went wrong? It could be the Bad software design,

  • Giant beast classes
  • Modules are tightly coupled
  • No unit tests

No unit tests

In this article, we are going to see only the consequences of not having unit tests and how we can prepare ourselves to write our first unit test. If you see, we might not face any of these problems while starting a new project from scratch. Everyone loves to work on a new project. As your application grows, your confidence level starts to dip down and you might not able to ship features as fast as you were. The codebase is becoming legacy very quickly without tests.

"Legacy code is the code without tests" - Micheal Feathers in 'Working Effectively with Legacy code'.

Excuse me ☝️

We find many excuses for not writing unit tests,

  • Writing tests slows down the development process
  • Don't have enough time
  • Anyway, we need to do E2E testing (End to End)
  • Not sure what to test
  • Don't know how to write unit tests

Well, these are misconceptions we have in our minds because we really don't understand what unit testing is for. If you're also the one finding these excuses, then it is time to understand what is unit testing and automation testing pyramid.

What is unit testing?

Unit Testing is a level of software testing where individual units/ components of software are tested. The purpose is to validate that each unit of the software performs as designed. A unit is the smallest testable part of any software.

Automation Testing Pyramid:

testpyramid.png If you notice, writing unit tests are cheaper and faster than E2E testing. Though, unit testing cannot replace E2E testing, unit testing holds the major importance in the pyramid space. Unit testing is the base of the test pyramid. As we know clearly that a weak base can topple down the entire building and software development is no exception. And that's when we are demanding to revamp. I cannot resist mentioning this funny dialogue(Tamil) by Vadivelu - that explains the weak basement quite well 🥴😂 basement_weak-uh.png If you're still wondering that unit tests are not that prominent in SDLC, you must know the benefits of writing them.

Why unit tests?

Writing unit tests allows us,

  • To Speed up the development process in the long run and deliver new features with better confidence (I know this is opposite to what I wrote in the excuses list)
  • To Refactor with confidence
  • To get Instant feedback, much faster than running the entire application and test visually every time
  • To keep the codebase maintainable. Remember that we may not be able to write effective unit tests on a codebase that is designed badly
  • To keep the various modules and components loosely coupled
  • Unit tests tell you how it behaves. Yes, it is a kind of passive documentation for your code
  • To debug the issues easily
  • Identify breaking changes in development stage itself

Not sure what to test?

Ideally, everything! But we aren't in the ideal world. While it is a good idea to aim for 100% code coverage, we might have some classes in our application that talk to External frameworks. Our objective for writing unit testing is not to test the external frameworks and that is the reason we should keep these classes as thin as possible. If your codebase doesn't have any tests and you're beginning to write, then I would recommend you start with the core feature of your application. Most often, it is not that easy to write effective tests on your core features. The chances are that they are the giant beast classes. In this case, You might want to do refactoring before writing tests. Remember,

The more complex the class, the more you want to write tests.

How to write unit tests?

AAA (Arrange, Act, Assert) is a popular pattern that suggests to divide the test method into 3 sections,

There are three steps we need to follow for every unit test,

  1. Arrange: This section initializes objects and sets the value of the data that is passed to the method under test. This involves topics such as Test Doubles, Dependency Injection, etc
  2. Act: This section invokes the method under test with the arranged parameters
  3. Assert: This section verifies that the action of the method under test behaves as expected

Consider we are testing the class Stack, You must have come across the term SUT which stands for 'System Under Test'. Well, in our case Stack is our SUT.

final class StackTests: XCTestCase {

    var sut: Stack<String> = Stack()

    func test_push() {
        // arrange
        sut = Stack()
        sut.push("Hello")

        // act
        sut.push("Reader")

        // assert
        XCTAssertTrue(sut.top.data == "Reader")
    }

    func test_pop() {
        // arrange
        sut = Stack()
        sut.push("Hello")
        sut.push("Reader")

        // act
        sut.pop()

        // assert
        XCTAssertTrue(sut.top.data == "Hello")
    }
}

Yeah, we are now ready to kick start our unit testing journey, I mean it. Unit testing is not a destination but a continuous journey. As our application grows, we should keep writing tests. The more unit test coverage you have, the more benefit you get. Always remember that unit tests are first-class citizens in our project and they are equally important as our production source code. So it is important to plan and structure the unit test classes as well. I've chosen a simple example but when we start writing tests to our actual code, we may encounter problems in creating mocks for a complex class, testing asynchronous code, private methods, UI Components. I will cover a separate article to address the common problems in writing tests.

unit-test-meme.png

Thank you for reading this article! Let's start doing more of CMD+U(Test) than CMD+R(Run).

Happy coding!