Posts tagged ‘contextlib’

Specializing TestCase.assertRaises

2014-02-09 15:52

When writing tests, ideally you should verify your code’s behavior not only in the usual, “happy” cases, but also in the erroneous ones. Although you may very well accept that a function blows when feed with incorrect data, it should blow up predictably and consistently. An error, exception or panic is still an output; and it should be possible to capture and examine it in tests.

Raise!

The Python unittest module has a couple of ways to deal with expected error. Probably the most useful among them is the TestCase.assertRaises method. It does pretty much exactly what it names hints to: asserting that a piece of code raises a specific type of exception:

  1. from unittest import TestCase
  2. import myproject.somemodule as __unit__
  3.  
  4. class Foo(TestCase):
  5.     def test_none(self):
  6.         self.assertRaises(TypeError, __unit__.foo, None)

In Python 2.7 (or with the unittest2 shim library), it can be also used much more conveniently as a context manager:

  1. class Foo(TestCase):
  2.     def test_none(self):
  3.         with self.assertRaises(TypeError):
  4.             __unit__.foo(None)

To clarify, assertRaises will execute given block of code (or a callable, like in the first example) and throw AssertionError if an exception of given type was not raised by the code. By calling the tested function with incorrect data, we intend to provoke the exception, affirm the assertion, and ultimately have our test pass.

Be more specific

I mentioned, however, that it doesn’t just matter if your code blows up in response to invalid input or state, but also how it does so. Even though assertRaises will verify that the exception is of correct type, it is often not nearly enough for a robust test. In Python, exception types tend to be awfully broad, insofar that a simple information about throwing TypeError may tell you next to nothing about the error’s true nature.

Ironically, designers of the unittest module seemed to be vaguely aware of the problem. One of their solutions, though, was to introduce a proverbial second problem, taking the form of assertRaisesRegexp method. While it may kinda-sorta work in simple cases, I wouldn’t be very confident relying on regular expressions for anything more complex.

Especially when the other possible approach appears much more sound anyway. Using the feature of with statement, we can capture the exception object and examine it ourselves, in a normal Python code. Not just the type or message (though these are typically the only reliable things), but also whatever other data it may carry:

  1. class Foo(TestCase):
  2.     def test_none(self):
  3.         with self.assertRaises(TypeError) as r:
  4.             __unit__.foo(None)
  5.         self.assertIn("NoneType", str(r.exception))

Sometimes, those checks might grow quite sophisticated. For example, in Python 3 you have exception chaining; it allows you to look not only at the immediate exception object, but also its __cause__, which is analogous to Throwable.getCause in Java or Exception.InnerException in C#. If you need to dig this deep, I’d suggest extracting a function with all that code – essentially a specialized version of assertRaises, preferably with all the context manager goodness that would enable us to use it like the original.

As it turns out, this can be very simple.

Tags: , , , ,
Author: Xion, posted under Programming » Comments Off on Specializing TestCase.assertRaises

Working within Temporary Directory

2012-03-24 15:35

Few days ago I needed to write a script which was supposed to run inside a temporary directory. The exact matter was about deployment from an ad hoc Git repository, and it’s something that I may describe in more detail later on. Today, however, I wanted to focus on its small part: a one that (I think) has neatly captured the notion of executing something within a non-persistent, working directory. Because it’s a very general technique, I suppose quite a few readers may find it pretty useful.

Obtaining a temporary file or even directory shouldn’t be a terribly complicated thing – and indeed, it’s very easy in case of Python. We have a standard tempfile module here and it serves our needs pretty well in this regard. For one, it has the mkdtemp function which creates a temporary directory and returns path to it:

  1. temp_dir = tempfile.mkdtemp()

That’s what it does. What it doesn’t do is e.g ensuring a proper cleanup once the directory is not needed anymore. This is especially important on Windows where the equivalent of /tmp is not wiped out at boot time.
We also wanted our fresh temp directory to be set as the program’s working one (PWD), and obviously this is also something we need to manually take care of. To combine those two needs, I think the best solution is to employ a context manager.

Context manager is basically a fancy name for an object that the with statement can be applied upon. You may recall that some time ago I wrote about interesting use cases for the with construct. This one could also qualify as such, but the principles are very typical. It’s about introducing a scope where some resource (here: a temporary directory) remains accessible as long as we’re inside it. Once we leave the with block, it is cleaned up – just like file handles, network sockets, concurrent locks and plenty of other similar objects.

But while semantics are pretty clear, there are of course several ways to do this syntactically. I took this opportunity to try out the supposedly simplest one which I learned recently on local Python community meet-up: the contextlib library. It includes the contextmanager decorator: a simple and clever way to write with-enabled objects as simple functions. It is based on particular usage of yield statement which makes it very interesting even by itself.

So without further ado, let’s look at the final solution I wanted to present:

  1. import os
  2. import shutil
  3. import tempfile
  4. from contextlib import contextmanager
  5.  
  6. @contextmanager
  7. def temp_directory(*args, **kwargs):
  8.     """Allows the program to operate inside temporary directory.
  9.    Sets the app's working dir automatically and restores it
  10.    to original one upon existing the `with` clause.
  11.    """
  12.     orig_workdir = os.getcwd()
  13.     temp_workdir = tempfile.mkdtemp(*args, **kwargs)
  14.     os.chdir(temp_workdir)
  15.  
  16.     yield temp_workdir
  17.  
  18.     os.chdir(orig_workdir)
  19.     shutil.rmtree(temp_workdir)

As we can see, yield divides this function into two parts: setup and cleanup. Setup will be executed when we enter the with block, while cleanup will run when we’re about to exit it. By the way, this scheme of multiple entry and exit points in one function is typically referred to as coroutine, and it allows for several very intriguing techniques of smart computation.

Usage of temp_directory function is pretty obvious, I’d say. Here’s a simplified excerpt of the Git-based deployment script that I used it in:

  1. import subprocess
  2. shell = lambda cmd: subprocess.call(cmd, shell=True)
  3.  
  4. orig_repo = os.getcwd()
  5. with temp_directory():
  6.     shell('git clone --shared %s .' % orig_repo)
  7.     shell('./build')
  8.     shell('git add -f ' + build_products)
  9.     shell('git commit -m "%s"' % message)
  10.     shell('git push %s master' % deploy_remote)

Note how the meaning of '.' (current directory) shifts depending on whether we’re inside or outside the with block. Users of Fabric (Python- and SSH-based remote administration tool) will find this very similar to its cd context manager. The main difference is of course that directory we’re cd-ing to is not a predetermined one, and that it will disappear once we’re done with it.

Tags: , , ,
Author: Xion, posted under Programming » Comments Off on Working within Temporary Directory
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.