Literate programming (LP) is good for a lot of things: it keeps your code and documentation in sync, lets you present code in the most logical order, and helps remove stubborn duplication. But most people are unfamiliar with how it works for testing.
And for testing, LP soars—it may very well be LP’s killer feature. You can mock, fake, and stub easily—all without a framework. It works just as well for C/C++ or other compiled languages as for scripting languages. It gives you a clear way to declare which dependencies you're faking and which you're not. And it turns testing into a joy by wiping away boilerplate and letting you think directly in your target language. Integration tests become as easy as unit tests.
Once you try it, you’ll never want to go back.
Mock a C function
Let me whet your appetite with an example. We’ll build a simple program in C—a language notoriously difficult to mock without resorting to linker tricks (like `-Wl,--wrap=<func>`
) or other ugly maneuvers. Not so with LP. Check it out.
Here’s a function that prints the current date repeatedly. It takes one argument: how many times to print the date.
Below is a literate file1 called `main.o.md`
2.
# Application Code
## main
```C {tangle=main.c}
#include <stdio.h>
#include <time.h>
@<print_date_repeatedly_def@>
int main()
{
print_date_repeatedly(10);
}
```
## print_date_repeatedly
```C {name=print_date_repeatedly_def}
void print_date_repeatedly(int count)
{
for (int i = 0; i < count; i++)
{
time_t now = time(NULL);
struct tm *local = localtime(&now);
printf("%s", asctime(local));
}
}
```
## Build and Run App
```bash {name=app}
gcc main.c -o app
./app
```
To run, type the following in terminal in the same directory as this file:
omd tangle && omd run app
You should see the following (with a different date/time though):
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Sat Jun 14 13:38:40 2025
Pretty easy. But now let’s try to test it.
That’s not so easy.
Let’s simplify and say we trust the date/time code, but we still want to test that the output is printed the correct number of times. That should be easy, right? Nope.
Ideally, we’d just call `print_date_repeatedly()`
with different values for count
and check that it prints the expected number of lines. But there’s a problem: the output goes to `stdout`
, so you have to capture it somehow and count the lines.
Here’s how you can test it using LP. Just add this section to your `.o.md`
file:
# Testing Code
```C {tangle=print_date_repeatedly.c}
#include <stdlib.h>
#include <time.h>
int times_called = 0;
int printf( const char* restrict format, ... )
{
times_called++;
}
@<print_date_repeatedly_def@>
void test_calls(int count)
{
times_called = 0;
print_date_repeatedly(count);
// was I called the right number of times?
if(times_called != count)
{
exit(-1);
}
}
int main()
{
test_calls(0);
test_calls(1);
test_calls(2);
test_calls(3);
test_calls(10);
test_calls(100);
// everything passed
exit(0);
}
```
```bash {name=test}
gcc print_date_repeatedly.c -o app_test
if ./app_test; then
echo "Success!"
else
echo "Failure! Exit code: $?"
fi
```
This creates a separate test binary that returns `-1` on failure and `0` on success. To run the test:
omd tangle && omd run test
You should see:
Success!
Notice how we use the `@<print_date_repeatedly@>`
reference to include the same code in both the application and the test. Referencing code this way acts like an automated cut-and-paste—but better. Instead of importing everything (with all its dependencies), you bring in just the code you want.
In this test, for example, we omit `#include <stdio.h>`
so we can safely mock `printf`
. And we could do the same thing for any function called by `print_date_repeatedly`
—system call or not.
And the really remarkable thing? We don’t need a library or framework to mock it.
And this is C! Try doing that without LP.
A Clean Split
With LP, you can place the test code right next to the function it’s testing. No need to hunt through some distant `tests/` directory. In fact, I recommend splitting the code above into two files: one for `main`, and one for the function (with its tests):
main.o.md
# main
```C {tangle=main.c}
#include <stdio.h>
#include <time.h>
@<print_date_repeatedly_def@>
int main()
{
print_date_repeatedly(10);
}
```
# Build and Run App
```bash {name=app}
gcc main.c -o app
./app
```
print_date_repeatedly.o.md
# print_date_repeatedly
```C {name=print_date_repeatedly_def}
void print_date_repeatedly(int count)
{
for (int i = 0; i < count; i++)
{
time_t now = time(NULL);
struct tm *local = localtime(&now);
printf("%s", asctime(local));
}
}
```
# Testing Code
```C {tangle=print_date_repeatedly.c}
#include <stdlib.h>
#include <time.h>
int times_called = 0;
int printf( const char* restrict format, ... )
{
times_called++;
}
@<print_date_repeatedly_def@>
void test_calls(int count)
{
times_called = 0;
print_date_repeatedly(count);
// was I called the right number of times?
if(times_called != count)
{
exit(-1);
}
}
int main()
{
test_calls(0);
test_calls(1);
test_calls(2);
test_calls(3);
test_calls(10);
test_calls(100);
// everything passed
exit(0);
}
```
## Run Tests
```bash {name=test}
gcc print_date_repeatedly.c -o app_test
if ./app_test; then
echo "Success!"
else
echo "Failure! Exit code: $?"
fi
```
With time, you can add more functions in their own files—each with its own tests—and manage them in a clean, modular way. Everything stays isolated and easy to understand. Add some docs and links between files, and you’ll have a first class literate program!
Conclusion
Literate programming isn’t just a clever way to organize files—it’s a fundamentally different way of thinking. Your code and your thinking grow side by side. Your tests live where they belong: next to the ideas they verify. You stop writing tests as an obligation, and start writing them because they’re part of the story.
I’m using Organic Markdown, a simple, markdown-based literate system. For more on this particular flavor of LP, check out the GitHub repo here.
The .o.md
stands for Organic Markdown. The “o
” is separated from the md
so that you can use any Markdown editor you want. I started off using .omd
, but many editors wouldn’t open it because they didn’t recognize the extension.