How to write tests that need a lot of data?

Does your production code need a lot of data? How do you setup this data for your unit and integration tests? Do you fill a database with a fixed set of data that is to be used by all tests? What if for a new use case you need more data? And how many mocks do you use in your unit tests? Do you have to add more mocks for each use case that you implement? Visit Sander's talk about how to write tests that need a lot of data. We are not talking about big-data, but about combining data from several tables of database to implement a use case. How can you write test code that is easy to read and easy to maintain? Sander shows examples of code for a warehouse management system (WMS) and explains how complex the production code is. Then he shows how the tests for the production code can be implemented and how to set up the test data.

This session took place in track PyData and was classified suitable for some domain / basic python by the speaker.

Transcript (auto)

Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.

Speaker 1 [00:03]

Thank you. My name is Sal McCoy-Mans. I'm a senior developer. I developed software for 18 years right now. Most of the time I developed in Java. Only this March...

Speaker 2 [00:15]

And this March, March this year,

Speaker 1 [00:16]

March this year I switched moved to a new company

Speaker 2 [00:19]

and since I don't work for Protech

Speaker 1 [00:19]

and since then I've work for Protix and Protix I started developing in Python. About Protix,

Speaker 2 [00:25]

about Protix. Protix is like

Speaker 1 [00:26]

Protix is like a farm.

Speaker 2 [00:27]

a a farm that we don't

Speaker 1 [00:28]

But we don't breed pigs or cows, we breed

Speaker 2 [00:30]

cows, we breed insects.

Speaker 1 [00:32]

And the insects we sell to our customers.

Speaker 2 [00:34]

to our customers, and those customers

Speaker 1 [00:36]

Customers turn those insects into

Speaker 2 [00:38]

into feed for different

Speaker 1 [00:39]

for different kinds of animals. And we also sell insects for human consumption.

Speaker 2 [00:44]

consumption. And the whole goal of the company

Speaker 1 [00:45]

of the company Protex is

Speaker 2 [00:46]

protect is to make the feed and food

Speaker 1 [00:48]

feed and food production more sustainable. Right, but since I'm working for Protix quite recently, most of the things I'm working on are still confidential, so I cannot show you code examples.

Speaker 2 [00:58]

code examples from

Speaker 1 [00:59]

from Protex but then

Speaker 2 [01:00]

But in the past I worked quite a long time for a paint factory

Speaker 1 [01:02]

for a paint factory so I want to show you code examples for a warehouse management system of a paint factory let's

Speaker 2 [01:10]

But the trouble was, I worked in Java most of the time.

Speaker 1 [01:12]

So I cannot show you the code example of the real warehouse management system.

Speaker 2 [01:17]

I just rewrote some

Speaker 1 [01:18]

wrote some parts of the warehouse management system just enough code to show you the techniques i want to present so in that sense what i'm going to tell you it's based on a true story first a little bit of names about tests if you have a test you don't need to read the whole code but the one part this

Speaker 2 [01:38]

in this case move better, that's the part we want

Speaker 1 [01:39]

that's the part we want to test that's called the subject under test we have an arranged part

Speaker 2 [01:43]

an arranged part. That's the part of the test where you

Speaker 1 [01:45]

the part of the test where you set up data, for instance if you set up data in a database that's used by the subject on the test.

Speaker 2 [01:50]

The act part, that's the part

Speaker 1 [01:52]

that's the part that actually invokes the subject on a test and finally the assert part that's the part where we validate that the subject on the test did what it was supposed to do so in short what I'm gonna present in this talk is

Speaker 2 [02:10]

How you can write an arranged part of the test that is short

Speaker 1 [02:13]

short few lines of code but generates a lot of data in your database to be used by your test so i want to first quickly introduce you to a warehouse management system

Speaker 2 [02:25]

Then I want to talk about different strategies.

Speaker 1 [02:26]

about different strategies to set up the database for test. Then the main course, the three techniques to set up database for test, and finally Q&A. So the best way to start explaining what a warehouse management system is, is first to explain what a warehouse is.

Speaker 2 [02:39]

warehouses. Well, this

Speaker 1 [02:40]

This is a warehouse. It's just like a large building full of racks.

Speaker 2 [02:44]

full of racks and each rack is full of pallets

Speaker 1 [02:46]

is full of pallets, and each pallet contains, let's say, cans of paint, buckets of paint. The pallets are moved around by operators.

Speaker 2 [02:54]

by operators driving forklifts and this is like a typical plan

Speaker 1 [02:57]

typical plan of warehouses, like the top view.

Speaker 2 [03:01]

The factory is out of scope.

Speaker 1 [03:02]

Out of scope, the factory is where the paint is.

Speaker 2 [03:03]

the paint is actually produced.

Speaker 1 [03:04]

actually produce but pallets with

Speaker 2 [03:05]

the pallets with paint are moved to

Speaker 1 [03:05]

paint and moved to the warehouse and then they enter and they moved when they entered the warehouse they're moved

Speaker 2 [03:11]

warehouse there moved in the bulk area in a wreck in

Speaker 1 [03:13]

area, in a rec and a bulk area.

Speaker 2 [03:14]

The bulk area is used to move

Speaker 1 [03:15]

used to move pallets to replenish empty locations in the pick area.

Speaker 2 [03:19]

area.

Speaker 1 [03:21]

The pick area is location where forklifts or operators driving forklifts will do the order picking on behalf of customers.

Speaker 2 [03:28]

And once a palette has been picked,

Speaker 1 [03:29]

that has been picked containing the items for an order

Speaker 2 [03:30]

items for an order for

Speaker 1 [03:31]

for an order for a customer.

Speaker 2 [03:32]

for customer.

Speaker 1 [03:32]

the pellet is moved to the

Speaker 2 [03:34]

Pallet is moved to the audit area.

Speaker 1 [03:36]

In the audit area, another operator verifies that the contents of the palace are

Speaker 2 [03:39]

of the palace are correct.

Speaker 1 [03:41]

After that, the pallet goes

Speaker 2 [03:42]

but the pallet goes to the wrapping

Speaker 1 [03:43]

to the wrapping area. In the wrapping area, we put a plastic

Speaker 2 [03:46]

a plastic foil around the pallet to keep the items

Speaker 1 [03:48]

to keep the items on the pallet during transport. Next, the pallets go to the staging area, and from the staging area, the pallets are put into the trucks. And finally, the truck driver...

Speaker 2 [03:57]

A truck driver will take the

Speaker 1 [03:58]

we'll take the panels

Speaker 2 [03:59]

the pallets to the customer.

Speaker 1 [04:00]

to the customer. There's also a problem location, and I'll come back to that in a minute.

Speaker 2 [04:06]

So that was like a warehouse in a nutshell.

Speaker 1 [04:07]

warehouse in a nutshell then okay what what is the warehouse management system well that's like

Speaker 2 [04:11]

That's like a software system.

Speaker 1 [04:12]

a software system that supports the warehouse

Speaker 2 [04:13]

supports the warehouse and, well,

Speaker 1 [04:14]

and the goal of the system is order.

Speaker 2 [04:16]

order handling. Customers

Speaker 1 [04:17]

customers.

Speaker 2 [04:18]

or items that have to be delivered.

Speaker 1 [04:19]

that have to be delivered. How does it do that? Well, at low level, a warehouse management system keeps track of everything. It keeps track of the items,

Speaker 2 [04:25]

the items, the orders, the picklists, locations, pallets, notes,

Speaker 1 [04:26]

the orders, Well, that's it, no...

Speaker 2 [04:28]

It knows where everything is.

Speaker 1 [04:29]

where everything is.

Speaker 2 [04:30]

And on a high level,

Speaker 1 [04:32]

It supports a couple of workflows, like stocking, picking, auditing, replenishment, loading.

Speaker 2 [04:38]

loading. A couple of these

Speaker 1 [04:39]

A couple of these workflows I will

Speaker 2 [04:41]

workflows I will show you later during the presentation.

Speaker 1 [04:42]

during the presentation.

Speaker 2 [04:44]

So now it's time to show you some code

Speaker 1 [04:47]

What do I mean with a lot of data in a test?

Speaker 2 [04:51]

many people that's

Speaker 1 [04:52]

that start writing unit tests or maybe follow a course on test-driven development will write a test for, for instance,

Speaker 2 [05:00]

For instance, a class.

Speaker 1 [05:01]

a class for a stack and this is how the test might look like and well there's not much data in there like the strings foo well it's strings not not data in a database but still I consider it like data

Speaker 2 [05:15]

Just one item for test.

Speaker 1 [05:17]

item for test, that's not much. And here, a test case of two.

Speaker 2 [05:19]

Two items, that's not much.

Speaker 1 [05:20]

that's not much. So for a warehouse management system, these

Speaker 2 [05:23]

system, well, depending on

Speaker 1 [05:24]

Depending on the workflow you want to test,

Speaker 2 [05:28]

You need like tens to hundreds

Speaker 1 [05:29]

to hundreds of records in a database.

Speaker 2 [05:35]

Okay, and there are different ways to...

Speaker 1 [05:36]

ways to get that data in a database. Before I joined Prodix, I worked for other companies, also the Paint Factory, other companies, and I've seen different strategies of getting test data in a database. The first question is when do you fill the database? Do you fill it for each test case, which I recommend, or

Speaker 2 [05:56]

Or do you fill the database only once?

Speaker 1 [05:57]

database only once and use the database for all test cases? Rather not. How do you fill a database? Using SQL statements or using code?

Speaker 2 [06:06]

Or using code, yeah.

Speaker 1 [06:07]

Yeah, I prefer using code. The code examples I show you are written

Speaker 2 [06:11]

are written in Python using Django

Speaker 1 [06:12]

using Django and Django has a nice

Speaker 2 [06:13]

and Django has a nice ORM feature which makes it very easy to

Speaker 1 [06:15]

easy to create data in a database. And what data are you going to put in a database? I only put in the minimum set of data that is

Speaker 2 [06:23]

of data that is needed by the

Speaker 1 [06:24]

needed by the subject or not test, maybe even incomplete.

Speaker 2 [06:26]

even incomplete data and

Speaker 1 [06:27]

fleet data and non-realistic data.

Speaker 2 [06:29]

Or are you going to put in complete?

Speaker 1 [06:30]

to put in complete and realistic data, but

Speaker 2 [06:32]

realistic data, but maybe a little bit more than that's strictly needed to test a subject

Speaker 1 [06:35]

to test your subject or not test.

Speaker 2 [06:37]

recommend seconds

Speaker 1 [06:39]

Okay, now here's an example of how we create

Speaker 2 [06:42]

of how we could create a location record.

Speaker 1 [06:44]

records in a database using SQL.

Speaker 2 [06:48]

And I have seen

Speaker 1 [06:49]

I have seen companies that

Speaker 2 [06:51]

import data from a real live

Speaker 1 [06:53]

real life database and store the data.

Speaker 2 [06:56]

in XML or now

Speaker 1 [06:57]

or nowadays we would use JSON for that but I consider that to be just as

Speaker 2 [07:02]

to be just as bad as SQL, I prefer to do it in code.

Speaker 1 [07:05]

encode. So this is how it would look if you

Speaker 2 [07:07]

look if you would like

Speaker 1 [07:08]

would look like if you use Django to

Speaker 2 [07:12]

Insert as one record.

Speaker 1 [07:14]

But if you look at this one line of code, it doesn't look that much better than SQL.

Speaker 2 [07:19]

Better than Sequel, I must admit.

Speaker 1 [07:20]

I must admit. But I'm going to explain you three techniques that will make it so much clear that using code is so

Speaker 2 [07:27]

Using code is so much better to do

Speaker 1 [07:28]

much better to do than

Speaker 2 [07:30]

using SQL.

Speaker 1 [07:31]

SQL to generate a lot of test data for your tests. So the first technique. Now we come to the problem location. Imagine an

Speaker 2 [07:39]

Imagine an operator device.

Speaker 1 [07:39]

operator drives a forklift. He has like three items on the pallet. And then, oh, one of the items, the can of pain falls and it opens. It

Speaker 2 [07:47]

it spills paint so we have to

Speaker 1 [07:49]

so oh we have to clean it up the

Speaker 2 [07:51]

The operator can drive...

Speaker 1 [07:53]

the palette to the problem location and then put the palette there, use a barcode

Speaker 2 [07:57]

use a barcode scanner to scan

Speaker 1 [07:58]

scanner. To scan the barcode of the problem location, that tells the warehouse management system, okay, the pallet has been moved to

Speaker 2 [08:03]

has been moved to the problem location.

Speaker 1 [08:04]

the problem location and at that moment the warehouse management system will

Speaker 2 [08:07]

management system will change the status of the pallet

Speaker 1 [08:09]

of the palette from okay to problem and this is a test scenario for what I just described and I will go through it step by step the step the first thing it does it creates the

Speaker 2 [08:21]

the forklift.

Speaker 1 [08:23]

It creates from location.

Speaker 2 [08:26]

It creates an item

Speaker 1 [08:27]

item

Speaker 2 [08:29]

Then it creates a palette.

Speaker 1 [08:30]

pallets and at this point the pallet is still empty because in this line we're going to add the items on pallets and to be specific we add one

Speaker 2 [08:36]

100 times

Speaker 1 [08:37]

the item on this pallet.

Speaker 2 [08:38]

the Well, this is the range part.

Speaker 1 [08:41]

This is the range part. This puts a couple of records in the database.

Speaker 2 [08:44]

Then we can call the subject

Speaker 1 [08:45]

called the subject of the test, that is, move the palette to the problem location. And finally, we can assert that...

Speaker 2 [08:50]

assert that the palette status has changed to blocked and that the palette actually moved to the

Speaker 1 [08:54]

actually moved to the problem location. If you look at this arranged part

Speaker 2 [08:57]

This arranged part is quite big.

Speaker 1 [08:59]

and a container

Speaker 2 [09:00]

And it contains a lot of detail.

Speaker 1 [09:01]

There's a lot of details that are not

Speaker 2 [09:02]

that are not really relevant

Speaker 1 [09:03]

not really relevant for this test.

Speaker 2 [09:04]

So the first thing I will do typically

Speaker 1 [09:05]

will do typically is refactor the

Speaker 2 [09:06]

is Refractorless Coat.

Speaker 1 [09:07]

And I do that by extracting methods.

Speaker 2 [09:07]

And I do it Now extract these methods, create forklift, create problem location, create item.

Speaker 1 [09:13]

create item, create palette.

Speaker 2 [09:15]

Create pallet. And after extraction,

Speaker 1 [09:15]

And after extraction, the test looks like this.

Speaker 2 [09:17]

It looks like this.

Speaker 1 [09:18]

So the range part is...

Speaker 2 [09:20]

shorter, more clear, I think

Speaker 1 [09:22]

I think the intent of the range part is not

Speaker 2 [09:23]

of the range part is now more clear than it was before.

Speaker 1 [09:27]

um but okay still four lines of code i want to make it even more smaller

Speaker 2 [09:33]

So...

Speaker 1 [09:34]

these variables, forklift, from location, I want to inline these variables so that I can put everything on one line

Speaker 2 [09:40]

Put everything on one line, the whole arranged part on one line,

Speaker 1 [09:42]

part on one line. But I cannot inline the problem location, because if I replace problem location by cell

Speaker 2 [09:47]

Set up the Create Problem Location.

Speaker 1 [09:48]

problem location I would

Speaker 2 [09:49]

I would actually create

Speaker 1 [09:50]

actually create two problem location one in the X part and one in the assert part so yeah I will do this

Speaker 2 [09:57]

How do I solve that?

Speaker 1 [09:58]

Well, I do the...

Speaker 2 [09:59]

I do the inline anyway.

Speaker 1 [10:01]

Anyways.

Speaker 2 [10:02]

but I changed the code a little bit.

Speaker 1 [10:04]

See, I read...

Speaker 2 [10:05]

I inlined the code, but I also renamed create forklift to forklift.

Speaker 1 [10:09]

I renamed create problem location to problem location and I renamed

Speaker 2 [10:13]

Trade item 2, item 1.

Speaker 1 [10:14]

item on but not only renamed the methods also changed implement

Speaker 2 [10:19]

also change the implementation a little bit.

Speaker 1 [10:20]

a little bit, for instance, for a problem

Speaker 2 [10:22]

for problem location, the first time you call problem location, it will

Speaker 1 [10:24]

location, it will create a new problem location in the database.

Speaker 2 [10:27]

And the second time you call it, the third time you call it, the fourth time you call it,

Speaker 1 [10:29]

you call the fourth time you call from location it will return the same problem location that was created the first time

Speaker 2 [10:34]

first time.

Speaker 1 [10:35]

So this way

Speaker 2 [10:36]

Well, we ended up with an arranged path.

Speaker 1 [10:37]

with an arranged path with just one line that puts in like

Speaker 2 [10:40]

that puts in like five records in a database.

Speaker 1 [10:42]

so I think that's a great improvement but now we extracted all

Speaker 2 [10:46]

we extracted all those helper methods.

Speaker 1 [10:49]

probably you want to reuse them because probably you're writing more tests and then you put your tests

Speaker 2 [10:53]

You put your test in different modules, different classes.

Speaker 1 [10:56]

So my next step is to move these helper methods

Speaker 2 [10:58]

these helper methods to a separate module. I call that module a test.

Speaker 1 [11:02]

a test data builder.

Speaker 2 [11:06]

I import the test data builder

Speaker 1 [11:07]

TestDataBuilder while renaming TestDataBuilder to TDD. So it's like a nice and a small, because if I want to use the TestDataBuilder, I can use it like that.

Speaker 2 [11:18]

And using TDP is even smaller than 70%.

Speaker 1 [11:19]

even smaller than itself, so it still looks the same. One line of code to set up the database for your test.

Speaker 2 [11:25]

for your tests.

Speaker 1 [11:26]

And when I move those helpful methods

Speaker 2 [11:28]

those helpful methods to this test data builder moduli.

Speaker 1 [11:30]

build a module i typically tweak them a little bit to make them more reuseable

Speaker 2 [11:32]

to make them more reusable for

Speaker 1 [11:33]

usable for other tests. For instance, well, we started out with one item. Well, typically, we create item one, item two, item three, because many tests need two different items

Speaker 2 [11:44]

different items or three different items

Speaker 1 [11:45]

with three different items and having them already available in the test data builder, it is very easy to write a test that uses two or three different items.

Speaker 2 [11:53]

Same for the forklift, I added

Speaker 1 [11:54]

I added uh you know

Speaker 2 [11:56]

this ID parameter

Speaker 1 [11:58]

parameter, it already has a default.

Speaker 2 [11:59]

It's already has a default value.

Speaker 1 [12:00]

default value, so you can

Speaker 2 [12:01]

So you can call forklift without parameters, and then you get forklift

Speaker 1 [12:03]

and then you get forklift with id forklift01, but sometimes you need a test that needs

Speaker 2 [12:07]

a test that needs two forklifts. Well if you need two forklifts you can call forklift with id is forklift01

Speaker 1 [12:12]

is forklift01, and you can call it with forklift id is forklift02. So it's very easy to write tests that even have more

Speaker 2 [12:18]

More than one forklift, if you want.

Speaker 1 [12:18]

than one forklift. forklift if you want.

Speaker 2 [12:22]

Finally, create blocks.

Speaker 1 [12:23]

create palette, I tweak that

Speaker 2 [12:25]

tweak that a little bit more.

Speaker 1 [12:27]

what does it do?

Speaker 2 [12:29]

Use Django to create valet.

Speaker 1 [12:30]

to create the valid object, and the location parameter

Speaker 2 [12:33]

parameter actually is the only required parameter. The other parameters are optional.

Speaker 1 [12:36]

are optional so it starts by creating this palette on the specified location and then if you specify items item is a dictionary dictionary from

Speaker 2 [12:44]

a dictionary from an item to quantity,

Speaker 1 [12:46]

quantity well for each item quantity you put in a dictionary it will add an items on pallet records

Speaker 2 [12:52]

But that's records.

Speaker 1 [12:53]

with the item and quantity you specified.

Speaker 2 [12:56]

Here I applied one trick that

Speaker 1 [12:57]

One trick that might be handy, each item or palette also

Speaker 2 [13:02]

ItemOnPalette also stores the batch

Speaker 1 [13:03]

the batch ID of that group of items.

Speaker 2 [13:05]

that group of items.

Speaker 1 [13:06]

I use this trick with this global variant

Speaker 2 [13:08]

what is Global Farewell Next Patch

Speaker 1 [13:10]

to generate unique batch IDs

Speaker 2 [13:12]

batch IDs for each item

Speaker 1 [13:12]

each item on palette that you create. As a global variable each

Speaker 2 [13:16]

of variable each time you need a new

Speaker 1 [13:17]

time you need a new unique value, you could just use the next batch variable and then increment that variable. So this is a very handy trick to generate unique values if the database doesn't supply them for you. Right, that was the test data builder, and we're going to see it back a lot. The second technique I want to show you is visualizing

Speaker 2 [13:36]

is visualizing the set of data

Speaker 1 [13:36]

setting the set of data.

Speaker 2 [13:39]

This is the workflow.

Speaker 1 [13:40]

workflow stocking and stocking is when a new palette arrives in the

Speaker 2 [13:42]

pallet arrives in the warehouse, it has to be put in the bulk area.

Speaker 1 [13:45]

in the bulk area and imagine we already decided in which aisle and which rack

Speaker 2 [13:49]

aisle and which rack we want to put it.

Speaker 1 [13:50]

we want to put it, we still have to figure out on what exact location

Speaker 2 [13:53]

exact location in that rack.

Speaker 1 [13:53]

in that rack we're going to put it well we can

Speaker 2 [13:56]

Well, we can fill the racks like row by row

Speaker 1 [13:57]

like row by row.

Speaker 2 [13:59]

But yeah, in real life,

Speaker 1 [14:00]

In real life, the warehouse has like hundreds of locations in one row. So that means that the operator has to drive quite far

Speaker 2 [14:04]

to drive quite far to fill

Speaker 1 [14:05]

to fill one row so that's that's not efficient so we can fill the racks column by column that's more efficient but for forklift moving vertically is a little bit slower than moving

Speaker 2 [14:17]

bit slower than moving horizontally.

Speaker 1 [14:18]

horizontally so we figured out that

Speaker 2 [14:20]

that the best way to fill these racks is to do it directly.

Speaker 1 [14:20]

the best way to is to do it diagonally. So okay, this is a test case that shows you

Speaker 2 [14:29]

where the first pallet in an empty rack should be placed.

Speaker 1 [14:33]

Let's explain what this test does. It starts by creating a palette.

Speaker 2 [14:37]

And I use the test data to build one line of code

Speaker 1 [14:38]

Build one line of code and have a pallet with 100 items

Speaker 2 [14:40]

100 items on it.

Speaker 1 [14:40]

on it. One line of code.

Speaker 2 [14:41]

  1. And then with two nested loops, I can create this.

Speaker 1 [14:44]

I can create this rack, the same rack as we saw in the previous picture.

Speaker 2 [14:50]

It's three lines of code.

Speaker 1 [14:52]

And then we ask, okay, the rack is still

Speaker 2 [14:54]

the rack is still empty, you won't have to

Speaker 1 [14:54]

empty you own applications without pallets so it is empty and we ask where should this pallet

Speaker 2 [14:57]

where should this pallet go? It should go to the first location

Speaker 1 [14:59]

go to the first location at level zero,

Speaker 2 [15:01]

level zero, at ground level. So that's what's

Speaker 1 [15:03]

So that's what's expressed in the assert part. But this is the setup for...

Speaker 2 [15:07]

for an empty rack, still doable in code.

Speaker 1 [15:10]

doable in code, but imagine you want to put a pallet at specific locations.

Speaker 2 [15:14]

locations? Well, the code

Speaker 1 [15:15]

Well, the code might get a bit ugly.

Speaker 2 [15:18]

But looking at this diagram inspires me to

Speaker 1 [15:21]

to represent the setup data using a multiline string.

Speaker 2 [15:26]

So you see the string on the right-hand side.

Speaker 1 [15:27]

string on the right hand side, I use the pipes to indicate like the boundaries of the rack. And

Speaker 2 [15:33]

And then the letter O indicates the positions, the locations that...

Speaker 1 [15:35]

locations that contain a pallet.

Speaker 2 [15:37]

And then the asterisk indicates the location where the next pallet should be.

Speaker 1 [15:40]

The next pallet should be put.

Speaker 2 [15:42]

So actually, we're not only specifying the range part, but even the assert part.

Speaker 1 [15:45]

part with one multiline string. We can write tests like that. I showed you an example of three different tests. Each test has a string that describes the intent of the test, and then comes this multiline string that

Speaker 2 [16:00]

specify the range part and assert part. And on the right hand, you see the code.

Speaker 1 [16:03]

see the code that executes this test. It's a parameterized test. So for each tuple of a description and this multi-line string, each such tuple

Speaker 2 [16:13]

For each such tupple tupple

Speaker 1 [16:13]

this method test

Speaker 2 [16:15]

method TestStockingPalette will be called once.

Speaker 1 [16:18]

And how does this...

Speaker 2 [16:19]

Just work while it gets

Speaker 1 [16:20]

while it gets the description and the smelter lines

Speaker 2 [16:22]

This multiline string is parametric.

Speaker 1 [16:23]

thing as parameter. Well, I

Speaker 2 [16:24]

Well, I ignored the description.

Speaker 1 [16:25]

the description for the test is only used for the developer to

Speaker 2 [16:28]

the developer to read, but yeah, to

Speaker 1 [16:28]

read but you have to understand the intent of the test but then this multi-line string it's a

Speaker 2 [16:33]

assigned to the variable bulk I'll map and then

Speaker 1 [16:35]

and then, okay, how does this test work?

Speaker 2 [16:37]

test work first it builds using the model on string and builds the location

Speaker 1 [16:41]

the locations in the database and it returns the expected location. I will show you the contents of this create location.

Speaker 2 [16:46]

this create locations in all methods in a minute. Then we use the test data builder

Speaker 1 [16:51]

a test data builder again to create a pallet. Then we ask, okay, we have a pallet to what stop location should it move. And finally, we assert that the destination is equal to the expected location.

Speaker 2 [17:04]

So using this multi-line string, okay,

Speaker 1 [17:05]

line string okay it gets a little bit maybe a little bit nasty but just a little bit this method takes this multi-line string and it's going to parse it

Speaker 2 [17:16]

I'll use a regular expression to

Speaker 1 [17:17]

expression to split this smooth line string in one line.

Speaker 2 [17:20]

one line

Speaker 1 [17:21]

an array of one lines.

Speaker 2 [17:24]

I reversed the order of the lines because I want to create

Speaker 1 [17:26]

because I want to create the rack from

Speaker 2 [17:27]

direct from level zero.

Speaker 1 [17:28]

level zero to the top.

Speaker 2 [17:31]

And then we enter an asset loop.

Speaker 1 [17:32]

nested loop and the nested loop will if

Speaker 2 [17:33]

loop will iterate over the lines and per line.

Speaker 1 [17:35]

and per line and it's going to scan character by character

Speaker 2 [17:36]

character by character within.

Speaker 1 [17:37]

actor of a film between

Speaker 2 [17:38]

between those five characters.

Speaker 1 [17:39]

in those pipe characters, and then it checks, okay, for each.

Speaker 2 [17:41]

Each position I have to create a bulk

Speaker 1 [17:42]

I have to create a build location. Again, I can use the test.

Speaker 2 [17:44]

the test data builder for that. Again, just one line. And then if the character happens to be the letter O, we

Speaker 1 [17:49]

We create a palette on that bulk location, again, using the test data builder.

Speaker 2 [17:56]

If the character happens to be the asterisk, we remember the expected

Speaker 1 [17:59]

remember the expected location and there's one thing I didn't tell you yet but if the character is the letter X then it means that the bullet location is blocked

Speaker 2 [18:08]

And if a bulk location is blocked,

Speaker 1 [18:09]

is blocked, that means we are not allowed to put

Speaker 2 [18:11]

you're not allowed to put paddles in there.

Speaker 1 [18:12]

It's not shown in this, in the three test cases before, but it's shown in one of these actual test cases. But see, having this kind of code and visualizing your set of data, it's...

Speaker 2 [18:24]

data it's very easy

Speaker 1 [18:25]

Very easy to

Speaker 2 [18:26]

to write lots and lots of tests. And you can even invite your

Speaker 1 [18:29]

invite like your product owner if the product owner is not a programmer everyone who understands the context can help you write test cases like that so i think it's really a

Speaker 2 [18:37]

I think it's really a powerful technique.

Speaker 1 [18:41]

The final technique I want to show you, the workflow,

Speaker 2 [18:45]

This is a typical workflow

Speaker 1 [18:46]

the typical workflow of

Speaker 2 [18:47]

flow of order handling.

Speaker 1 [18:48]

order handling, customer places order, plan on general support,

Speaker 2 [18:51]

The planner generates a pick list. An operator picks the pick list.

Speaker 1 [18:53]

the pick list, more steps alone. In the end, the

Speaker 2 [18:57]

Yeah, the fella's put in a truck.

Speaker 1 [18:57]

the pallets put in the truck, but each of those steps actually consists of a sub-break flow, like the picking of a pick list.

Speaker 2 [19:06]

It has its subflow on the right.

Speaker 1 [19:07]

flow on the right. It starts with an operator in a forklift picking up an empty pallet, then go to some location in the pick area and then the wearer's management system tells him to which location he should drive, and then how many items should be picked, and then go to the next location, again, pick a number of items, and finally the pallet is put away, and then the operator can pick up the next pallet for the next pick list.

Speaker 2 [19:32]

But having this workflow inspires me.

Speaker 1 [19:34]

inspires me to write

Speaker 2 [19:35]

to write a class, which I will call PickWorkflow,

Speaker 1 [19:38]

that will help you to execute these

Speaker 2 [19:40]

execute these steps in the correct order.

Speaker 1 [19:43]

So this big workflow

Speaker 2 [19:44]

This pick workflow class will get methods like

Speaker 1 [19:46]

at methods like generatePickList, which generates a pick list.

Speaker 2 [19:49]

But it also will have like a method called

Speaker 1 [19:50]

a method called pick and you can specify which items should be picked

Speaker 2 [19:53]

But you can call this pick method.

Speaker 1 [19:55]

all this big methods without explicitly calling

Speaker 2 [19:57]

explicitly calling generatePixels

Speaker 1 [19:58]

generate picklist. And then this pick method will detect that no picklist was generated and it will

Speaker 2 [20:03]

will ensure that all the necessary steps

Speaker 1 [20:05]

the necessary steps that have to be taken in advance,

Speaker 2 [20:08]

will be executed for you.

Speaker 1 [20:09]

for you so let's show

Speaker 2 [20:10]

let's show it in code. I think maybe it's easier to explain in code. This is the pick workflow class. It has only one required parameter. It's the items.

Speaker 1 [20:17]

It's the items, and that is a dictionary with items.

Speaker 2 [20:20]

item quantities and that are the

Speaker 1 [20:21]

and that are the items of the order.

Speaker 2 [20:23]

the customer ordered certain items.

Speaker 1 [20:25]

certain items. We create a service,

Speaker 2 [20:28]

service class

Speaker 1 [20:28]

class that contains the business logic.

Speaker 2 [20:29]

that contains

Speaker 1 [20:31]

It has methods like generate picklist, move palette. The forklift, we store the forklift. If you didn't specify a forklift explicitly, we use a test data builder to

Speaker 2 [20:39]

a test data builder to get you one

Speaker 1 [20:40]

to get you one, we create the order using the

Speaker 2 [20:42]

order using the test data builder.

Speaker 1 [20:43]

That's Data Builder.

Speaker 2 [20:44]

We remember the picklist we were working on, well initially

Speaker 1 [20:47]

well initially it's none

Speaker 2 [20:49]

We remember the big bell

Speaker 1 [20:50]

Pick palette is actually the palette that's now on your forklift.

Speaker 2 [20:53]

on your forklift.

Speaker 1 [20:54]

That's the palette on which you're putting the item.

Speaker 2 [20:55]

putting the items and we also have a list called picked pallets where we

Speaker 1 [21:00]

That's where we remember all the pick palettes you have seen so far. Imagine some orders are so big that you need more

Speaker 2 [21:06]

you need more than one palette to pick

Speaker 1 [21:07]

on one palette to pick them, so this

Speaker 2 [21:09]

So this list pick palettes.

Speaker 1 [21:09]

list picked palettes remembers all palettes that belong

Speaker 2 [21:11]

all pallets that belong to this one order.

Speaker 1 [21:12]

to this one order. And then you can specify if you should generate pick locations. Many tests are not really interested from which locations you actually want to pick. So if that's not relevant, you can say, okay, generate the pick locations for me.

Speaker 2 [21:27]

And then this big workflow,

Speaker 1 [21:27]

And then this pick... workflow we'll look at

Speaker 2 [21:29]

We'll look at the order and we'll create

Speaker 1 [21:30]

create pick locations for the items in the

Speaker 2 [21:32]

for the items in the order and we'll put

Speaker 1 [21:33]

order and we'll put pallets on the pick locations with enough stock

Speaker 2 [21:35]

so you can pick the entire order.

Speaker 1 [21:35]

so you the entire order.

Speaker 2 [21:39]

And then, for instance, the pick method.

Speaker 1 [21:43]

Yeah, you can only pick after picklist has been generated. So how do we ensure

Speaker 2 [21:46]

How do we ensure that all the steps in the workflow are

Speaker 1 [21:48]

are executed in the correct order,

Speaker 2 [21:49]

executed in the correct order, well, this is one way to

Speaker 1 [21:50]

This is one way to implement that. The pick method starts the calling, ensure picklist

Speaker 2 [21:54]

ensure picklist is generated

Speaker 1 [21:54]

is generated. And that method only checks

Speaker 2 [21:57]

If there's already a pick list,

Speaker 1 [21:58]

a picklist defined, and if not, it will generate a picklist right then.

Speaker 2 [22:03]

And next, well, you

Speaker 1 [22:03]

Well, you can only pick pick items if there is a

Speaker 2 [22:05]

if there is a pallet on your forklift. So we ensure that the pick pallet is created

Speaker 1 [22:08]

pig palette is created. And how do you do that? Again, we check this

Speaker 2 [22:11]

at this detectives pick palette variable, if it's none,

Speaker 1 [22:14]

If it's none, then we create a palette on the forklift.

Speaker 2 [22:16]

on the forklift and we remember this palette in

Speaker 1 [22:18]

this pallet in the picked pallet list.

Speaker 2 [22:22]

And after that, we can perform the actual pick by

Speaker 1 [22:24]

actual pick by asking the service method to which location you should drive with the forklift and then pick the items from that location. So how can you use this workflow? Well, imagine we're testing picking. One of the flows is you have an order.

Speaker 2 [22:41]

You have an order of five items, which

Speaker 1 [22:42]

which is expressed here.

Speaker 2 [22:44]

express here and you already picked two items

Speaker 1 [22:46]

In this case, I generate the pick location explicitly.

Speaker 2 [22:50]

I call location P1.

Speaker 1 [22:51]

P1. So we

Speaker 2 [22:52]

So we create an order of five items

Speaker 1 [22:53]

five items and we already pick two items that's just expressed by two lines of code.

Speaker 2 [22:57]

And then we go to...

Speaker 1 [22:58]

we ask, okay, how many items should I still pick if I go to location P1?

Speaker 2 [23:02]

in P1 and the answer

Speaker 1 [23:03]

And the answer should be three.

Speaker 2 [23:03]

should be

Speaker 1 [23:05]

But the whole setup, these three lines of code, will generate a lot of data in your database.

Speaker 2 [23:08]

an order, a pick list, a

Speaker 1 [23:10]

a big palette on a forklift, just three lines of code.

Speaker 2 [23:14]

I think it'd even get better if you

Speaker 1 [23:16]

get better if you

Speaker 2 [23:17]

have to go further into the

Speaker 1 [23:18]

further into the flow, maybe even

Speaker 2 [23:19]

flow, maybe even go all the way in the flow.

Speaker 1 [23:20]

in the flow for instance imagine you want to put pellets inside the trucks

Speaker 2 [23:24]

inside the truck, you have to pick the pallets, put them in the truck

Speaker 1 [23:25]

Let's put them in a truck.

Speaker 2 [23:26]

Before the truck leaves, you have to generate

Speaker 1 [23:28]

you have to generate a transport notice.

Speaker 2 [23:30]

A transport notice is a document

Speaker 1 [23:31]

a document or a file you send to the transport company and it just describes the contents in the in the truck so describes which pallets are in there

Speaker 2 [23:39]

which palettes aren't there and what

Speaker 1 [23:39]

there, and what are the items on the pallet. Well, if you want to test this for three pallets,

Speaker 2 [23:44]

from three different orders.

Speaker 1 [23:48]

Well, you only need three lines of code.

Speaker 2 [23:50]

we create three times a big workflow specified

Speaker 1 [23:51]

specify the contents of the orders and say, pick and put pallet in a truck. It's just three lines of code to get three pallets, but on the route, it will create three orders, three pick lists. It will pick those orders and move

Speaker 2 [24:04]

and move the palace to the picture.

Speaker 1 [24:04]

the palace to the piglets. Three lines of code.

Speaker 2 [24:07]

Then we can generate the tick list of the

Speaker 1 [24:08]

pick list or the transport notice,

Speaker 2 [24:10]

And we can assure that the transfer notice

Speaker 1 [24:11]

transport notice contains the correct contents so to conclude

Speaker 2 [24:17]

I've shown you three techniques, test data builder, visualizing the

Speaker 1 [24:19]

visualizing the set of data and

Speaker 2 [24:20]

setup data and the workflow, and if you use

Speaker 1 [24:22]

And if you use these techniques, you can set up your database for each test case with complete and realistic data with just a few lines of code.

Speaker 2 [24:29]

A few lines of code.

Speaker 1 [24:31]

So, to finalize, if you want...

Speaker 2 [24:33]

Finally, if you want to learn more

Speaker 1 [24:34]

want to learn more about me, my hobbies, my open source projects, I

Speaker 2 [24:38]

worked on and why I draw these

Speaker 1 [24:39]

draw these funny pictures check out my website the contents of what i just talked to you and explained to you are available on my website just scan the qr code then there's a post that explains everything with the same code samples even more complete code samples so

Speaker 2 [24:57]

Thank you for your attention.

Speaker 1 [24:58]

Thank you for your attention.

Speaker 3 [25:05]

Thank you, Sandra, for the very insightful talk. I have to confess, when I do my Python code testing, it heavily relies on print statements, so this was a whole other world of testing. We have five minutes for questions, so please just do raise your hand and I'll run over to you.

Speaker 4 [25:33]

Yes, hi. Thank you for a great talk and just a little advice. It might be a good idea to start using PyTest fixtures to separate creation of test data from the actual testing method, because then you can reduce even amount of code instead of that three lines of code. But when you create actual data, you can just put one argument or three arguments to the method. It will be a huge simplification.

Speaker 1 [26:10]

Okay, I would love to find out more about that, but then I wonder, is the test data still near the code?

Speaker 2 [26:17]

near the code that executes the test or it's some are completely different.

Speaker 1 [26:20]

Some are completely different because that's something new for me.

Speaker 4 [26:23]

Yes, just a point to discover, so that might be a good idea.

Speaker 1 [26:28]

Okay, I would love to talk about that. Maybe in the evening during the party at IBM.

Speaker 2 [26:35]

maybe during lunch break or during another break i'd love to hear more

Speaker 1 [26:39]

hear more about that

Speaker 3 [26:41]

Very well. Thanks for the suggestion. Anybody has...

Speaker 5 [26:54]

Hi, thanks for the talk and the nice examples. I was curious if you could speak a little bit to the performance or maybe some of the trade-offs in terms of the database operations required to set up these complex structures. Do you find that that ends up being a significant proportion of the test running time? Are there ways to maybe speed that up when you have lots of objects that need to be created every single time? What are your thoughts on that?

Speaker 2 [27:18]

Good question.

Speaker 1 [27:20]

As I said, the Paint Factory, in the end, we wrote, I think, about 16,000 tests. Most of them used the database. I must admit that there we even used the database.

Speaker 2 [27:32]

used SQL statements or something.

Speaker 1 [27:33]

statements ourselves to create the structure of the database. We didn't have a nice ORM like Django over there. But to fill the data in the database, in the end, we started using these kind of techniques. I think the running time was mostly determined by...

Speaker 2 [27:51]

building the tables and

Speaker 1 [27:52]

tables and executing the SQL scripts to build the tables. Filling like 10, 20, 100 records in a database per test wasn't the biggest concern for us in the end.

Speaker 5 [28:02]

So sorry to monopolize. Do you run into any problems related to if you have data migrations that, for example, change the structure of something like a palette or just to say more generally, if you have data migrations or schema migrations where you need to basically.

Speaker 1 [28:17]

to basically uh

Speaker 5 [28:18]

apply migrations, having them defined in code, I could imagine is maybe a liability because then you can't so easily just apply the migrations to that. You have to actually make changes in the code. Do you find that that's a concern?

Speaker 1 [28:31]

or how do you get around that? I think it's even quite easy to support migrations because if you use Django, you use Django for the database migrations. But for instance, if you say, we're gonna add one more column to the pallet table, it means that in the test data builder, you maybe have to change a couple of methods that they

Speaker 2 [28:49]

methods that they also take care

Speaker 1 [28:50]

also take care of this new property of that palette but that you only do that like in the in the test

Speaker 2 [28:56]

in the Test Data Builder and all

Speaker 1 [28:56]

data build and all tests that use a test data builder get this new column basically for free. If you, at least if you can use like a default value for a new column. And only probably also gonna add a few more tests

Speaker 2 [29:08]

a few more tests that will attest

Speaker 1 [29:09]

test, that will test this new property. So you have to add a couple of more tests that are going to use that new property. And yeah, in my experience, I think it's really easy to use to deal with changes in the database with the test data builder instead of writing SQL scripts to fill the data for your test cases.

Speaker 2 [29:29]

So for us, yeah, it was never a problem.

Speaker 1 [29:30]

never a problem using these techniques.

Speaker 3 [29:35]

Yeah, just one question from that side of the room, and then we'll have to wrap it up.

Speaker 4 [29:51]

Hi, thanks for the talk. I like the idea of the test data builder we are also using at our company. I was just wondering for the examples that you presented.

Speaker 1 [30:00]

I thought that...

Speaker 4 [30:02]

I would rather write unit tests for that and not having every single test rely on the full database integration. And as you just said, most of your tests in the paint factory were actually using the databases. Maybe you have a comment on that as well.

Speaker 1 [30:15]

Yeah, we indeed made a swing. We started out writing unit tests with lots of mocks in there. And in the end, it bit us so hard because we had quite a lot of complex logic in our service layer. And it turned out that if you want to use unit tests where you have to mock out the data layer, then the setup of those tests was so complex. And it made it harder to refactor code or to reimagine.

Speaker 2 [30:42]

or to re-implement certain parts of this logic,

Speaker 1 [30:44]

parts of this logic. So for us, it really paid out by using those tests that use the database for basically all test cases. So yeah, they perform maybe a little bit, take a little bit longer to run. But in the end, for us, it was much easier to write the test. The tests were also easier to understand because they are written like at a high level describing a whole workflow. And if you only have a unit test that only focus on one

Speaker 2 [31:08]

focus on one small part

Speaker 1 [31:08]

small part with a very small

Speaker 2 [31:09]

with a very specific setup.

Speaker 1 [31:11]

up, yeah, for us, it didn't break

Speaker 2 [31:11]

Yeah, for us, Break out at all

Speaker 1 [31:13]

out.

Speaker 3 [31:15]

Right. Thank you very much for attending. Please stay in for the next talk about PyWTF. That's the description. And for those who want to leave, please leave, but do give a round of applause for the speaker.

Sander Kooijmans

Sander Kooijmans graduated in Computer Science from the Eindhoven University of Technology in 1999 and in Software Technology Program of the Stan Ackermans Institute in 2001. After that he worked for several companies on the domains of digital television, medical and logistics.

His hobbies include piano, volleyball and programming. In his free time writes open source applications and libraries, such as a chess game, a logbook, a bookkeeping application and a library with text search algorithms.

Sander loves sharing knowledge and experience and he has been a speaker at several conferences in Europe.

You can find out more about Sander at his website: https://gogognome.nl

Social card for talk: How to write tests that need a lot of data?