Setting up PostgreSQL for running integration tests
gajus.com
gajus.com
Basically you create docker container from some postgres image.
Then you run DDL scripts.
Then you stop this container and commit it as a new image.
And now you can create new container from this new image and use it for test. You can even parallelize tests by launching multiple containers. It should be fast enough thanks to docker overlay magic.
And it should work with any persistent solution, not just postgres.
it really enabled end to end level testing as well as being able to stand up development instances quickly.
With docker you can build out the test image with default usernames/passwords/etc...
Then as your install gets more complicated with stored procedures and the like you can add them to your test database and update local testing tooling and CI/CD to use that.
The massive benefit here is that you're using the exact same code to power your tests as you use to power your production systems. This eliminates issues that are the caused by differences between prod & test environments and anyone who's debugged those issues know how long they can take because it can take a really long time to figure out that is where the issue lies.
This will be much faster than restarting postgres a bunch of times, since this will just `cp` the database files on disk from the template to the new database.
The only "problem" is your application will need to switch to the new database name. You can also just put pgbouncer in front and let it solve that, if you want.
> However, on its own, template databases are not fast enough for our use case. The time it takes to create a new database from a template database is still too high for running thousands of tests:
And then in the timing shows that this took about 2 seconds. Launching another container is surely going to be at least that slow, correct?
So it's clear the author is trying to get an "absolutely clean slate" for each of potentially many tests. That may not be what all teams need, but I will say we had an absolute beast of a time as we grew our test suite that, as we parallelized it, we would get random tests failures for tests stepping on each other's toes, so I really like the approach of starting with a totally clean template for each test.
Your solution requires spinning up hundreds of docker images per test run..
We do the same thing as described with MS SQL; takes about 1 sec to get a fresh DB that way.
While MS SQL takes 30 seconds or something from Docker image.
- Follow this guide to disable all durability settings. We don’t care if the DB can recover from a crash, since it’s only test data: https://www.postgresql.org/docs/current/non-durability.html (I wouldn’t worry about unclogged tables, personally)
- Set wal_level=minimal, which requires max_wal_senders=0. This reduces the amount of data written to [mem]disk in the first place.
- The big one: create a volume in /var/run/postgresql/ and share it with your application so that you can connect over the Unix domain socket rather than TCP. This is substantially faster, especially when you create new connections per test (or per thread).
With PostgreSQL at the beginning of the test the outer transaction is opened, then connection is shared with application, test steps are performed (including Selenium etc), requests open/close nested transactions etc. Once steps are done the test closes the outer transaction and the database is back to initial state for the next test. It is very simple and handled by the framework.
In fact it can be even more sophisticated. E.g. an integration test class can preload some data (fixtures) for all the test cases of the class. For that the another nested transaction is used to not repeat the data load process for every test case.
MariaDB doesn't have nested transactions, however in that case RoR uses SAVEPOINTs mechanism. https://mariadb.com/kb/en/savepoint/
-- Prevent new connections to template db
update pg_database
set datallowconn = false
where datname = 'my_template_db';
-- Close all current connections
select pg_terminate_backend(procpid)
from pg_stat_activity
where datname = 'my_template_db';https://medium.com/@kova98/easy-test-database-reset-in-net-w...
For Java services using MySQL, I was able to use just the H2 database (in-memory) many times. Does a decent job and it's very compatible with MySQL. If you try to avoid specific features from the databases, this in-memory database can do a decent (and fast) job running integration tests.
This includes a full build of the application, postgres container startup and spring context startup.
Starting the postgres container seems be less than 2 seconds of those 8 seconds.
Is that fast enough?
However, on its own, template databases are not fast enough for our use case. The time it takes to create a new database from a template database is still too high for running thousands of tests:
postgres=# CREATE DATABASE foo TEMPLATE contra;
CREATE DATABASE
Time: 1999.758 ms (00:02.000)
So no. They say this adds ~33 minutes to running their tests.I know the article title says "integration tests" but when a lot of functionality is done inside PostgreSQL then you can cover a lot of the test pyramid with unit tests directly in the DB as well.
The test database orchestration from the article pairs really well with pgTAP for isolation.
You could use a zfs fs for your cluster, snapshot it, then mount it in a new cluster. This would prevent copying the data, as zfs would do copy on write. So you get your isolation, and speed on large DB's. You do need to run a new postgres cluster though.
Each time the hash of our migrations change, we make a new template DB. That takes about 30 seconds to run all our migrations..
If the hash didn't change, clone the template of the given hash. This takes less than 1 second.
Some advantages not mentioned in article:
- When developing locally, a new DB clone is made to run my single test interactively. If this took much more than a second I would get rather annoyed.
- Yes a test suite can share a DB for full suite if all tests are written without hardcoding anything and provisiom new IDs always etc. But it is super useful to run a function QueryDump("select * from MyTable") in the test of the code I am working on and the full table dump only having what that single test worked on during debugging, speeds up debugging a lot vs querying for whatever random ID my test allocated.
The OP mentions lack of extensions being a blocker for them using it now, they are coming soon. Additionally it's currently limited by being single connection, and again I hope we can remove this limitation.
I also have a few other ideas around how to make supper fast testing possible.
Another thing to contemplate is utilizing the multi tenancy systems you have in place for isolation. It can be a good way to test that the tenant isolation actually works.
It can run in parallel if you name schemas randomly, or by test name.
You only need one DB, no need to even re-connect, start DB cluster or whatever. It can all work with a single persistent connection.
And it's much less resource intensive.
But yea, DELETEing is faster than creating a DB from template (depends on how much you have to delete, of course). However, templates allow parallelism by giving its test its own database. I ended up doing a mix of both: create a DB if there's not one available, or reuse one if available, always tearing down data.
Pretty neat, I didn't know about it before.
There is testcontainers + flyway/liquibase. Problem solved.
Running the full suite of tests is about 5-10 mins on a relatively low-powered build server, whereas on my more robust development workstation it's about 3 mins. Getting a TestContainer running an empty DB for integration tests ends up being a very small portion of the time.
Most of the time any work being performed is done in a transaction and rolled back after the test is completed, though there are some instances where I'm testing code that requires multiple transactions with committed data needing to be available. The one ugly area here is that in some of these outlier test cases I need to add code to manually clear out that data.
Are you talking about a single DB reused for each test? That is of course no problem...
Our suite spends 3 to 10 minutes to run too. It provisions about a hundred databases (takes 1 sec each DB template clone.. the wall clock is running stuff in parallell)
Also, every time we change code and run a related test locally we make a new DB to run its test. If that took more than 1 second I would go crazy.