Testing dependencies in Python

STX Next
4 min readMar 3, 2020

I’m often faced with the idea that testing an external dependency, especially something like a database, is pointless. After all, they have their own tests and they work reliably unless something goes terribly wrong, which may not even be in our control.

This is what I call the “test retrofitting thinking”:

  • I have a dependency, it does something for me, so what’s the point of testing it?

The TDD thinking is very different:

  • I have an assumption about the system I’m building and I want that assumption to be true.
  • I don’t really care about how that’s facilitated. It may be done by my own code or by some external service (say, Elasticsearch), but I don’t care.

So how would I approach it? Let’s use an example:

Now this example has a couple of problems, not the least of which is naming, but more importantly it’s unrealistic for the sake of brevity. Usually the act of retrieving and sending the data it’s even encapsulated, defeating the possibility of changing these libraries or services. But discussing encapsulation, important as it is, is slightly beyond the scope.

Anyway, from the test perspective what normally happens is one of two things:

  1. A festival of mocking. get_data gets mocked, send_data gets mocked and the actual operations behind them aren’t tested anywhere.
  2. Desperate attempts at testing anything instead of everything, manifested by checking if the low-level library was called with the presumably correct arguments/query. This rarely tests anything and usually leads to duplicating the query generation logic in the test.

Run it!

What you should do instead is think of it as another piece of code to run on something and actually run it.

Because really, where do you draw the line? If your language of choice is Python, then how do you validate your python instructions? You write a test which runs them on the Python interpreter and asserts on the result. So how do you test your instructions for an ORM or an external payment service? The same way — you write a test which runs them on that database or service.

Let’s say you want get_data to return, possibly among other things, a list of all user IDs. You would write a test like this:

Notice I don’t care at all how get_data really does its thing. SQL, NoSQL, memory, external service — doesn’t matter, as long as the users_batch fixture prepares data as accessible to get_data. That’s the only assumption I need to make.

This means I can (with the assumption above) change the implementation of get_data on the fly, without affecting this test. If I can get users_batch IDs from both Postgres and from Redis, I can switch between them easily and the test won’t even notice.

From here it’s just the standard pass the test → improve → repeat ‘till happy cycle.

As for the whole some_data_transfer_foo function, you can still use mocks (or, better, fakes), but now you’re sure that once you plug the real thing in their place it will not fall apart. Which, if you ask me, is quite a liberating feeling.

This is the way I usually approach it with TDD, but with some refactoring it’s possible with retro-fitting as well — you just need the right mindset of caring about what instead of how.

Yeah, but availability…

Of course this all hangs on one assumption — you have a test instance of the service you can actually use in your tests. Preferably available from your developer machine and from your deployment pipeline.

Ideally, it would be available constantly, but some external services only allow you this luxury for the development period. What then?

Even in those cases, I found programming this way to be advantageous. It forces you to isolate and encapsulate, which is generally a good thing, and it speeds things up because you’ve automated the state preparation process.

What if you don’t ever have that external system available to you? What if all you have is docs and (costly) production?

Well… if it’s self-hosted, then why isn’t it in docker-compose? Come on!

If it’s not — why are you even integrating it?! If they can’t provide you with a test/dev environment, just walk away. Let me talk to your manager for a moment… It’s simply not worth it. I know the sales pitch was good and that system looks like the second coming right now, but in 2 months you’ll be contacting legal to walk from that 2 year contract.

Written by Peter Podgórski, Senior Python Programmer at STX Next

--

--

STX Next

We are Europe’s #Python Powerhouse. Our Agile crew of over 400 developers stands ready to make our code your superpower. Reviews: http://bit.ly/stxreviews