1

I have a class that contains XML strings. These are my models.

class ContainerForStringXMLs():
    def __init__(self):
        pass

    @staticmethod
    def get_model1(self):
        return """I'm a long string called model1"""

    @staticmethod
    def get_model2(self):
        return """I'm a long string called model2"""

I have a base test class which gives me access to the models in my other tests (and a few other things which aren't important here)

class BaseTest(unittest.TestCase):
    def setUp(self, model='model1'):
        self.model=model

        if self.model == 'model1':
            self.test_model = ContainerForStringXMLs.model1()
        elif self.model == 'model2':
            self.test_model = ContainerForStringXMLs.model2()

    def tearDown(self):
        del self.model
        del self.test_model

And my actual test class looks something like this:

class TestClass(BaseTest):
    def __init__(self):
        pass

    def test_on_model1(self):
        """
        I want to perform this test on model1
        """
        print self.test_model ##would return model 1

    def test_on_model2(self):
        """
        I want to perform this test on model 2 
        """
        print self.testmodel2

The test I want to perform is exactly the same test but the models are different and therefore the values that I pull from the xml will be different in each case. My question is: is there a nice pythonic way to switch between models from the TestClass? I was thinking perhaps a decorator or some sort?

It'd be great if I was able to use something like the following to choose which model I direct the test toward:

class TestClass(BaseTest):
    def __init__(self):
        pass

    @testmodel1
    def test_on_model1(self):
        """
        I want to perform this test on model1
        """
        print self.test_model ##would return model 1

    @testmodel2    
    def test_on_model2(self):
        """
        I want to perform this test on model 2 
        """
        print self.testmodel2

Is this sort of behavior possible?

CiaranWelsh
  • 7,014
  • 10
  • 53
  • 106
  • I'm really struggling to understand why you want to do this. Why is `@testmodel1` before a test def preferable to `self.test_model = ContainerForStringXMLs.model1()` inside the test def? Or better yet, pull out the testing of separate models into separate test cases and then override `setUp` to use the appropriate model. – Dunes Aug 05 '17 at 21:11

1 Answers1

1

“XML strings” class is in models.py as-is. Testing helpers (base class and decorator) are in testutils.py:

import unittest
import functools

import models


class BaseTestCase(unittest.TestCase):

    def setUp(self, model='model1'):
        self.model_loader = models.ContainerForStringXMLs
        self.model = model

    @property
    def model_contents(self):
        return getattr(
            self.model_loader, 'get_' + self.model)(self.model_loader)


def use_model(model):
    """Make BaseTestCase-aware test use particular model."""
    def _wrapper(func):
        @functools.wraps(func)
        def _inner(self):
            orig = self.model
            self.model = model
            func(self)
            self.model = orig
        return _inner
    return _wrapper

use_model is a decorator that takes model name (model), and temporarily sets self.model for test method. model_contents is a property that gets XML string from ContainerForStringXMLs for current self.model.

As get_model1 and get_model2 are static methods that take self (in your example), in model_contents I pass ContainerForStringXMLs class as self (that’s not particularly good). If that is not what you want, change (self.model_loader) to something else (and, of course, change get_* methods in ContainerForStringXMLs to match the way you call them). Given that this is a minor issue, I just assumed that only tests and BaseTest may be modified.

In test_models.py there are two test cases:

from testutils import BaseTestCase, use_model


class TwoModelsWithDefaultModelTest(BaseTestCase):

    def setUp(self):
        BaseTestCase.setUp(self, model='model2')

    @use_model('model1')
    def test_first_model(self):
        self.assertEqual(self.model, 'model1')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model1")

    def test_second_model(self):
        self.assertEqual(self.model, 'model2')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model2")

    @use_model('model1')
    def test_first_model_again(self):
        self.assertEqual(self.model, 'model1')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model1")

    def test_second_model_again(self):
        self.assertEqual(self.model, 'model2')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model2")


class TwoModelsTestWithoutExplicitSetUp(BaseTestCase):

    @use_model('model1')
    def test_first_model(self):
        self.assertEqual(self.model, 'model1')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model1")

    @use_model('model2')
    def test_second_model(self):
        self.assertEqual(self.model, 'model2')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model2")

    def test_first_model_again(self):
        self.assertEqual(self.model, 'model1')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model1")

    @use_model('model2')
    def test_second_model_again(self):
        self.assertEqual(self.model, 'model2')
        self.assertEqual(
            self.model_contents,
            "I'm a long string called model2")

TwoModelsWithDefaultModelTest calls BaseTestCase.setUp to set self.model to 'model2' for tests that do not use use_model decorator. TwoModelsTestWithoutExplicitSetUp uses default implementation of BaseTestCase.setUp without changing model argument default, so there default self.model is 'model1'.


Judging by print statements, you use Python 2. You should make ContainerForStringXMLs inherit from object.

paka
  • 791
  • 1
  • 8
  • 7