Search by Algolia
Building a performant search bar in Nuxt with Algolia & Storefront UI
engineering

Building a performant search bar in Nuxt with Algolia & Storefront UI

In today's highly competitive e-commerce landscape, providing customers with a seamless and efficient search experience can make all ...

Jakub Andrzejewski

Senior Developer and Dev Advocate at Vue Storefront

How to use AI to build your business
ai

How to use AI to build your business

The world of technology is constantly evolving with generative Artificial Intelligence (AI) currently leading the charge. We’re suddenly surrounded ...

Abhijit Mehta

Director of Product Management

Haystack EU 2023: Learnings and reflections from our team
ai

Haystack EU 2023: Learnings and reflections from our team

If you have built search experiences, you know creating a great search experience is a never-ending process: the data ...

Paul-Louis Nech

Senior ML Engineer

What is k-means clustering? An introduction
product

What is k-means clustering? An introduction

Just as with a school kid who’s left unsupervised when their teacher steps outside to deal with a distraction ...

Catherine Dee

Search and Discovery writer

Feature Spotlight: Synonyms
product

Feature Spotlight: Synonyms

Back in May 2014, we added support for synonyms inside Algolia. We took our time to really nail the details ...

Jaden Baptista

Technical Writer

Feature Spotlight: Query Rules
product

Feature Spotlight: Query Rules

You’re running an ecommerce site for an electronics retailer, and you’re seeing in your analytics that users keep ...

Jaden Baptista

Technical Writer

An introduction to transformer models in neural networks and machine learning
ai

An introduction to transformer models in neural networks and machine learning

What do OpenAI and DeepMind have in common? Give up? These innovative organizations both utilize technology known as transformer models ...

Vincent Caruana

Sr. SEO Web Digital Marketing Manager

What’s the secret of online merchandise management? Giving store merchandisers the right tools
e-commerce

What’s the secret of online merchandise management? Giving store merchandisers the right tools

As a successful in-store boutique manager in 1994, you might have had your merchandisers adorn your street-facing storefront ...

Catherine Dee

Search and Discovery writer

New features and capabilities in Algolia InstantSearch
engineering

New features and capabilities in Algolia InstantSearch

At Algolia, our business is more than search and discovery, it’s the continuous improvement of site search. If you ...

Haroen Viaene

JavaScript Library Developer

Feature Spotlight: Analytics
product

Feature Spotlight: Analytics

Analytics brings math and data into the otherwise very subjective world of ecommerce. It helps companies quantify how well their ...

Jaden Baptista

Technical Writer

What is clustering?
ai

What is clustering?

Amid all the momentous developments in the generative AI data space, are you a data scientist struggling to make sense ...

Vincent Caruana

Sr. SEO Web Digital Marketing Manager

What is a vector database?
product

What is a vector database?

Fashion ideas for guest aunt informal summer wedding Funny movie to get my bored high-schoolers off their addictive gaming ...

Vincent Caruana

Sr. SEO Web Digital Marketing Manager

Unlock the power of image-based recommendation with Algolia’s LookingSimilar
engineering

Unlock the power of image-based recommendation with Algolia’s LookingSimilar

Imagine you're visiting an online art gallery and a specific painting catches your eye. You'd like to find ...

Raed Chammam

Senior Software Engineer

Empowering Change: Algolia's Global Giving Days Impact Report
algolia

Empowering Change: Algolia's Global Giving Days Impact Report

At Algolia, our commitment to making a positive impact extends far beyond the digital landscape. We believe in the power ...

Amy Ciba

Senior Manager, People Success

Retail personalization: Give your ecommerce customers the tailored shopping experiences they expect and deserve
e-commerce

Retail personalization: Give your ecommerce customers the tailored shopping experiences they expect and deserve

In today’s post-pandemic-yet-still-super-competitive retail landscape, gaining, keeping, and converting ecommerce customers is no easy ...

Vincent Caruana

Sr. SEO Web Digital Marketing Manager

Algolia x eTail | A busy few days in Boston
algolia

Algolia x eTail | A busy few days in Boston

There are few atmospheres as unique as that of a conference exhibit hall: the air always filled with an indescribable ...

Marissa Wharton

Marketing Content Manager

What are vectors and how do they apply to machine learning?
ai

What are vectors and how do they apply to machine learning?

To consider the question of what vectors are, it helps to be a mathematician, or at least someone who’s ...

Catherine Dee

Search and Discovery writer

Why imports are important in JS
engineering

Why imports are important in JS

My first foray into programming was writing Python on a Raspberry Pi to flicker some LED lights — it wasn’t ...

Jaden Baptista

Technical Writer

Looking for something?

facebookfacebooklinkedinlinkedintwittertwittermailmail

A while back, a tweet came up on my timeline that asked the Twitterverse to scare a product manager in three words. Among the wonderful responses were “one more thing…”, “pretty much done”, “that’s legacy code”, and the perhaps underrated “I tested it”. That last one stuck with me because it’s perhaps the scariest of them all.

It leads to so many more questions: how did you test it? Did you cover all of the edge cases? Was the test accurate to how the piece will actually be used? Did the test only check the expected outcome? Did the test cover if the change may have broken something else in the application? Thankfully, us developers have come up with a good system to answer these questions systematically and thoroughly, so let’s explore the two major types of automated testing in this article.

Unit tests

Before we get started building unit tests, it’s good to take a step back and ask why we need them. In general, we want to make sure that our apps are working! Unit tests are just little pieces of code that verify that things are functioning as they should. To understand how unit tests do this, though, it’s important to first realize that modern development involves modularity (splitting functionality into the smallest pieces possible). Once we chunk up our large tasks into these small functions, many of them will happen to be pure functions: deterministic functions that always return the same output given the same input, without affecting or being affected by anything outside of the function.

Those properties make pure functions straightforward to test, since we can just simulate how those functions are going to be used and check the output against what it’s supposed to be. This is what we mean by “unit test”: using the function we want to test a couple of times with fake data and making sure that we get the correct results. For example, take the famous Sigmoid function, written in Python, with a simple unit test beneath it:

import math

def sigmoid(x):
	return 1 / (1 + math.exp(-x))

def test_sigmoid():
	assert sigmoid(0) == 0.5
	assert 0.9999 < sigmoid(10) < 1
	assert 0.2689 < sigmoid(-1) < 0.269
	assert sigmoid(-100000000000000000) == 0

Sure enough, that last line reveals a bug in our code. Who would have thought that the math library in Python couldn’t handle numbers like -100000000000000000? In hindsight, it’s obvious to me — the positive version we’re feeding into math.exp is slightly outside the range of a double, but I didn’t think about that before! This little bug demonstrates well that we can’t know all of the things that there are to know about our tools. Even though the formula here is technically correct, the tool that I’m using can’t handle the edge case of manipulating such a large number. Thankfully, math.exp will handle this fine if we give it a negative number (which is a positive input into sigmoid, as we give math.exp with -x) because the result just rounds to 0, but in the case that x is positive, we need to write the function slightly differently:

def sigmoid(x):
	if x >= 0:
		return 1 / (1 + math.exp(-x))
	else:
		ex = math.exp(x)
		return ex / (1 + ex)

Now, our unit test returns without setting off any alarms! And if our PM happens to think of an edge case we didn’t, it’s trivial to add another assert to the testing function.

What if our function to test isn’t pure, though? Here’s one idea: refactor whatever you can into a new pure function so that the impure function contains as little as possible. For example, take this JavaScript function, which gets a string value from the DOM before running the Caesar cipher on it:

const runCaesarCipherOnInput = () => {
	const input = document.getElementById("input");
	const result = document.getElementById("result");
	const key = 4;
	
	result.innerText = input
		.value
		.toUpperCase()
		.split("")
		.map((letter, i) => {
	    const code = input.charCodeAt(i);
	    if (65 <= code && code <= 90) {
				let n = code - 65 + key;
				if (n < 0) n = 26 - Math.abs(n) % 26;
				return String.fromCharCode((n % 26) + 65);
	    } else return letter;
		})
		.join("");
};

This works! But it’s difficult to unit test because of all that’s going on. It’s not deterministic (that is, the function doesn’t always return the same output for a given input), and it contains a few side effects that reach outside of the scope of the function. Even the internal loop function inside the .map call isn’t pure! How can we fix this?

Let’s try to pull everything making the function impure out for now, leaving us with a few bits of easily-testable functionality, and then we’ll rebuild the pieces into the original function again.

const caesar = (msg, key, rangeStart=65, rangeEnd=91) => msg
	.split("")
	.map(letter => shiftLetter(letter, key, rangeStart, rangeEnd))
	.join("");

const shiftLetter = (letter, key, rangeStart, rangeEnd) => {
	const rangeLength = rangeEnd - rangeStart;
	const code = letter.charCodeAt(0);
	if (rangeStart <= code && code < rangeEnd) {
		let n = code - rangeStart + key;
		if (n < 0) n = rangeLength - Math.abs(n) % rangeLength;
		return String.fromCharCode((n % rangeLength) + rangeStart);
	} else return letter;
};

const runCaesarCipherOnInput = () => {
	const input = document.getElementById("input");
	const result = document.getElementById("result");
	const key = 4;

	result.innerText = caesar(input.value.toUpperCase(), key);
};

Notice in this example, we still have two impure functions: the function fed to .map on line 3 and runCaesarCipherOnInput. However, these functions are simple enough that they don’t need to be tested! They aren’t actually doing any logic (they both primarily exist to feed pure functions arguments), and the constant declarations and DOM reads are fairly straightforward to understand, so we can safely just expect that they’ll do what we want. I could have gone even further if I used a for loop instead of map, but the point here is that creating unit tests often won’t require a big change from you as the developer. This minor rewrite accomplishes the same purpose, it’s easier to read, and it’s now testable — the important logic bits like caesar and shiftLetter are pure functions, so we can just write assertion tests for them like before:

const testCaesar = () => {
	if (caesar("HELLO", 1) != "IFMMP") throw "Assertion error"; // test basic case
	if (caesar("these are all lowercase letters, so nothing should happen", 8) != "these are all lowercase letters, so nothing should happen") throw "Assertion error"; // test lowercase
	if (caesar(caesar("DECRYPTED TEXT", 19), -19) != "DECRYPTED TEXT") throw "Assertion error"; // test negative keys for decryption
};

const testShiftLetter = () => {
	if (shiftLetter("L", 3, 65, 91) != "O") throw "Assertion error"; // test basic case
	if (shiftLetter("s", 14, 65, 122) throw "Assertion error"; // test wrap around, and custom ranges
};

These functions should go wherever their logic counterparts do! To make it easier to run batch tests, it might be helpful to package the unit tests into logical groups, or even to use a dedicated unit testing framework that handles the boilerplate for you.

But what happens when we’re trying to test something that’s not so easily encapsulated in a function? It’s not as straightforward to test UI elements and entire workflows with simple unit tests. What can we do next? Check out part 2 in this series: simulating the entire browser.

About the author
Jaden Baptista

Technical Writer

Recommended Articles

Powered byAlgolia Algolia Recommend

How to build a simulator for any feature — Part 2: Browser emulation
engineering

Jaden Baptista

Technical Writer

Building a Store Locator in React using Algolia, Mapbox, and Twilio – Part 3
engineering

Clément Sauvage

Software Engineer, Freelance

How to use Algolia as a game engine debugging tool in Rust
engineering

Gyula László

Senior Developer @ Starschema