Search by Algolia
How personalization boosts customer engagement
e-commerce

How personalization boosts customer engagement

You land on your favorite retailer’s website, where everything seems to be attractively arranged just for you. Your favorite ...

Jon Silvers

Director, Digital Marketing

What is retail analytics and how can it inform your data-driven ecommerce merchandising strategy?
e-commerce

What is retail analytics and how can it inform your data-driven ecommerce merchandising strategy?

There is such tremendous activity both on and off of retailer websites today that it would be impossible to make ...

Catherine Dee

Search and Discovery writer

8 ways to use merchandising data to boost your online store ROI
e-commerce

8 ways to use merchandising data to boost your online store ROI

New year, new goals. Sounds positive, but looking at your sales data, your revenue and profit aren’t so hot ...

John Stewart

VP, Corporate Communications and Brand

Algolia DocSearch + Astro Starlight
engineering

Algolia DocSearch + Astro Starlight

What is Astro Starlight? If you're building a documentation site, your content needs to be easy to write and ...

Jaden Baptista

Technical Writer

What role does AI play in recommendation systems and engines?
ai

What role does AI play in recommendation systems and engines?

You put that in your cart. How about this cool thing to go with it? You liked that? Here are ...

Catherine Dee

Search and Discovery writer

How AI can help improve your user experience
ux

How AI can help improve your user experience

They say you get one chance to make a great first impression. With visual design on ecommerce web pages, this ...

Jon Silvers

Director, Digital Marketing

Keeping your Algolia search index up to date
product

Keeping your Algolia search index up to date

When creating your initial Algolia index, you may seed the index with an initial set of data. This is convenient ...

Jaden Baptista

Technical Writer

Merchandising in the AI era
e-commerce

Merchandising in the AI era

For merchandisers, every website visit is an opportunity to promote products to potential buyers. In the era of AI, incorporating ...

Tariq Khan

Director of Content Marketing

Debunking the most common AI myths
ai

Debunking the most common AI myths

ARTIFICIAL INTELLIGENCE CAN’T BE TRUSTED, shouts the headline on your social media newsfeed. Is that really true, or is ...

Vincent Caruana

Senior Digital Marketing Manager, SEO

How AI can benefit the retail industry
ai

How AI can benefit the retail industry

Artificial intelligence is on a roll. It’s strengthening healthcare diagnostics, taking on office grunt work, helping banks combat fraud ...

Catherine Dee

Search and Discovery writer

How ecommerce AI is reshaping business
e-commerce

How ecommerce AI is reshaping business

Like other modern phenomena such as social media, artificial intelligence has landed on the ecommerce industry scene with a giant ...

Vincent Caruana

Senior Digital Marketing Manager, SEO

AI-driven smart merchandising: what it is and why your ecommerce store needs it
ai

AI-driven smart merchandising: what it is and why your ecommerce store needs it

Do you dream of having your own personal online shopper? Someone familiar and fun who pops up every time you ...

Catherine Dee

Search and Discovery writer

NRF 2024: A cocktail of inspiration and innovation
e-commerce

NRF 2024: A cocktail of inspiration and innovation

Retail’s big show, NRF 2024, once again brought together a wide spectrum of practitioners focused on innovation and transformation ...

Reshma Iyer

Director of Product Marketing, Ecommerce

How AI-powered personalization is transforming the user and customer experience
ai

How AI-powered personalization is transforming the user and customer experience

In a world of so many overwhelming choices for consumers, how can you best engage with the shoppers who visit ...

Vincent Caruana

Senior Digital Marketing Manager, SEO

Unveiling the future: Algolia’s AI revolution at NRF Retail Big Show
algolia

Unveiling the future: Algolia’s AI revolution at NRF Retail Big Show

Get ready for an exhilarating journey into the future of retail as Algolia takes center stage at the NRF Retail ...

John Stewart

VP Corporate Marketing

How to master personalization with AI
ai

How to master personalization with AI

Picture ecommerce in its early days: businesses were just beginning to discover the power of personalized marketing. They’d divide ...

Ciprian Borodescu

AI Product Manager | On a mission to help people succeed through the use of AI

5 best practices for nailing the ecommerce virtual assistant user experience
ai

5 best practices for nailing the ecommerce virtual assistant user experience

“Hello there, how can I help you today?”, asks the virtual shopping assistant in the lower right-hand corner ...

Vincent Caruana

Senior Digital Marketing Manager, SEO

Add InstantSearch and Autocomplete to your search experience in just 5 minutes
product

Add InstantSearch and Autocomplete to your search experience in just 5 minutes

A good starting point for building a comprehensive search experience is a straightforward app template. When crafting your application’s ...

Imogen Lovera

Senior Product Manager

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