pytest:如何避免在自定义用户管理器测试中重复

pytest: how to avoid repetition in custom User manager testing

我正在使用 pytestfactory_boy 测试自定义用户管理器。我想测试那些创建新用户所需的信息不完整的情况,但我有不同的必需参数,目前有 3 个(email, username, identification_number),但将来可能会有更多。

经理

class UserManager(BaseUserManager):
    """ Define a manager for custom User model. """

    def create_user(
        self,
        email: str,
        username: str,
        identification_number: str,
        password: Optional[str] = None,
        is_active: bool = True,
        is_staff: bool = False,
        is_admin: bool = False,
    ) -> User:
        """ Creates and saves a User. """
        if not email:
            raise ValueError(_("Users must have an email address."))
        if not username:
            raise ValueError(_("Users must have a username."))
        if not identification_number:
            raise ValueError(_("Users must have an identification number."))
        user = self.model(email=self.normalize_email(email))
        user.set_password(password)
        user.username = username
        user.identification_number = identification_number
        user.active = is_active
        user.staff = is_staff
        user.admin = is_admin
        user.save(using=self._db)
        return user

测试

import pytest
from django.contrib.auth import get_user_model

from my_app.users.tests.factories import UserFactory

pytestmark = pytest.mark.django_db

class TestsUsersManagers:
    def test_user_with_no_email(self):
        proto_user = UserFactory.build()  # User created with factory boy 
        User = get_user_model()
        with pytest.raises(TypeError):
            User.objects.create_user()
        with pytest.raises(TypeError):
            User.objects.create_user(
                username=proto_user.username,
                identification_number=proto_user.identification_number,
                password=proto_user._password,
            )
        with pytest.raises(ValueError):
            User.objects.create_user(
                email="",
                username=proto_user.username,
                identification_number=proto_user.identification_number,
                password=proto_user._password,
            )

    def test_user_with_no_username(self):
        proto_user = UserFactory.build()
        User = get_user_model()
        with pytest.raises(TypeError):
            User.objects.create_user()
        with pytest.raises(TypeError):
            User.objects.create_user(
                email=proto_user.email,
                identification_number=proto_user.identification_number,
                password=proto_user._password,
            )
        with pytest.raises(ValueError):
            User.objects.create_user(
                email=proto_user.email,
                username="",
                identification_number=proto_user.identification_number,
                password=proto_user._password,
            )

    def test_user_with_no_identification_number(self):
        proto_user = UserFactory.build()
        User = get_user_model()
        with pytest.raises(TypeError):
            User.objects.create_user()
        with pytest.raises(TypeError):
            User.objects.create_user(
                email=proto_user.email,
                username=proto_user.username,
                password=proto_user._password,
            )
        with pytest.raises(ValueError):
            User.objects.create_user(
                email=proto_user.email,
                username=proto_user.username,
                identification_number="",
                password=proto_user._password,
            )

问题

有很多重复的代码,并且由于所需的参数数量可能会增加,我应该一遍又一遍地对那些额外的参数重复相同的测试。

您可以使用辅助方法和 fixture 来减少重复代码。以下代码块是这种方法的示例。

import pytest
from django.contrib.auth import get_user_model

from my_app.users.tests.factories import UserFactory

pytestmark = pytest.mark.django_db

class TestsUsersManagers:

    @pytest.fixture
    def proto_user(self):
        return UserFactory.build()

    def helper(self, username, password, email, identification_number):
        User = get_user_model()
        with pytest.raises(TypeError):
            User.objects.create_user()
        params = {'password': password}
        if username:
            params['username'] = username
        if email:
            params['email'] = email
        if identification_number:
            params['identification_number'] = identification_number
        with pytest.raises(TypeError):
            User.objects.create_user(**params)
        with pytest.raises(ValueError):
            User.objects.create_user(
                email=email,
                username=username,
                identification_number=identification_number,
                password=password,
            )

    def test_user_with_no_email(self, proto_user):
        self.helper(proto_user.username, proto_user._password, "", proto_user.identification_number)

    def test_user_with_no_username(self, proto_user):
        self.helper("", proto_user._password, proto_user.email, proto_user.identification_number)

    def test_user_with_no_identification_number(self, proto_user):
        self.helper(proto_user.username, proto_user._password, proto_user.email, "")

或者你也可以写parametrize测试。像下面这样,

import pytest
from django.contrib.auth import get_user_model

from my_app.users.tests.factories import UserFactory

pytestmark = pytest.mark.django_db

class TestsUsersManagers:

    def helper(self, username, password, email, identification_number):
        User = get_user_model()
        with pytest.raises(TypeError):
            User.objects.create_user()
        params = {'password': password}
        if username:
            params['username'] = username
        if email:
            params['email'] = email
        if identification_number:
            params['identification_number'] = identification_number
        with pytest.raises(TypeError):
            User.objects.create_user(**params)
        with pytest.raises(ValueError):
            User.objects.create_user(
                email=email,
                username=username,
                identification_number=identification_number,
                password=password,
            )

    @pytest.mark.parametrize("username,password,email,identification_number", [
        ("username", "password", "", "identification_number"),
        ("", "password", "email@email.com", "identification_number"),
        ("username", "password", "email@email.com", ""),
    ])
    def test_user(self, username, password, email, identification_number):
        self.helper(username, password, email, identification_number)

注意:我没有机会 运行 代码。所以你可能需要修改它们。