0

When writing unit tests, I was often using following pattern:

with self.subTest("Invalid input") and self.assertRaises(ValueError):
  ...

But only today I learned that according to python specs, I should be using , here:

with self.subTest("Invalid input"), self.assertRaises(ValueError):
  ...

And the specifications don't mention and as an option. Yet the tests always seemed to be working fine.

What are the possible problems when using and here? Why does it seem to usually work the same way as ,?

Related: Multiple variables in a 'with' statement?

2 Answers2

1

What you have is an expression with the and operator. The and operator returns its first operand if it is falsey, or its second operand otherwise. Assuming self.subTest(...) returns something truthy, your code is equivalent to:

ctx = self.subTest("Invalid input") and self.assertRaises(ValueError)
with ctx: ...

Which is equivalent to:

self.subTest("Invalid input")
ctx = self.assertRaises(ValueError)
with ctx: ...

Or:

self.subTest("Invalid input")
with self.assertRaises(ValueError): ...

So, at best, what you have may be misleading. At worst it's a bug, since subTest's context manager isn't being used.

If self.subTest returns a falsey value, then self.assertRaises(...) is never executed, which would be a clear bug in your test.

deceze
  • 510,633
  • 85
  • 743
  • 889
1

Adding to the excellent answer of @deceze, you can observe that yourself quite easily: you just need to create your own context manager.

Run the following code:

class C:
    def __init__(self, id):
        self.id = id
    def __enter__(self):
        print(f'Entering {self.id}')
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        print(f'Exiting {self.id}')
        return self

with C(0), C(1): print('with statement 1')
print()
with C(2) and C(3): print('with statement 2')

You'll see that the following output is printed:

Entering 0
Entering 1
with statement 1
Exiting 1
Exiting 0

Entering 3
with statement 2
Exiting 3

What happens is that for the first with statement, both values are entered in order, then exited in reverse order (these are the semantics of Python's with statement). But in the second with, only the second item is processed (entered then exited) - because the first is thrown away by the and operator.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77