0

I am trying the unitest to raise the value error when the value is negative using the code below.

A raise error is added : number should be greater than 0 But when I run the code below:

from functools import reduce
import math
import unittest

def calculate_factorial(number):
    if number < 0:
        print('about to throw value error')
        raise ValueError('number should be greater than 0')
    elif type(number) != int:
        raise  TypeError('number should be an integer type')
    else:
        data = []
        for i in range(number):
            data.append(number - i)
            print(data)
        results = reduce((lambda x, y: x * y), data, 1)
        return results

class TestCalc(unittest.TestCase):
    def test_factorial(self):
        print('Function is starting to check for values')
        print()
        result = calculate_factorial(n)
        print('results are:',result)
        print()
        self.assertEqual(result,math.factorial(n))
        
        
    def test_n(self):
        print('The value of n taken by the class function is:',n)
        

run = True
while run:
    n = int(input('Enter an integer value: '))
    if n != -9999:
        unittest.main(argv=[''], verbosity=2, exit=False)
    else:
        run = False

I am getting the error as following. I can see below that my raise value is getting passed through but somehow the test class is not considering it.

test_factorial (__main__.TestCalc) ... ERROR
test_n (__main__.TestCalc) ... ok

======================================================================
ERROR: test_factorial (__main__.TestCalc)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-3-d89c3d44c70d>", line 5, in test_factorial
    result = calculate_factorial(n)
  File "<ipython-input-2-2ad930b1e911>", line 5, in calculate_factorial
    raise ValueError('number should be greater than 0')
ValueError: number should be greater than 0

----------------------------------------------------------------------
Ran 2 tests in 0.010s

FAILED (errors=1)
trillion
  • 1,207
  • 1
  • 5
  • 15
  • Can you explains what you expect. The test `test_factorial` fails, in case of a negative number due to the raised `ValueError`. – Sven Eberth Aug 16 '21 at 10:22
  • @SvenEberth I would expect the tests to pass as I am raising value error if a number is negative but as you can see from above the test is failing in that case, even though I can see my raise Value error value – trillion Aug 16 '21 at 10:29
  • 1
    This is not how the unittest works. The exception is here unexpected for the test-case. But you can add an `if number < 0` with [`assertRaises(ValueError)`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises) to handle this case properly. – Sven Eberth Aug 16 '21 at 10:37
  • if I understand what you need correctly, I believe you need to use assertRaises when the input is negative. See the answer to this question: https://stackoverflow.com/questions/129507/how-do-you-test-that-a-python-function-throws-an-exception – DS_UNI Aug 16 '21 at 10:38
  • @SvenEberth do you mean i should add assert_raises(ValueError) under my first if statement ? It didn't work would it be possible if you can share the code – trillion Aug 16 '21 at 11:24
  • @DS_UNI I am not sure how can I make my unit test consider the entire function for value error only when it is a negative value. Would it possible if you can share the code with my use case – trillion Aug 16 '21 at 11:44

1 Answers1

2

As already mentioned in my comments, your test fails because the raised ValueError is not expected here. You can extend you test with an if to handle positive and non-positive values differently.

class TestCalc(unittest.TestCase):
    def test_factorial(self):
        print('Function is starting to check for values')
        print()
        if n < 0:
            with self.assertRaises(ValueError) as context:
                calculate_factorial(n)
            self.assertEqual('number should be greater than 0', str(context.exception))
        else:
            result = calculate_factorial(n)
            print('results are:', result)
            print()
            self.assertEqual(result, math.factorial(n))

    def test_n(self):
        print('The value of n taken by the class function is:', n)

However, it could be better to have different tests for different value ranges/kinds with fixed values like this:

class TestCalcFixedValues(unittest.TestCase):
    def test_factorial_positive(self):
        self.assertEqual(calculate_factorial(42), math.factorial(42))

    def test_factorial_negative(self):
        with self.assertRaises(ValueError) as context:
            calculate_factorial(-42)
        self.assertEqual('number should be greater than 0', str(context.exception))

    def test_factorial_NaN(self):
        with self.assertRaises(TypeError) as context:
            calculate_factorial("NaN")
        self.assertEqual('number should be an integer type', str(context.exception))

(then you will see, that calculate_factorial has a bug ;))

Sven Eberth
  • 3,057
  • 12
  • 24
  • 29
  • oh that'sreally nice, since I am new to this would like confirm if that's the logic you used: You used the raise value error from my function and compared it with the ValueError string generated by the ValueError, By default ValueError shows different error description (depending on the condition) but since I have set ValueError to the "number should be greater than 0", the ValueError of assert_raises will get compared to my ValueError value i.e number should be greater than 0 [if n < 0] and hence will confirm if the test is working or not. If I change the string the test will fail – trillion Aug 16 '21 at 12:06
  • also why didn't we put ```self.assertEqual('number should be greater than 0', str(context.exception))``` under the ```with``` – trillion Aug 16 '21 at 12:07
  • 1) Correct 2) Because the `calculate_factorial()` fails here (raises the exception), so code after this line in the with block will not be executed. You can add a `print("Hello")` after that and will see it will never be printed. – Sven Eberth Aug 16 '21 at 12:18
  • yes that's right thanks for the confirming the code. – trillion Aug 16 '21 at 12:57