Testing 101

Is testing important?

Testing 101: An Introduction to Software Testing

I'm still trying to crack this 'get a Developer job' thing, but I have started to realise that as my projects get bigger, in the hope of building something useful enough that an employer might be interested in taking a chance, the lines of code have increased hugely.

When you look at something like a React project, it is likely made from many components, each of those could be a couple of hundred lines long. Testing bit by bit sort of works, but when you put it all together, you can find things don't work as intended, even simple things like multiple CSS documents can clash.

I built a project recently where I used a main stylesheet for my overall theme definition but component-level stylesheets for things that were specific to the component. This still makes sense in my head today, but it did cause some issues when a page was made up from several components, and this is just the 'pretty stuff'—we're not even talking about functional clashes yet.

One Person vs Many People

I already stumble into things where my own complexity can trip me up. Now imagine if that new feature I'm working on is for an existing piece of software, perhaps it's something built by the previous you (or me), or perhaps you've joined a team of people that may be based in any number of locations. Understanding how a change can affect other things is key, and some of the ways we can do that are through Quality Assurance (QA) and Testing.

Millions of Lines of Code

The bigger the codebase, the bigger the potential problem(s). We've all heard the comment it's not a bug, it's a feature... but imagine if that feature did actually start out as a bug and was brushed aside. The users learned to live with this feature, and then a new developer comes along. They have been asked to add a new feature, but that feature interacts with that earlier bug in unexpected ways. The new developer doesn't know the history of the earlier bug, so they go about manipulating their own code to work with this bug. Some time later, another developer joins the team and stumbles into the same trap.

Before you know it, you might have millions of lines of code stacked on top of a bug that should never have made it through QA anyway. Eventually, this may collapse on its own, or it may be that a future you decides to try and unravel this mess of spaghetti code in order to clear out technical debt.

Bugs Can Be Expensive

I'm writing this blog post in early August 2024, only a couple of weeks after the largest technology meltdown to date. That link to bad software updates getting into the wild wasn't planned, and this isn't meant to be a dig at CrowdStrike. Heck, for all I know, they might want to hire me, and I don't want to burn the bridge before I even get there 😀 (although I was quite vocal in my defence of Microsoft on my Twitter feed for a few days as I didn't see why they were taking all the bad press for another company's mistake).

This was a planned piece for my blog, partly because I did stumble into a CSS minefield of my own making recently and partly because I saw some brilliant jobs advertised by a company called QA Wolf, so I thought I'd swot up on my Testing & QA knowledge by throwing out a blog post.

However, the Crowdstrike example caused untold sums of damage by affecting computer systems across the globe. Much of that damage will be reputational, and whilst it was an Endpoint Security company at fault, not a lot of people will know that, partly because the media focused too much on Microsoft and partly because a lot of affected end-users were consumers and they see the issue and damaged reputation as sitting with the company providing a service to them. Imagine booking a holiday, letting the excitement build up, you and your family arrive at the airport to see countless flights cancelled. No one knows who is to blame or how long it will take to fix it. As far as many people are concerned, their frustration sits entirely with that airline, and that reputational damage may never be clawed back.

Testing 101

Every serious company will have tests, and they will want code to be tested. This could be as simple as a file that you keep in the development environment only, not something you would push out to production.

Types of Tests

Generally, tests can be grouped into three main categories:

Unit Tests

The most common, easiest to implement, and probably the most important to have in an application.

Unit Tests test individual functions/classes to be sure that they do what they are expected to do.

These types of tests are probably the cheapest and easiest to implement.

Integration Tests

Testing how different pieces of code work together. Such as ensuring the database works with Express, or one function works with another function, etc.

Automation Tests

Sometimes called UI Tests, this is about testing in the browser and making sure the application performs as expected. This can be done by humans or can be programmatically completed, essentially using bots, etc.

These types of tests are typically the most difficult to implement and therefore typically the most expensive.

Testing Tools

Testing JavaScript could be tested with more JavaScript; this works, but it is tedious. This is where Testing Tools come in to help us.

Testing Libraries

Libraries are like scaffolding that we use to frame something. In the testing world, common libraries include Jasmine, Jest, and Mocha. We can install these using npm.

Assertion Libraries

We then want an assertion library. Again, common libraries include Jasmine, Jest, and Chai.

Assertion libraries test that your assertions are met. 😵‍💫

Okay, to phrase it a different way, when we define a variable, we expect it to have specific values or value types. We build functions around those expectations; this is an assertion. An Assertion Library checks that your variables contain the data or data types that you're expecting them to contain.

Test Runner

This is something that allows us to run the tests, for example npm run test (it runs tests for us).

Karma.js and Playwright are two popular testing frameworks used for automating browser-based testing. While both frameworks share some similarities, they have distinct differences in their approach, features, and use cases.

Puppeteer is another tool; it's a headless browser from Google. It allows you to do many things, even taking screenshots, etc.

Mock, Spy and Stub

Spy provides information about functions, how many times they were called, and by whom/what, etc.

Stub replaces some of your functions to ensure expected behaviour happens.

Mock is like faking your functions to verify how different parts of the program work together.

Code Coverage

$ npm test -- --coverage

> tests@1.0.0 test
> jest --coverage

 PASS  ./script.test.js
  ● Console

    console.log
      [ 'cats.com', 'souprecipes.com', 'flowers.com' ]

      at Object.log (script.js:17:9)

 PASS  ./script2.test.js
------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |     100 |      100 |     100 |     100 |                   
 script.js  |     100 |      100 |     100 |     100 |                   
 script2.js |     100 |      100 |     100 |     100 |                   
------------|---------|----------|---------|---------|-------------------

Test Suites: 2 passed, 2 total
Tests:       8 passed, 8 total
Snapshots:   0 total
Time:        2.988 s, estimated 3 s
Ran all test suites.

This tells me what percentage of the Statements, Branches, Functions, and Lines are covered by tests.

In this example, that is 100%, but this example was written for a reason and is a small code sample to begin with. The above test relied on Istanbul for code coverage feedback.