Search code examples
pythonargparsepython-unittest

How could I make it so that the assertRaise for SystemExit will result as true?


I am trying to create a python CLI tool for monitoring a specific process at a given interval of time.

I am trying to customize the message that gets printed out when a ValueError is caught, while also trying to exit the program by using sys.exit(1), which can be found in the validate function within example.py. If I weren't to use sys.exit(1), the print command within the main function would've been executed.

Having that sorted out, I procceded with performing a unit test (using unittest), by using test_example.py, for the specific program, to check if a SystemExit is raised when a negative value is passed to the time argument.

As such, how could I make it so that the assertRaise for SystemExit will result as true?

I'm using python 3.10.4 and argparse 1.1 .

# example.py

import argparse, sys

def parse_args(args):
    parser = argparse.ArgumentParser() 
    parser.add_argument("-t", "--time", type=float, metavar=" ")
    return parser.parse_args(args)

def validate(data):
    try:
        if data.time < 0:            
            raise ValueError
    except ValueError:
        print(f"Time has a negative value: {data.time}. Please use a positive value")
        sys.exit(1)
    
def main():
    parsed_data = parse_args(sys.argv[1:])
    validate(parsed_data)    

    print(parsed_data.time)

if __name__ == '__main__':
    main()
# test_example.py

import unittest
from example import parse_args, validate

class TestExemplu(unittest.TestCase):    
    def test_negative_value(self):   
        with self.assertRaises(SystemExit) as cm:
            validate()

        the_exception = cm.exception
        self.assertEqual(the_exception.code, 1)


if __name__ == '__main__':
    unittest.main()

This is the error that I get:

test_negative_value (test_example.TestExemplu) ... ERROR

======================================================================
ERROR: test_negative_value (test_example.TestExemplu)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\tester\Downloads\cli_mon_tool_1\test_example.py", line 16, in test_negative_value
    validate()
TypeError: validate() missing 1 required positional argument: 'data'

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (errors=1)

Solution

  • I came up with this solution, according to these 2 links:

    1. Hide traceback unless a debug flag is set

    2. https://gist.github.com/maphew/e3a75c147cca98019cd8/7236687e4161b2c3c5eca0daec500a516cc21055


    # example.py
    
    import argparse, sys
    debug = False
    
    def parse_args(args):
        parser = argparse.ArgumentParser()
        parser.add_argument("-p", "--path", type=str, metavar=" ")
        parser.add_argument("-t", "--time", type=float, metavar=" ")
        return parser.parse_args(args)
    
    def exceptionHandler(exception_type, exception, traceback, debug_hook=sys.excepthook):
        '''Print user friendly error messages normally, full traceback if DEBUG on.
           Adapted from http://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set
        '''
        if debug:
            print('\n*** Error:')
            debug_hook(exception_type, exception, traceback)
        else:
            print("%s: %s" % (exception_type.__name__, exception))
    sys.excepthook = exceptionHandler    
    
    def validate(data):
        try:
            if data.time < 0:            
                raise ValueError
        except ValueError:        
            raise ValueError(f"Time has a negative value: {data.time}. Please use a positive value")
        try:
            if data.time == 0:            
                raise ValueError
        except ValueError as e:        
            raise ValueError(f"Time has a value of zero. Please use a positive value")    
        
    def main():
        parsed_data = parse_args(sys.argv[1:])
        validate(parsed_data)
    
        print(parsed_data.path)
        print(parsed_data.time)
    
    if __name__ == '__main__':
        main()
    

    # test_example.py
    
    import unittest
    from example import parse_args, validate
    
    class TestExemplu(unittest.TestCase):  
        
        def test_negative_value(self):
            parsed_data1 = parse_args(['-t', '-1'])       
            parsed_data2 = parse_args(['-t', '0'])       
            parsed_data3 = parse_args(['-t', '-1'])       
            self.assertRaises(ValueError, validate, parsed_data1)    
            self.assertRaises(ValueError, validate, parsed_data2)    
            try:    
                self.assertRaises(ValueError, validate, parsed_data2)
            except:
                pass     
    
    if __name__ == '__main__':
        unittest.main()