1

I have the following code:

def test_transformation_last_price(self):
    data_set = etl.fromdicts([{'MTDReturn': 4, 'EffectiveDate': '1992-06-30'},
                              {'MTDReturn': 3.2, 'EffectiveDate': '1992-07-31'}])
    last_price_dataset = self.parser.last_price_dataset(data_set)
    first_row = list(etl.dicts(last_price_dataset))[0]
    expected_row = {'TimeSeriesValue': 121.20923188958272,
                    'EffectiveDate': datetime.date(1992, 6, 30),
                    'FundID': self.parser.FUND_ID,
                    'TimeSeriesTypeID': self.parser.LAST_PRICE_ID}
    self.assertEqual(first_row, expected_row)

My method: last_price_dataset runs a few other methods that essentially grab a value from a database and produces some calculations based on it. At the moment this passes, and it's correct. However, that value might change by a few decimal points here and there.

Is there a unittest I can use that checks if the TimeSeriesValue is close that number in the dict?

AssertAlmostEqual doesn't work with dicts like that. Any suggestions?

3 Answers3

2

I had the same problem today. So I simply build my own method assertDictAlmostEqual.

def assertDictAlmostEqual(self, d1, d2, msg=None, places=7):

    # check if both inputs are dicts
    self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
    self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')

    # check if both inputs have the same keys
    self.assertEqual(d1.keys(), d2.keys())

    # check each key
    for key, value in d1.items():
        if isinstance(value, dict):
            self.assertDictAlmostEqual(d1[key], d2[key], msg=msg)
        else:
            self.assertAlmostEqual(d1[key], d2[key], places=places, msg=msg)
derphelix
  • 100
  • 1
  • 9
1

FWIW, here's the wrapper function I wrote just now to do a "deep" assert over a dict/list.

import unittest
from collections.abc import Iterable, Mapping
from itertools import zip_longest

# thanks to https://stackoverflow.com/questions/32954486/zip-iterators-asserting-for-equal-length-in-python
def zip_equal(*iterables):
    sentinel = object()
    for combo in zip_longest(*iterables, fillvalue=sentinel):
        if sentinel in combo:
            raise ValueError('Iterables have different lengths')
        yield combo

def assert_deep(instance: unittest.TestCase, method, first, second, *args, path=tuple(), **kwargs):
    instance.assertIsInstance(second, type(first))
    if isinstance(first, Mapping):
        instance.assertEqual(first.keys(), second.keys())
        for key in first:
            subtest_path = path + (key,)
            with instance.subTest(subtest_path):
                assert_deep(instance, method, first[key], second[key], path=subtest_path, *args, **kwargs)
        return instance

    # thanks to https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable
    if isinstance(first, Iterable):
        index = 0
        try:
            for first_el, second_el in zip_equal(first, second):
                # avoid infinite recursions on iterables that return themselves as elements, e.g. strings
                if first_el is first:
                    return method(first, second, *args, **kwargs)
                subtest_path = path + (index,)
                with instance.subTest(subtest_path):
                    assert_deep(instance, method, first_el, second_el, *args, path=subtest_path, **kwargs)
                index += 1
        except ValueError as e:
            instance.fail(e)
        return instance

    return method(first, second, *args, **kwargs)    

class AssertDeepTest(unittest.TestCase):
    def test_assert_deep_ok(self):
        assert_deep(self, self.assertAlmostEqual, 
            {'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]}, 
            {'a': [0.101, 0.201], 'b': [0.301, 0.401, 0.501]}, 
            places=2)
        assert_deep(self, self.assertEqual,
            {'a': [1, 1.0, 'a'], 'foo': [2, 'bar']},
            {'foo': [2, 'bar'], 'a': [1, 1.0, 'a']})

    @unittest.expectedFailure
    def test_assert_deep_fail_method(self):
        assert_deep(self, self.assertAlmostEqual, 
            {'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]}, 
            {'a': [0.101, 0.201], 'b': [0.301, 0.401, 0.501]}, 
            places=3)

    @unittest.expectedFailure
    def test_assert_deep_fail_keys(self):
        assert_deep(self, self.assertAlmostEqual, 
            {'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]}, 
            {'a': [0.101, 0.201], 'c': [0.301, 0.401, 0.501]}, 
            places=2)

    @unittest.expectedFailure
    def test_assert_deep_fail_types(self):
        assert_deep(self, self.assertAlmostEqual, 
            {'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]}, 
            {'a': [0.101, 0.201], 'b': (0.301, 0.401, 0.501)}, 
            places=2)

    @unittest.expectedFailure
    def test_assert_deep_fail_lens(self):
        assert_deep(self, self.assertAlmostEqual, 
            {'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]}, 
            {'a': [0.101, 0.201], 'b': [0.301, 0.401, 0.501, 0.601]}, 
            places=2)
Yuval
  • 3,207
  • 32
  • 45
0

You could use TestCase.assertAmostEqual(please read more here - https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual):

assertAlmostEqual(first, second, places=7, msg=None, delta=None)

For instance, if the precision is 0 decimal places then the following test passes:

from unittest import TestCase
from unittest import main

class Test(TestCase):
    def test_1(self):
        dict1 = {'TimeSeriesValue': 121.20923188958272}
        dict2 = {'TimeSeriesValue': 121.3}
        self.assertAlmostEqual(dict1['TimeSeriesValue'], dict2['TimeSeriesValue'], 1)

if __name__ == '__main__':
    main()

OK

For your test it could be like:

# number of decimal places is 0
self.assertAlmostEqual(expected_row['TimeSeriesValue'], first_row[<your key here>], 0)
Anatolii
  • 14,139
  • 4
  • 35
  • 65