96

Is there a way in Python unittest to set the order in which test cases are run?

In my current TestCase class, some testcases have side effects that set conditions for the others to run properly. Now I realize the proper way to do this is to use setUp() to do all setup related things, but I would like to implement a design where each successive test builds slightly more state that the next can use. I find this much more elegant.

class MyTest(TestCase):

  def test_setup(self):
    # Do something

  def test_thing(self):
    # Do something that depends on test_setup()

Ideally, I would like the tests to be run in the order they appear in the class. It appears that they run in alphabetical order.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mike
  • 58,961
  • 76
  • 175
  • 221

11 Answers11

89

Don't make them independent tests - if you want a monolithic test, write a monolithic test.

class Monolithic(TestCase):
  def step1(self):
      ...

  def step2(self):
      ...

  def _steps(self):
    for name in dir(self): # dir() result is implicitly sorted
      if name.startswith("step"):
        yield name, getattr(self, name) 

  def test_steps(self):
    for name, step in self._steps():
      try:
        step()
      except Exception as e:
        self.fail("{} failed ({}: {})".format(step, type(e), e))

If the test later starts failing and you want information on all failing steps instead of halting the test case at the first failed step, you can use the subtests feature: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests

(The subtest feature is available via unittest2 for versions prior to Python 3.4: https://pypi.python.org/pypi/unittest2 )

ncoghlan
  • 40,168
  • 10
  • 71
  • 80
  • I am quite new to unit testing, and I get the feeling that monolithic test is bad. Is that true? Just build my test suite, and I really depend on monolithic test using your code. Is this a sign that I approach unit testing in a bad way? Thanks – swdev Oct 25 '13 at 16:13
  • 12
    Pure unit tests offer the benefit that when they fail, they often tell you *exactly* what is wrong. You can also just rerun the tests that failed when trying to fix them. Monolithic tests like this don't have those benefits: when they fail, it's a debugging exercise to figure out what went wrong. On the other hand, tests like this are often a lot easier and quicker to write, especially when retrofitting tests to an existing application that wasn't built with unit testing in mind. – ncoghlan Oct 30 '13 at 02:20
  • there is no need for all the code, just use a better naming convention for the testnames... like "test_1_newuser" and "test_2_signin" this works because the tests are sorted respect to the built-in ordering for strings. – shakirthow Sep 26 '15 at 19:43
  • 6
    @shakirthow If the order of execution matters, they're not unit tests any more - they're steps in a scenario test. That's still a worthwhile thing to do, but it's best handled either as a larger test case as shown, or using a higher level behavioural testing framework like http://pythonhosted.org/behave/ – ncoghlan Sep 30 '15 at 06:46
  • 1
    Note, in your code `sorted()` is not really necessary, because `dir()` returns the step methods alphabetically sorted by guarantee. That's why also `unittest` handles the test classes and test methods in alphabetical order by default (even when `sortTestMethodsUsing` is None) - which can be exploited for practicability to e.g. have the latest-work-tests running first for speeding up the edit-testrun-cycle. – kxr Mar 02 '17 at 18:16
  • 1
    @ncoghlan Nick, just wanted to thank you for these comments on testing - really opened my eyes on a problem I was having. I also stalked some of your other answers which were equally excellent. Cheers! – Brandon Bertelsen May 25 '19 at 18:06
60

It's a good practice to always write a monolithic test for such expectations. However, if you are a goofy dude like me, then you could simply write ugly looking methods in alphabetical order so that they are sorted from a to b as mentioned in the Python documentation - unittest — Unit testing framework

Note that the order in which the various test cases will be run is determined by sorting the test function names with respect to the built-in ordering for strings

Example

def test_a_first():
    print "1"
def test_b_next():
    print "2"
def test_c_last():
    print "3"
M at
  • 1,020
  • 1
  • 12
  • 26
varun
  • 4,522
  • 33
  • 28
  • 8
    IMO this approach is better than adding more code as workaround. – Ahsan Feb 27 '17 at 03:45
  • Why do you say it's good practice to write monolithic tests? Check out the more sophisticated way Java [TestNG does it](http://testng.org/doc/documentation-main.html#dependent-methods) with test groups and dependencies. In any case, I'm a goofy guy too, and when I write my tests in alpha order, I've found it useful to pass state through global variables, because the test runner may create different instances for each test. – Joshua Richardson Nov 22 '17 at 17:22
  • 1
    @Joshua Just like every other thing out there, there is no "one solution to rule them all", Monolithinc solution is often a good design practise considered by some programmers, ordered tests or scenario driven tests break one of the design rules for testing which is "one test per expectation", but you don't have to adhere to this. I am not a big java fan, and just because a framework tries to do something doesn't necessary mean its a good practise. And the word test group it self doesn't make sence to me, but feel free to do whatever bruh. – varun Nov 22 '17 at 21:42
  • There is a test I am thinking should run last rather than in the middle, so will start its name with a "z". – cardamom Jul 10 '19 at 09:16
29

From unittest — Unit testing framework, section Organizing test code:

Note: The order in which the various tests will be run is determined by sorting the test method names with respect to the built-in ordering for strings.

So just make sure test_setup's name has the smallest string value.

Note that you should not rely on this behavior — different test functions are supposed to be independent of the order of execution. See ngcohlan's answer above for a solution if you explicitly need an order.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 6
    Different testrunner, different behavior. Your advice is unhelpful for writing stable code snd tests. –  Mar 22 '11 at 06:37
27

Another way that I didn't see listed in any related questions: Use a TestSuite.

Another way to accomplish ordering is to add the tests to a unitest.TestSuite. This seems to respect the order in which the tests are added to the suite using suite.addTest(...). To do this:

  • Create one or more TestCase subclasses,

      class FooTestCase(unittest.TestCase):
          def test_ten():
              print('Testing ten (10)...')
          def test_eleven():
              print('Testing eleven (11)...')
    
      class BarTestCase(unittest.TestCase):
          def test_twelve():
              print('Testing twelve (12)...')
          def test_nine():
              print('Testing nine (09)...')
    
  • Create a callable test-suite generation added in your desired order, adapted from the documentation and this question:

      def suite():
          suite = unittest.TestSuite()
          suite.addTest(BarTestCase('test_nine'))
          suite.addTest(FooTestCase('test_ten'))
          suite.addTest(FooTestCase('test_eleven'))
          suite.addTest(BarTestCase('test_twelve'))
          return suite
    
  • Execute the test-suite, e.g.,

      if __name__ == '__main__':
          runner = unittest.TextTestRunner(failfast=True)
          runner.run(suite())
    

For context, I had a need for this and wasn't satisfied with the other options. I settled on the above way of doing test ordering.

I didn't see this TestSuite method listed any of the several "unit-test ordering questions" (e.g., this question and others including execution order, or changing order, or tests order).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
hoc_age
  • 1,945
  • 2
  • 18
  • 28
  • this is good except that it creates a new class for each test case. is there a way to retain data from test_ten and use it in test_eleven? – thang Oct 06 '16 at 20:25
  • @thang if you make things `@classmethod` then they can persist state across instances. – Nick Chapman Oct 04 '17 at 14:41
  • When doing this do you know if `setUpClass` is called? Or does it need to be run manually? – Nick Chapman Oct 04 '17 at 14:42
  • @NickChapman how does that make sense? @ classmethod pretty much makes it a static function (with class info as a parameter) – thang Oct 12 '17 at 15:11
  • 1
    @thang `@classmethod != @staticmethod`!!! Be careful, they are totally different things. `@staticmethod` will allow you to invoke the method without having an instance of the class. `@classmethod` gives you access to the class and on the class itself you can store information. For example if you do `cls.somevar = 10` inside of a class method then all instances of that class and all other class methods will see that `somevar = 10` after that function has been run. Classes themselves are objects that you can bind values onto. – Nick Chapman Oct 12 '17 at 17:19
  • @NickChapman yes, so that basically makes it a static method with the class object as parameter. you can call @ classmethod without any instance as well. Suppose I have 2 groups of tests on the same class. In the first group, I want to share one instance, and in the second group, I want to share a second instance. This isn't possible. If I have a class C with test functions t1, t2, t3, t4. I would like to do this: x1 = C(), x1.t1(), x1.t3(), x2 = C(), x2.t1(), x2.t2(), x2.t4() – thang Oct 12 '17 at 17:32
  • @thang so I actually just did something like that. To get around it I put all of the tests inside of a wrapper class and then had the wrapper inject an instance of itself into each of the test classes to create a kind of shared bus between the tests. – Nick Chapman Oct 12 '17 at 18:22
  • @NickChapman yes you can hack it up by creating a class for each group, which is ok. it's just not a terribly clean solution. – thang Oct 12 '17 at 21:16
  • @thang the alternative is you make a class that inherits from `unittest.TestCase` and give it a dict or something to pass data around in. The reality of the situation is that tests with state are not well supported by Python so you're gonna have to hack something one way or another. – Nick Chapman Oct 13 '17 at 15:07
  • @NickChapman you're right. i wonder if there is already a better framework on top of python unittest that allows for this. the c++ test suites all seem to be able to do fixtures pretty well. – thang Oct 13 '17 at 17:23
  • This should be the accepted answer. A test suite respects the order of tests. Also when you have not the option to install additional libs, this is the way to create a monolithic test case. – Torisuta Apr 23 '18 at 11:39
6

I ended up with a simple solution that worked for me:

class SequentialTestLoader(unittest.TestLoader):
    def getTestCaseNames(self, testCaseClass):
        test_names = super().getTestCaseNames(testCaseClass)
        testcase_methods = list(testCaseClass.__dict__.keys())
        test_names.sort(key=testcase_methods.index)
        return test_names

And then

unittest.main(testLoader=utils.SequentialTestLoader())
caio
  • 1,949
  • 16
  • 20
5

A simple and flexible way is to assign a comparator function to unittest.TestLoader.sortTestMethodsUsing:

Function to be used to compare method names when sorting them in getTestCaseNames() and all the loadTestsFrom*() methods.

Minimal usage:

import unittest

class Test(unittest.TestCase):
    def test_foo(self):
        """ test foo """
        self.assertEqual(1, 1)

    def test_bar(self):
        """ test bar """
        self.assertEqual(1, 1)

if __name__ == "__main__":
    test_order = ["test_foo", "test_bar"] # could be sys.argv
    loader = unittest.TestLoader()
    loader.sortTestMethodsUsing = lambda x, y: test_order.index(x) - test_order.index(y)
    unittest.main(testLoader=loader, verbosity=2)

Output:

test_foo (__main__.Test)
test foo ... ok
test_bar (__main__.Test)
test bar ... ok

Here's a proof of concept for running tests in source code order instead of the default lexical order (output is as above).

import inspect
import unittest

class Test(unittest.TestCase):
    def test_foo(self):
        """ test foo """
        self.assertEqual(1, 1)

    def test_bar(self):
        """ test bar """
        self.assertEqual(1, 1)

if __name__ == "__main__":
    test_src = inspect.getsource(Test)
    unittest.TestLoader.sortTestMethodsUsing = lambda _, x, y: (
        test_src.index(f"def {x}") - test_src.index(f"def {y}")
    )
    unittest.main(verbosity=2)

I used Python 3.8.0 in this post.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
2

Tests which really depend on each other should be explicitly chained into one test.

Tests which require different levels of setup, could also have their corresponding setUp() running enough setup - various ways thinkable.

Otherwise unittest handles the test classes and test methods inside the test classes in alphabetical order by default (even when loader.sortTestMethodsUsing is None). dir() is used internally which sorts by guarantee.

The latter behavior can be exploited for practicability - e.g. for having the latest-work-tests run first to speed up the edit-testrun-cycle. But that behavior should not be used to establish real dependencies. Consider that tests can be run individually via command-line options etc.

kxr
  • 4,841
  • 1
  • 49
  • 32
1

One approach can be to let those sub tests be not be treated as tests by the unittest module by appending _ in front of them and then building a test case which builds on the right order of these sub-operations executed.

This is better than relying on the sorting order of unittest module as that might change tomorrow and also achieving topological sort on the order will not be very straightforward.

An example of this approach, taken from here (Disclaimer: my own module), is as below.

Here, test case runs independent tests, such as checking for table parameter not set (test_table_not_set) or test for primary key (test_primary_key) still in parallel, but a CRUD test makes sense only if done in right order and state set by previous operations. Hence those tests have been rather made just separate unit, but not test. Another test (test_CRUD) then builds a right order of those operations and tests them.

import os
import sqlite3
import unittest

from sql30 import db

DB_NAME = 'review.db'


class Reviews(db.Model):
    TABLE = 'reviews'
    PKEY = 'rid'
    DB_SCHEMA = {
        'db_name': DB_NAME,
        'tables': [
            {
                'name': TABLE,
                'fields': {
                    'rid': 'uuid',
                    'header': 'text',
                    'rating': 'int',
                    'desc': 'text'
                    },
                'primary_key': PKEY
            }]
        }
    VALIDATE_BEFORE_WRITE = True

class ReviewTest(unittest.TestCase):

    def setUp(self):
        if os.path.exists(DB_NAME):
            os.remove(DB_NAME)

    def test_table_not_set(self):
        """
        Tests for raise of assertion when table is not set.
        """
        db = Reviews()
        try:
            db.read()
        except Exception as err:
            self.assertIn('No table set for operation', str(err))

    def test_primary_key(self):
        """
        Ensures, primary key is honored.
        """
        db = Reviews()
        db.table = 'reviews'
        db.write(rid=10, rating=5)
        try:
            db.write(rid=10, rating=4)
        except sqlite3.IntegrityError as err:
            self.assertIn('UNIQUE constraint failed', str(err))

    def _test_CREATE(self):
        db = Reviews()
        db.table = 'reviews'
        # backward compatibility for 'write' API
        db.write(tbl='reviews', rid=1, header='good thing', rating=5)

        # New API with 'create'
        db.create(tbl='reviews', rid=2, header='good thing', rating=5)

        # Backward compatibility for 'write' API, without tbl,
        # explicitly passed
        db.write(tbl='reviews', rid=3, header='good thing', rating=5)

        # New API with 'create', without table name explicitly passed.
        db.create(tbl='reviews', rid=4, header='good thing', rating=5)

        db.commit()   # Save the work.

    def _test_READ(self):
        db = Reviews()
        db.table = 'reviews'

        rec1 = db.read(tbl='reviews', rid=1, header='good thing', rating=5)
        rec2 = db.read(rid=1, header='good thing')
        rec3 = db.read(rid=1)

        self.assertEqual(rec1, rec2)
        self.assertEqual(rec2, rec3)

        recs = db.read()  # Read all
        self.assertEqual(len(recs), 4)

    def _test_UPDATE(self):
        db = Reviews()
        db.table = 'reviews'

        where = {'rid': 2}
        db.update(condition=where, header='average item', rating=2)
        db.commit()

        rec = db.read(rid=2)[0]
        self.assertIn('average item', rec)

    def _test_DELETE(self):
        db = Reviews()
        db.table = 'reviews'

        db.delete(rid=2)
        db.commit()
        self.assertFalse(db.read(rid=2))

    def test_CRUD(self):
        self._test_CREATE()
        self._test_READ()
        self._test_UPDATE()
        self._test_DELETE()

    def tearDown(self):
        os.remove(DB_NAME)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ViFI
  • 971
  • 1
  • 11
  • 27
  • Yes, but you risk false negative tests: Tests that are present, but are not executed because you left them out by accident. For instance, if you by accident left out the call of `self._test_UPDATE()` in `test_CRUD`. – Peter Mortensen Feb 05 '21 at 18:26
1

you can start with:

test_order = ['base']

def index_of(item, list):
    try:
        return list.index(item)
    except:
        return len(list) + 1

2nd define the order function:

def order_methods(x, y):
    x_rank = index_of(x[5:100], test_order)
    y_rank = index_of(y[5:100], test_order)
    return (x_rank > y_rank) - (x_rank < y_rank)

3rd set it in the class:

class ClassTests(unittest.TestCase):
    unittest.TestLoader.sortTestMethodsUsing = staticmethod(order_methods)
igreenfield
  • 1,618
  • 19
  • 36
0

ncoghlan's answer was exactly what I was looking for when I came to this question. I ended up modifying it to allow each step-test to run, even if a previous step had already thrown an error; this helps me (and maybe you!) to discover and plan for the propagation of error in multi-threaded database-centric software.

class Monolithic(TestCase):
  def step1_testName1(self):
      ...

  def step2_testName2(self):
      ...

  def steps(self):
      '''
      Generates the step methods from their parent object
      '''
      for name in sorted(dir(self)):
          if name.startswith('step'):
              yield name, getattr(self, name)

  def test_steps(self):
      '''
      Run the individual steps associated with this test
      '''
      # Create a flag that determines whether to raise an error at
      # the end of the test
      failed = False

      # An empty string that the will accumulate error messages for
      # each failing step
      fail_message = ''
      for name, step in self.steps():
          try:
              step()
          except Exception as e:
              # A step has failed, the test should continue through
              # the remaining steps, but eventually fail
              failed = True

              # Get the name of the method -- so the fail message is
              # nicer to read :)
              name = name.split('_')[1]
              # Append this step's exception to the fail message
              fail_message += "\n\nFAIL: {}\n {} failed ({}: {})".format(name,
                                                                       step,
                                                                       type(e),
                                                                       e)

      # Check if any of the steps failed
      if failed is True:
          # Fail the test with the accumulated exception message
          self.fail(fail_message)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
dslosky
  • 867
  • 8
  • 18
0

I also wanted to specify a particular order of execution to my tests. The main differences to other answers in here are:

  • I wanted to perverse a more verbose test method name without replacing whole name with step1, step2 etc.
  • I also wanted the printed method execution in the console to have some granularity apposed to using a Monolithic solution in some of the other answers.

So for the execution for monolithic test method is looked like this:

test_booking (__main__.TestBooking) ... ok

I wanted:

test_create_booking__step1 (__main__.TestBooking) ... ok
test_process_booking__step2 (__main__.TestBooking) ... ok
test_delete_booking__step3 (__main__.TestBooking) ... ok

How to achieve this

I provided a suffix to my method name with the __step<order> for example (order of definition is not important):

def test_create_booking__step1(self):
    [...]

def test_delete_booking__step3(self):
    [...]

def test_process_booking__step2(self):
    [...]

For the test suite override the __iter__ function which will build an iterator for the test methods.

class BookingTestSuite(unittest.TestSuite):
    """ Extends the functionality of the the standard test suites """
    
    def __iter__(self):
        for suite in self._tests:
            suite._tests = sorted(
                [x for x in suite._tests if hasattr(x, '_testMethodName')], 
                key = lambda x: int(x._testMethodName.split("step")[1])
            )
        return iter(self._tests)

This will sort test methods into order and execute them accordingly.

Lewis
  • 2,718
  • 1
  • 11
  • 28
  • It's just not a great idea - see the other answer to this question: https://stackoverflow.com/a/42563214/51685 – AKX Sep 20 '22 at 11:50
  • @AKX maybe for my particular use case it's not advised but I feel this is still relevant for individuals to whom want an explicit way to order their test methods without dependencies. – Lewis Sep 20 '22 at 11:58