You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django pytest测试中用户Profile的identity_number无法获取问题排查

Django测试中Profile字段为空导致重置密码失败的问题排查与解决

问题背景

我实现了一个Django重置密码功能:当用户已认证时,会进入修改密码流程,此时从用户的Profile模型中获取identity_number作为nationalId。但用pytest编写测试用例时,明明创建了测试用户并设置了Profile的identity_number,测试中该字段却为空,导致接口返回WrongInputs错误。

相关代码如下:

views.py

@method_decorator(csrf_protect, name='dispatch')
class ResetPassword(APIView):
    permission_classes = (permissions.AllowAny, )
    def post(self, request):
        try:
            # 读取输入
            nid = request.data.get('nationalId')
            # 根据输入判断流程阶段...
            if not request.user.is_anonymous and request.user.is_authenticated:
                user = request.user
                nid = user.profile.identity_number 
            # 后续逻辑...
        except Exception as e:
            # 错误处理...
            return Response({'State': 'Error', 'Message': 'WrongInputs', 'Data': {}})

tests.py

import pytest
from django.contrib.auth.models import User
from myapp.models import Profile
from rest_framework.test import APIClient
from rest_framework import status

@pytest.fixture
def test_user_nid():
    user = User.objects.create_user(
        username="8835246563",
        password="upass123",
    )
    # 尝试更新Profile,但未生效
    profile = Profile.objects.update(user=user, identity_number="8835246563")
    return user

@pytest.mark.django_db
class TestResetPassword:
    url = '/api/reset-password/'  # 假设接口URL

    def test_change_password_stages(self, api_client, test_user_nid):
        api_client.force_authenticate(user=test_user_nid)
        print("User ID:", test_user_nid.id)
        print("User Profile Identity Number:", test_user_nid.profile.identity_number)
        data = {
            "tfaCode": 12345
        }
        response = api_client.post(self.url, data, format='json')
        response_data = response.json()
        print(">>>>>", response_data)
    
        # 再次验证Profile字段
        profile = test_user_nid.profile
        print("Profile Identity Number:", profile.identity_number)

models.py

import uuid
from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    identity_number = models.CharField(max_length=20, blank=True, default='')

测试打印结果

User ID: 12
User Profile Identity Number:
>>>>> {'State': 'Error', 'Message': 'WrongInputs', 'Data': {}}
Profile Identity Number:

问题原因排查

  1. Profile更新方式错误:
    测试fixture中使用Profile.objects.update(user=user, identity_number="8835246563")是错误的。update()是批量更新操作,此写法会尝试将所有Profile实例的user字段设置为当前测试用户,并更新identity_number,而非定位到当前用户对应的Profile进行修改。如果项目通过post_save信号自动为新User生成Profile,这条语句会修改所有已有Profile;如果没有自动生成逻辑,该语句不会创建新Profile,后续访问user.profile时Django会自动生成一个空Profile,导致identity_number为空。

  2. ORM缓存问题:
    即使正确更新了Profile,Django ORM会缓存已加载的对象实例。若更新后未重新获取用户或刷新Profile实例,test_user_nid.profile会保留缓存中的旧数据,导致读取到的identity_number为空。

解决方案

方法一:修复测试fixture的Profile创建/更新逻辑

根据项目是否自动生成Profile,选择对应方式:

情况1:无自动创建Profile的信号

直接创建Profile实例并关联用户:

@pytest.fixture
def test_user_nid():
    user = User.objects.create_user(
        username="8835246563",
        password="upass123",
    )
    # 直接创建关联的Profile
    Profile.objects.create(user=user, identity_number="8835246563")
    # 刷新用户对象,确保关联的Profile被加载
    user.refresh_from_db()
    return user

情况2:有自动创建Profile的信号

通过filter定位目标Profile再更新,或直接修改实例保存:

@pytest.fixture
def test_user_nid():
    user = User.objects.create_user(
        username="8835246563",
        password="upass123",
    )
    # 方式1:筛选目标Profile并更新
    Profile.objects.filter(user=user).update(identity_number="8835246563")
    # 方式2:直接修改实例并保存(更直观)
    # profile = user.profile
    # profile.identity_number = "8835246563"
    # profile.save()
    # 刷新用户对象的Profile缓存
    user.refresh_from_db()
    return user

方法二:其他创建测试用户的可行方式

1. 使用get_or_create确保Profile存在

适用于不确定是否有自动创建逻辑的场景:

@pytest.fixture
def test_user_nid():
    user = User.objects.create_user(
        username="8835246563",
        password="upass123",
    )
    # 确保Profile存在并设置identity_number
    profile, created = Profile.objects.get_or_create(user=user)
    profile.identity_number = "8835246563"
    profile.save()
    user.refresh_from_db()
    return user

2. 使用Factory Boy工厂函数

适合测试用例较多的场景,简化用户和Profile的创建:
首先安装依赖:

pip install factory-boy

创建工厂类:

# tests/factories.py
import factory
from django.contrib.auth.models import User
from myapp.models import Profile

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    username = factory.Sequence(lambda n: f"user{n}")
    password = factory.PostGenerationMethodCall('set_password', 'testpass123')

class ProfileFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Profile

    user = factory.SubFactory(UserFactory)
    identity_number = factory.Sequence(lambda n: f"id{n}")

测试中使用:

@pytest.fixture
def test_user_nid():
    user = UserFactory(username="8835246563")
    ProfileFactory(user=user, identity_number="8835246563")
    user.refresh_from_db()
    return user

3. 手动创建并关联(基础方式)

@pytest.fixture
def test_user_nid():
    user = User.objects.create(
        username="8835246563",
        is_active=True,
    )
    user.set_password("upass123")
    user.save()
    profile = Profile.objects.create(
        user=user,
        identity_number="8835246563"
    )
    return user

验证修改效果

修改fixture后重新运行测试,test_user_nid.profile.identity_number应能正确输出设置的值,接口也不会再返回WrongInputs错误。

内容的提问来源于stack exchange,提问作者HesamHashemi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 11:35:53