decision tables

source | test results

ns
itl.example.bank
itl.example.milk
(usens:)
Cash in Walletcredit cardunits remaininggo to store?
0no0no
10no0yes
0yes0yes
10yes0yes
0no1no
10no1no
0yes1no
10yes1Expected 'nope' but got 'no'
(dt:buy-milk? parallel=true)

This is the most basic, and simplest form, of table processing function. If you look at the source code for the buy-milk? function, you'll see some interesting magic. First, the parameter list doesn't look like a normal function. Instead, it has some data coercion functions declared in it. This syntax allows for a simple translation between the strings that are pulled out of the markdown into whatever data your assertion fixture needs in order to do its work.

You'll also notice that there are spaces in the table headers, and some of them are capitalized, but the keys in the function definition are all lower case have dashes in them. This is a convention used by the decision table processor so you can easily refer to data in standard Clojure ways. In a sense, all you're doing is taking a map, operating on it, and returning a map of data to be asserted against.

Finally, there's an argument passed to dt that enables parallel execution. Even though this is a trivial example, it's important to note that each row in this table can be executed and checked in parallel by simply sending this flag. This can be useful when using decision tables to do lots of long-running calculations.

page state

Here's an example of something slightly more complex. What if we wanted to work with a system that generated random values that we then had to use to look stuff up? Like, say, a database?

Here's a simple approach based on our very simple banking app:

namebalanceid?
Alice25{:alice 5e439583-d698-4e0a-a751-7612cefcd3f1}
Stephen5{:stephen 17337f72-3824-497b-87f2-f311c0e94a6e}
Add some users (dt:add-user)
idamountbalance?id?
:alice -> 5e439583-d698-4e0a-a751-7612cefcd3f1530.05e439583-d698-4e0a-a751-7612cefcd3f1
:alice -> 5e439583-d698-4e0a-a751-7612cefcd3f1-1020.05e439583-d698-4e0a-a751-7612cefcd3f1
:alice -> 5e439583-d698-4e0a-a751-7612cefcd3f11535.05e439583-d698-4e0a-a751-7612cefcd3f1
:stephen -> 17337f72-3824-497b-87f2-f311c0e94a6e510.017337f72-3824-497b-87f2-f311c0e94a6e
:larry ->25
Add a bunch of transactions (dt:update-balance)

An important thing to note for itl test pages: each page has a map (that starts out empty) associated with it. As tables are executed, that map is passed into and out of them. This map can be manipulated by tables. We call this current-state.

We call a cell whose header ends with ? an output cell. All others are input cells. So, if an output cell contains a value that starts with =:, then an assertion does not happen. Instead, the output is stored in current-state and printed to the screen.

Then, if an input cell contains a value that starts with :, instead of using that value, we go look in current-state and retrieve the value with that name. That is the value that gets passed to your helper function.

Another thing: bindings that are created in one row cannot be used in a different row of the same table. This is because processing rows can happen in parallel. Therefore, you should use one table to bind values, and a separate row to use them.

If you bind the same value multiple times, the last binding will always supercede the previous one. This is because rows are executed in parallel, but result processing happens in serial.

more to come...

There's much more to be done on this fixture (see its SliM counterpart for more), but it's a good start!