简体   繁体   中英

Is there a way to automatically create test cases in Python's unittest framework?

Suppose we would like to write test cases for a (simple) math library that (currently) only implements two routines sqrt and ln . Among other things, both routines are expected to raise a ValueError if their input is negative. So, the test cases would look like this:

import unittest
from my_math_lib import sqrt, ln

class TestSqrt(unittest.TestCase):

    def test_negatives(self):
        self.assertRaises(ValueError, sqrt, -5)

    # ... any many other cases 

class TestLn(unittest.TestCase):

    def test_negatives(self):
        self.assertRaises(ValueError, ln, -5)

    # ... any many other cases 

Obviously the two test cases TestSqrt.test_negatives and TestLn.test_negatives are identical except for the function that is being tested, and repeating exactly the same code is a bad idea.

Is there away to auto-generate such test cases? For example, in C/C++ one could have implemented a macro that defines the test code with the appropriate function name. Is there a similar solution in Python?

There is the possibility to subclass as proposed by @jonrsharpe, though that has one caveat. You cannot derive your base class from unittest.TestCase and write a testXXX there, because that also would be found and executed, and would fail. You need a mix-in instead:

class BaseTest:
    def setUp(self):
        self.tested = None

    def test_negatives(self):
        self.assertRaises(ValueError, self.tested, -5)

class TestSqrt(unittest.TestCase, BaseTest):
    def setUp(self):
        self.tested = sqrt

class TestLn(unittest.TestCase, BaseTest):
    def setUp(self):
        self.tested = log

This works, but is not nice because the base class calls a method from TestCase ( assertRaises ) that it is not derived from, so every linter or IDE check will warn you about this.

To work around that problem, you could just use one of the real implementations as the base class:

class TestSqrt(unittest.TestCase):
    def setUp(self):
        self.tested = sqrt

    def test_negatives(self):
        self.assertRaises(ValueError, self.tested, -5)

class TestLn(TestSqrt):
    def setUp(self):
        self.tested = log

class TestSomethingElse(TestSqrt):
    def setUp(self):
        self.tested = something_else

What I would use instead is fixture parametrization, if you can use pytest:

import pytest

@pytest.fixture(params=[sqrt, log])
def tested_function(request):
    return request.param

def test_negatives(tested_function):
    with pytest.raises(ValueError):
        tested_function(-5)

def test_something_else(tested_function):
    ...

You could also put that into a class, of course.
Caveat: this does not work with unittest , or at least I don't know a clean way to do it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM