The Quest for Reliable Code (Part 1)

Exploring the World of Software Testing through Python and Flutter

Aditya Rajput
MDG Space
6 min readAug 22, 2023

--

Greetings, young adventurer! Have a seat, for I know you must be tired after the perilous trek you have undertaken to reach my cottage in these mountains. Here, have a cup of dwarven ale to warm you up.

By a swift glance at the phase of the moon and the length of your fingernails, I can surmise that you are troubled. I know not what exactly troubles you, but since my parents named me “The Wizard Who Teaches Software Testing (And Nothing Else)”, and since the banner outside my little cottage says “Come Here to Learn About Software Testing”, I will do my best to teach you about Software Testing, and hope it solves your problems. I am incredibly sorry if you came here hoping to book a couples’ therapy session.

A four panel comic about a one trick pony
My parents kinda ensured that I would grow up to be a One-Trick Pony

🧪

Why bother with tests?
Imagine building a masterpiece, pouring your heart and soul into it, only to have it crumble the very second someone pokes it too hard. That’s the risk you take every time you start testing your software after you’ve already written the bulk of it. And don’t get me started on ignorant mortals who think they’ll manually test the application and be done with it!

You see, tests are not just a fancy accessory for your code; they serve several purposes that make your life easier (and overall development time shorter):

  • Define features and expected behaviour: Tests act as keen-eyed guardians of your code’s functionality, clearly defining what your code should and shouldn’t do.
  • Ensure that your code plays nicely with others: A well-defined test suite prevents confusing integration issues during PR reviews or CI workflows.
  • Refactor and upgrade: Once your test suite attains a high coverage, you can confidently refactor your codebase or introduce new features without losing sleep over unintended consequences.
  • Avoid embarrassment: You should write tests so that those jerks at QA don’t get on your ass about edge-cases and embarrass you in front of your peers.

📝

What do we test for?
Now then, there are various types of tests, each serving a specific purpose in ensuring the quality of your software. You need to verify the behaviour and performance of classes and functions, interactions of project components/modules and also ensure the look & feel is what you want it to be.

Accordingly, we can broadly categorize tests as follows:

  • unit tests validate the behaviour of individual functions or classes
  • integration tests evaluate how different parts of your project work together
  • UI tests ensure your components and interactivity are on point.
  • acceptance tests are the human element
  • (and a thousand others… but those can be grouped into the ones enumerated previously)
A person staring at the camera and rapidly coming very close so as to cover the entire frame with their face. Text in all caps impact font that says “Let’s take a closer look”
Wizards have no concept of personal space

📱

UI Tests are a designer’s best friend. When frontend developers try to cut corners, these tests catch them in the act and enforce the might of the designer overlords!

  • They check whether expected components are on screen for any given state of the page, giving you the power to detect sneaky disappearing buttons or rebellious check-boxes.
  • They verify your components against so-called golden files¹, which are image files containing the expected visual representation of a specific UI component.
  • They ensure that your UI is responsive and thus remains dashing on every device, from the latest Pixel to even my old Nokia!

🕵🏻‍♀️

Acceptance Tests involve trying out the wildest, most far-fetched scenarios to check whether you’ve handled the edge cases. Because, of course, there will always be users who take the worst decisions when using your application.

  • They ensure that you have thought of the question “Well what if the user wants to spam the Send button in my messaging app without waiting for the API call to finish?”
  • This is where QA comes in to whoop your butt.

Acceptance tests generally go as follows:

A QA Engineer walks into a bar…

Orders a beer
Orders 2 beers
Orders 9999.99 beers
Orders -1111% beers
Orders NULL beers
Orders -__- beers
Orders 24kq2f43y$%^^&*(&*$%qd20tlpl2;3414l1kf beers

(I respect QA… I just don’t like them.)

🔬

Unit Tests validate the behavior of individual functions or classes.

They investigate the behaviour of the fundamental units of your project, examining them under different states and inputs.

  • They are usually function-centric or class-centric. Thus their structure closely follows your src/ directory.
  • They are usually structured using the paradigm Arrange-Act-Assert — you first set up the inputs-object’s attributes, then call the function to be tested, then finally verify the final values of the output-object’s attributes.
  • They require intimate access to the internals (private functions and intermediary classes). These tests peek inside the magician’s hat so there are no blackboxes whose functionality is untested.

The pseudocode for a typical unit test goes like “When deserializing this JSON, this function should either return a valid User object, or throw appropriate exceptions with proper description

🧩

Integration Tests focus on the big picture, ensuring smooth sailing from start to finish.

  • They ensure that all your project modules fits together like a perfectly solved puzzle from one end to the other end.
  • They are usually feature-centric. In a messaging app, you may have integration tests for login, signup, messaging, profile, etc.
  • They don’t require access to the internals (private functions and intermediary classes). This is why a lot of these tests require the use of mocks, in order to maintain dependency graphs without actually executing expensive operations.

The pseudocode for a typical integration test can go like “When the login method is called, these states should be pushed and this data should be fetched”

Additionally, there are end-to-end tests which are very similar to integration tests. Where they differ, however, is while integration tests verify the compatibility of modules, E2E tests enact Critical User Journeys² of the project.

To give a vague example, an integration/E2E test can enact the check-out process for an e-commerce website, including adding items to the cart, applying discounts, calculating totals, and processing payment. Thus, this test will verify the integration between the shopping cart, inventory management, pricing, and payment processing modules.

a diagram indicating the difference between E2E, integration and unit tests
I hate that “code” is represented using </> instead of {}

Well my child, the next step on this journey is to take a look at some tests written for a real project. As a wizard I do not posses a laptop (because frankly it would hurt my brand image). But for this next bit you should set your backpack down and take out your laptop. If the battery’s low I can prepare a spell to call down the wrath of heaven in the form of Zeus’ bolts — oh you have a powerbank, that’s nice. Anyway… shall we?

Furthermore, an essential part of Software Testing is the art of Mocking. Mocks are trickster objects that mimic the outward behavior of the dependencies of the module being tested, so as to maintain dependency graphs without actually executing expensive operations.

Now, given what my parents named me, I won’t teach you about Mocks, but I can give you a referral to a guy named “The Sorcerer Who Has Mastered Software Mocks (And Nothing Else)” whom you can visit in your next adventure:

Follow MDG Space to get notified when the article on Mocking is released!

📜 The Summary

Tests are your best buddies in ensuring software quality, defining features, ensuring compatibility, facilitating refactoring, and avoiding embarrassment during QA reviews. We delved into four broad categories of software tests: UI, unit, integration and acceptance. For each category, we discussed their significance and specific requirements.

📚 The Resources

Take these tomes with you, for the journey ahead is long and you’ll need some books to keep you company.

📌 The Footnotes

  1. Golden files can refer to any file representing expected output of an operation. I’ll tell you more about golden files when we dive into actual code.
  2. Critical User Journeys refer to the most important pathways that users take to achieve their desired outcomes. If your project is a food delivery app, some of your CUJs can be: (i) browsing for and ordering food, (ii) user registration and profile creation, (iii) reviewing the delivery and food dish.

--

--

Aditya Rajput
MDG Space

Secretary @MDG Space | Ex-intern @PLAID | GSoC '22 @Matrix.org | InterIIT silver medalist