software testing, assertions
Tags: python, testing, sideprojects
Published: 2020-07-11 15:16:43

I work on a testing framework which validates application by sending and receiving messages with the proprietary protocol. And certainly, there are a lot of assertions with most of them checking the value of the field, some checks the type of message received and some ordering of messages. All of these assertions are grouped by functionality in multiple functions across multiple files. Having a lot of assertions is not a problem but not showing a clear message is certainly a problem. Imaging test being failed with something like this

```python
... long stack trace ...
AssetionError: True != False
```

looking at it just gives a basic idea that something should have been `True` but was not! If you read stack trace you can figure out where the assertion failed, but there is a big "if you read" Nobody reads it! Then we get a lot of links to failed jobs with a sweet message, "tests are failing". 

This is due to having lot of bare `asserts`, when we started developing the tests our the main focus was converting manual tests to automated and that's why we have lot of asserts lying around in our code base.

The second pain point was not that big deal, but surely good to address is, when we check new message structure and values, its fails one by one. If message has 20 fileds then you can imagine how long it will take to figure out all wrongly populated fields. I wanted to report all failures at once so that I can ask a developer to take a look at all in one go. There is already a solution to this, `delayed_assert` module in python, but that is a wrapper around assert which stores stack trace and dumps in the end and it also meant that i need to replace all assets.

I decided to solve both the problems in one go, I searched and found few assertion libraries in python, one particular I liked was `assertpy` because it has clean API and it has `assert_warn` function which would just report the errors (without stack trace) but won't stop the execution. My idea was, write a wrapper which would return `assert_that` or `assert_warn` depending upon a certain condition.

```python
# some wrapper file
import os
from assertpy import assert_that, assert_warn

def get_assertor():
   if bool(os.getenv("ERRORS_AS_WARN", False)):
       return assert_warn
   else:
       return assert_that

assert_that = get_assertor()
```

also the API os `assertpy` is just brilliant 

```python
from assertpy import assert_that
from nose.tools import assert_equal

# not very easy to understand
assert_equal(1 + 3, 2, "4 is not equal to 2")

# its like reading a sentance
assert_that(1 + 3, "addition").is_equal_to(2)
```

I decided I will use `assertpy` but then I started implementing and I realized,
 
1) we only use a handful of assertions and regular ones that we use are not in `assertpy` 

2) though assertion methods can be easily plugged in `assertpy`, requirement of such function is the first argument named as `self`, which felt bit odd
```python
def is_in_between(self, other): 
   ...
```
so I checked the code and realized that for every assert_that, a new instance of the class is created and extensions are added on every initialization, I thought its extra work for little gain.

3) my dynamic error to the warning was working but it was printing the wrong location of the source as it was looking for `assert_warn` in stack trace but I was referring it to as `assert_that`, in short it was only working partially.

Adding PR to `assertpy` seemed worthless as all solutions felt like a hack and then I decided to write one which is based on `assertpy` but has very basic functionality but easy to extend.

I created `simple-assertions`, after thinking many times whether to re-invent the wheel or not...

# how I solved the above issues?

1) I created a class with a handful of common assertions, the class was picked as you can easily inherit to extend the functionality.
```python
from simple_assertions import SimpleAssertions
from typing import Union

class YourAssertions(SimpleAssertions):
    def __init__(self, as_warn=False, logger=None):
        super().__init__(as_warn, logger)

    def is_greater_than(self, other: Union[int, float]):
        if self.val_to_chk.val < other:
            self.error(self.generate_err_msg(other, "to be greater than"))
        return self


class YourTestClass:
    def __init__(self):
        self.assert_that = YourAssertions().assert_that

    def test_something(self):
        self.assert_that(4 + 10).is_greater_than(10).is_equal_to(14)
        self.assert_that(1).is_instance_of(int)
        self.assert_that(3, "lucky_num", as_warn=True).is_equal_to(4)      
```

2) With composition, an instance of assertion class can be created and then that single instance can be reused. IDE can also help me code completion. 

```python
class MyTests(unittest.Testcase):
    def setUp(self):
        self.assert_that = SimpleAssert().assert_that
```

3) I changed the API of `assert_that` and instead of 2 functions, 1 for error and 1 for the warning, I added an optional parameter to create warnings, also added an environment variable which if set will convert all error to warnings. 


> Use of environment variable to change the behaviour is topic for discussion but for my needs, it was a perfect solution!


```python

# instead of, assert_warn(10).is_equal_to(9)

assert_that(10, as_warn=True).is_equal_to(9)

```

I am planning to add a few more assertions so that it can be used without need to adding anything. If you want to give it a try, all you have to do is 
```
pip install simple-assertions
```
and you can check the source here please check here https://github.com/ninadmhatre/simple-assertions