Есть ли более быстрый способ написания похожих тестов для представлений Django?

По сути, я понимаю, что пишу один и тот же контрольный пример (test_update_with_only_1_field) для аналогичного URL для нескольких моделей

from django.test import RequestFactory, TestCase
class BaseApiTest(TestCase):
def setUp(self):
    superuser = User.objects.create_superuser('test', 'test@api.com', 'testpassword')
    self.factory = RequestFactory()
    self.user = superuser
    self.client.login(username=superuser.username, password='testpassword')

class SomeModelApiTests(base_tests.BaseApiTest):
def test_update_with_only_1_field(self):
    """
    Tests for update only 1 field 

    GIVEN the following shape and related are valid
    WHEN we update only with just 1 field
    THEN we expect the update to be successful
    """
    shape_data = {
        'name': 'test shape',
        'name_en': 'test shape en',
        'name_zh_hans': 'test shape zh hans',
        'serial_number': 'test shape serial number',
        'model_name': {
            'some_field': '123'
        }
    }

    data = json.dumps(shape_data)
    response = self.client.post(reverse('shape-list-create'), data, 'application/json')
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    some_model = response.data['some_model']
    new_some_field = '12345'

    data = json.dumps({'some_field': new_some_field, 'id': response.data['some_model']['id']})
    response = self.client.put(reverse('some-model', args=[some_model['id']]), data, 'application/json')
    self.assertEqual(response.status_code, status.HTTP_200_OK)
    self.assertEqual(new_some_field, response.data['some_field'])

Мне нужно сделать это более 10 раз. Что я уже сделал так.

единственная разница каждый раз, это следующие фразы "some_model", "some-model" и "some_field"

Мне было интересно, есть ли более быстрый способ сделать это.

Я могу мыслить абстрактно двумя способами:

  1. создайте шаблон в текстовом редакторе, который каким-то образом может сгенерировать окончательный тестовый пример, который я затем скопирую и вставлю. Я использую возвышенный текст 3, хотя я могу переключиться на другой текстовый редактор

  2. Есть способ, которым я могу написать немного больше кода в форме преобразования этого тестового примера в класс поведения, который может вызвать отдельный тестовый класс. ака состав.

Какой из них имеет больше смысла или есть другой способ сделать это?

Обратите внимание, что класс BaseApi также наследуется другим классом тестирования, который НЕ имеет такого метода повторяющихся тестов.

4 ответа

Я думаю, что вы хотите, это "параметризованные тесты", стандартные unittest может сделать это с параметризованным пакетом:

import unittest
from parameterized import parameterized

class SomeModelApiTests(unittest.TestCase):

    @parameterized.expand([
        ('case1', 'm1', 'f1', 'nf1'),
        ('case1', 'm2', 'f2', 'nf2'),
    ])
    def test_update_with_only_1_field(self, dummy_subtest_name, model_name, field_name, new_field_value):
        print(model_name, field_name, new_field_value)

будет давать:

test_update_with_only_1_field_0_case1 (t.SomeModelApiTests) ... m1 f1 nf1
ok
test_update_with_only_1_field_1_case1 (t.SomeModelApiTests) ... m2 f2 nf2
ok

pytest инфраструктура тестирования лучше поддерживает встроенные параметризованные тесты, на которые стоит обратить внимание.

В проектах, над которыми я работаю, мы иногда используем mixin + "настройки", когда тест необходимо повторить. (и конечные точки, такие как "shape-list-create", могут быть изменены / реорганизованы)

Пример для вопроса:

class TestUpdateWithOnly1FieldMixin(object):
    some_model = None
    some_field = None
    some_model2 = None

    def get_some_model(self):
        return self.some_model

    def get_some_field(self):
        return self.some_field

    def get_some_model2(self):
        return self.some_model2

    def test_update_with_only_1_field(self):
        some_model = self.get_some_model()
        # represents some-model in example
        some_model2 = self.get_some_model2()
        some_field = self.get_some_field()

        shape_data = {
            'name': 'test shape',
            'name_en': 'test shape en',
            'name_zh_hans': 'test shape zh hans',
            'serial_number': 'test shape serial number',
            'model_name': {
                some_field: '123'
            }
        }

      data = json.dumps(shape_data)
      response = self.client.post(reverse('shape-list-create'), data, 'application/json')
      self.assertEqual(response.status_code, status.HTTP_201_CREATED)

      some_model_data = response.data[some_model]



class SomeModelApiTests(base_tests.BaseApiTest, TestUpdateWithOnly1FieldMixin):
    some_model = 'choose your model'
    some_field = 'some_field'
    some_model2 = 'some-model'

    def get_some_field(self):
        # Do customization
        return 'some-field after customize'

Как разделить хуки настройки и что добавить в mixin и т. Д., Зависит от ситуации. По моему мнению, цель состоит в том, чтобы иметь простой контрольный пример. (Возможно, переместите "post shape-list-create" в отдельную функцию, так как она может не соответствовать данному тестовому примеру)

Другой пример, немного переборщенный с настройками, но просто чтобы дать идею.

class TestWithGoodNameMixin(object):
    some_model = None
    some_field = None

    # "Customization hooks"

    def get_shape_data(self):
        return {self.some_field: 'x'}

    def create_model(self, shape_data):
        response = self.client.post(reverse('shape-list-create'), shape_data,
                                    'application/json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        return response[self.some_model]

    def create_put_data(self, some_model_data):
        # Add default implementation
        pass

    # .....

    def test_update_with_only_1_field(self):
        shape_data = self.get_shape_data()
        some_model_data = self.create_model(shape_data)

        data = self.create_put_data(some_model_data)
        response = self.put_data(data)

        self.assert_put_response(response)

Вы можете создать список / dict для "some_model" для тестирования и использовать subtest ( https://docs.python.org/3/library/unittest.html) для каждого из ваших элементов "some_model".

my_list_of_model = [FirstModel, SecondModel]

for my_model in my_list_of_model:
    with subTest(model=mymodel):
        # Testing model here

Если вы хотите другой TestCase для каждой вашей модели я думаю, что множественное наследование - это путь:

class BaseApiTestCase(TestCase):
    def setUp():
        # Setup stuff

class RepetitiveTestCaseMixin:
    # Class to do the repetitive stuff
    def test_update_should_work(self):
        # Do some thing with self.model and self.field here

class ModelTestCase(BaseApiTestCase, RepetitiveTestCaseMixin):
    @classmethod
    def setUpClass(cls):
       super().setUpClass()

       cls.model = MyModel
       cls.field = 'some_field'

Вы можете использовать пакет pytest для модульного тестирования. Это очень просто и удобно в использовании.

@pytest.mark.parametrize() Декоратор может быть использован для достижения этой функциональности.

Примером параметризованных тестов является следующий:

import pytest
class SampleTesting(object):
    data_for_test = [
                      ('{inputdata1:value1}','output1'),
                      ('{inputdata1:value2}','output2')
                     ]
   @pytest.mark.parametrized('input_data, expected_output', data_for_test)
   def test_sample_function(self, input_data, expected_output):
       response = function_to_be_tested(input_data)
       assert response == expected_output

Вы можете прочитать больше об этом декораторе в документации

Вы также можете использовать @pytest.fixture() декоратор для настройки тестовой функции.

Другие вопросы по тегам