【问题标题】:Unit test for Django Update formDjango 更新表单的单元测试
【发布时间】:2021-08-29 09:57:41
【问题描述】:

我不明白如何管理表单和相关单元测试的更新,我非常感谢一些建议 =)

我有一个 Company 模型,以及相关的非常简单的 CompanyForm:

class Company(models.Model):
    """
    Company informations
    - Detailed information for display purposes in the application
      but also used in documents built and sent by the application
    - Mail information to be able to send emails
    """
    company_name = models.CharField("nom", max_length=200)
    comp_slug = models.SlugField("slug")
    logo = models.ImageField(upload_to="img/", null=True, blank=True)
    use_groups = models.BooleanField("utilise les groupes", default=False)   # Company uses groups or not
    rules = [("MAJ", "Majorité"), ("PROP", "Proportionnelle")]   # Default management rule
    rule = models.CharField(
        "mode de scrutin", max_length=5, choices=rules, default="MAJ"
    )
    upd_rule = models.BooleanField("choisir la règle de répartition pour chaque événement", default=False)     # Event rule might change from one to another or always use default
    statut = models.CharField("forme juridique", max_length=50)
    siret = models.CharField("SIRET", max_length=50)
    street_num = models.IntegerField("N° de rue", null=True, blank=True)
    street_cplt = models.CharField("complément", max_length=50, null=True, blank=True)
    address1 = models.CharField("adresse", max_length=300)
    address2 = models.CharField(
        "complément d'adresse", max_length=300, null=True, blank=True
    )
    zip_code = models.IntegerField("code postal")
    city = models.CharField("ville", max_length=200)
    host = models.CharField("serveur mail", max_length=50, null=True, blank=True)
    port = models.IntegerField("port du serveur", null=True, blank=True)
    hname = models.EmailField("utilisateur", max_length=100, null=True, blank=True)
    fax = models.CharField("mot de passe", max_length=50, null=True, blank=True)
    use_tls = models.BooleanField("authentification requise", default=True, blank=True)

    class Meta:
        verbose_name = "Société"
        constraints = [
            models.UniqueConstraint(fields=["comp_slug"], name="unique_comp_slug")
        ]

    def __str__(self):
        return self.company_name

    @classmethod
    def get_company(cls, slug):
        """ Retreive company from its slug """
        return cls.objects.get(comp_slug=slug)


class CompanyForm(forms.ModelForm):
    company_name = forms.CharField(label="Société", disabled=True)

    class Meta:
        model = Company
        exclude = []

视图也很简单:

@user_passes_test(lambda u: u.is_superuser or u.usercomp.is_admin)
def adm_options(request, comp_slug):
    '''
        Manage Company options
    '''
    company = Company.get_company(comp_slug)
    comp_form = CompanyForm(request.POST or None, instance=company)

    if request.method == "POST":
        if comp_form.is_valid():
            comp_form.save()

    return render(request, "polls/adm_options.html", locals())

这个视图工作正常,我可以更新信息(它实际上不用于创建,这要归功于 Django 管理面板)。

很遗憾,我无法构建单元测试来确保更新工作正常!
我尝试了两种方法,但都没有奏效。 我的第一次尝试如下:

class TestOptions(TestCase):
    def setUp(self):
        self.company = create_dummy_company("Société de test")
        self.user_staff = create_dummy_user(self.company, "staff", admin=True)
        self.client.force_login(self.user_staff.user)

    def test_adm_options_update(self):
        # Load company options page
        url = reverse("polls:adm_options", args=[self.company.comp_slug])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "0123456789")
        self.assertEqual(self.company.siret, "0123456789")

        # Options update
        response = self.client.post(
            reverse("polls:adm_options", args=[self.company.comp_slug]),
            {"siret": "987654321"}
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "987654321")
        self.assertNotContains(response, "0123456789")
        self.assertEqual(self.company.siret, "987654321")

在这种情况下,一切正常,但最新的断言。看起来更新没有保存,其实不然。我之前尝试读取数据库,密钥存储在上下文中,但它保持不变。

当我找到this topic 时,我正在寻找其他信息,所以我尝试了另一种方法来测试,即使这种方法让我有点吃惊(我看不到视图是如何实际测试的)。
这是我的第二次尝试(setUp() 保持不变):

    def test_adm_options_update(self):
        # Load company options page
        url = reverse("polls:adm_options", args=[self.company.comp_slug])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "0123456789")         # this is the default value in tests for this field
        self.assertEqual(self.company.siret, "0123456789")

        # Options update
        self.company.siret = "987654321"
        comp_form = CompanyForm(instance=self.company)
        self.assertTrue(comp_form.is_valid())
        comp_form.save()
        company = Company.get_company(self.company.comp_slug)
        self.assertEqual(company.siret, "987654321")

在这种情况下,表单只是空的!

我可以认为我的视图有效并继续,我的问题是我在另一个视图中有一个错误,我想确保我可以构建测试以找出错误!

非常感谢您的回答!

编辑 - 8 月 30 日
根据建议,我尝试使用self.company.refresh_from_db(),但并没有改变结果。
尝试传递 self.client.post() 中的所有字段,但一旦字段为空,它就会失败(“无法将无编码为 POST 数据”错误消息)
似乎我还创建了一个“虚拟”公司来进行带有空必填字段的测试……而且它仍然有效。测试环境的问题?我改变了这一点,但我想知道问题是否不在其他任何地方......

编辑 - 9 月 15 日
寻找可以为我提供新想法的人,请=)

为确保我理解最新的命题,这里是完整的测试代码:

def test_adm_options_update(self):
    # Load company options page
    url = reverse("polls:adm_options", args=[self.company.comp_slug])
    response = self.client.get(url)
    self.assertEqual(response.status_code, 200)
    self.assertContains(response, "0123456789")
    self.assertEqual(self.company.siret, "0123456789")

    # Apply changes
    company_data = copy.deepcopy(CompanyForm(instance=self.company).initial)
    company_data['siret'] = "987654321"
    response = self.client.post(
        reverse("polls:adm_options", args=[self.company.comp_slug]),
        company_data,
        )
    self.company.refresh_from_db()
    self.assertEqual(response.status_code, 200)
    self.assertContains(response, "987654321")
    self.assertNotContains(response, "0123456789")
    self.assertEqual(self.company.siret, "987654321")

这是为测试创建“虚拟”公司的函数:

def create_dummy_company(name):
    return Company.objects.create(
        company_name=name,
        comp_slug=slugify(name),
        logo=SimpleUploadedFile(name='logo.jpg', content=b'content', content_type='image/jpeg'),
        statut="SARL",
        siret="0123456789",
        address1="Rue des fauvettes",
        zip_code="99456",
        city='Somewhere',
        host="smtp.gmail.com",
        port=587,
        hname="test@polls.com",
        fax="toto",
    )

【问题讨论】:

    标签: python django forms unit-testing django-tests


    【解决方案1】:

    在这种情况下,一旦视图和表单完成更新您的对象,您需要使用refresh_from_db 来“刷新”您的对象。这意味着当您当前断言时,您使用的是self.company 的“旧快照”,因此断言失败,因此您需要对其进行更新:

            # Options update
            response = self.client.post(
                reverse("polls:adm_options", args=[self.company.comp_slug]),
                {"siret": "987654321"}
            )
            ...
            self.company.refresh_from_db()
            self.assertEqual(self.company.siret, "987654321")
    

    编辑

    想出了一种方法来完成这项工作。由于表单要求您输入所有数据,您只需将公司实例传递给同一表单,并访问初始值(将用作您的请求数据)。

    然后您可以使用所需的更改对其进行修改,在本例中为 siretlogo

    from django.core.files.uploadedfile import SimpleUploadedFile
    
        def test(self):
            company_data = CompanyForm(instance=self.company).initial
            company_data['logo'] = SimpleUploadedFile(name='somefile', content=b'content', content_type='image/jpeg')
            company_data['siret'] = "987654321"
    
            response = self.client.post(
                reverse("polls:adm_options", args=[self.company.comp_slug]),
                company_data,
            )
    
            self.company.refresh_from_db()
            self.assertEqual(self.company.siret, "987654321")
    

    这很有效,并以与您完全相同的模型传递给我。

    【讨论】:

    • 感谢@bdbd 的回答!我认为这将是正确的答案,但不幸的是它仍然不起作用:-/
    • 我明白了。尝试调试它,看看它是否到达视图,表单验证是否正确,以及表单是否保存。您也可以尝试在断言之前使用self.company = Company.get_company(self.company.comp_slug) 而不是refresh_from_db
    • 我试图深入调查,但表格无效(我也尝试过使用 get_company 方法)。实际上,有些字段是空的!其中 2 个依赖于第 3 个,因此页面中的相关字段被禁用:可能会产生任何影响吗?对于其他的,GET 怎么会检索到表单为 POST 时丢失的信息?
    • 尝试打印request.POST 的内容,同时打印form.errors 以查看调用form.is_valid() 后表单出现的错误
    • 我做到了,错误来自空的必填字段...但我不明白为什么它们是空的!
    猜你喜欢
    • 2017-10-19
    • 1970-01-01
    • 2016-12-16
    • 2011-01-16
    • 1970-01-01
    • 2012-11-27
    • 2014-07-07
    • 2012-03-20
    • 1970-01-01
    相关资源
    最近更新 更多