6

I've created a Python library with some command-line scripts in a 'bin' directory (so that setup.py will install it into 'bin' when installing it with pip). Since this isn't a Python module, I can't work out how to test it with nose.

How can I test a command line script that's part of a library using nose/unittest?

johnsyweb
  • 136,902
  • 23
  • 188
  • 247
user1491250
  • 1,831
  • 4
  • 18
  • 21

1 Answers1

12

Use the "if __name__ == "__main__":" idiom in your scripts and encapsulate all of the function-ality in function-s.

Then you can import your scripts into another script (such as a unit test script) without the body of it being executed. This will allow you to write unit-tests for the functionality and run them through nose.

I recommend keeping the "main" block to a line or two.

For example:

plus_one.py

#!/usr/bin/env python

import sys


def main(args):
    try:
        output(plus_one(get_number(args)))
    except (IndexError, ValueError), e:
        print e
        return 1
    return 0


def get_number(args):
    return int(args[1])


def plus_one(number):
    return number + 1


def output(some_text):
    print some_text


if __name__ == '__main__':
    sys.exit(main(sys.argv))

You can test command-line parameters, output, exceptions and return codes in your unittests...

t_plus_one.py

#!/usr/bin/env python

from StringIO import StringIO
import plus_one
import unittest


class TestPlusOne(unittest.TestCase):

    def test_main_returns_zero_on_success(self):
        self.assertEquals(plus_one.main(['test', '1']), 0)

    def test_main_returns_nonzero_on_error(self):
        self.assertNotEqual(plus_one.main(['test']), 0)

    def test_get_number_returns_second_list_element_as_integer(self):
        self.assertEquals(plus_one.get_number(['anything', 42]), 42)

    def test_get_number_raises_value_error_with_string(self):
        self.assertRaises(ValueError, plus_one.get_number, ['something',
                                                            'forty-two'])

    def test_get_number_raises_index_error_with_too_few_arguments(self):
        self.assertRaises(IndexError, plus_one.get_number, ['nothing'])

    def test_plus_one_adds_one_to_number(self):
        self.assertEquals(plus_one.plus_one(1), 2)

    def test_output_prints_input(self):
        saved_stdout, plus_one.sys.stdout = plus_one.sys.stdout, StringIO('_')
        plus_one.output('some_text')
        self.assertEquals(plus_one.sys.stdout.getvalue(), 'some_text\n')
        plus_one.sys.stdout = saved_stdout

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

Output

python plus_one.py 41

42

nosetests -v t_plus_one.py

test_get_number_raises_index_error_with_too_few_arguments (t_plus_one.TestPlusOne) ... ok
test_get_number_raises_value_error_with_string (t_plus_one.TestPlusOne) ... ok
test_get_number_returns_second_list_element_as_integer (t_plus_one.TestPlusOne) ... ok
test_main_returns_nonzero_on_error (t_plus_one.TestPlusOne) ... ok
test_main_returns_zero_on_success (t_plus_one.TestPlusOne) ... ok
test_output_prints_input (t_plus_one.TestPlusOne) ... ok
test_plus_one_adds_one_to_number (t_plus_one.TestPlusOne) ... ok

----------------------------------------------------------------------
Ran 7 tests in 0.002s

OK
Community
  • 1
  • 1
johnsyweb
  • 136,902
  • 23
  • 188
  • 247
  • That's what I've done. My CLI script basically just handles exceptions and arg parsing and calls the library modules. I still want to make sure that it returns a code of 1 if there's an error or a 0 otherwise, things like that. – user1491250 Jun 04 '13 at 13:02
  • No. Functions can raise errors. If any errors were raised, they need to be handled and then a non-zero return code is returned by the script. If no errors occurred, the script returns zero. It'd also be useful to be able to test the command line arguments too. The library is fully tested (100% coverage) and it seems that the CLI scripts should be tested too. – user1491250 Jun 05 '13 at 09:02
  • @user1491250: Then it sounds like you can still extract some more from your "main" block. I've added examples to my answer to demonstrate how you can test command-line parameters, output, exceptions and return codes in your unit-tests. – johnsyweb Jun 11 '13 at 11:13