【问题标题】:Django fixture with all possible combinations?具有所有可能组合的 Django 夹具?
【发布时间】:2018-04-30 11:06:07
【问题描述】:

我正在尝试测试一个统计函数,该函数计算我的数据库中的对象类型。为此,我想为每个可能的模型字段组合创建至少一个实例。随机测试数据需要大量的测试对象来确保满足所有可能的组合。

这是我的一个模型中的一个简短示例:

class Member(models.Model)
    is_active = models.BooleanField()
    name = models.CharField()
    balance = models.IntegerField()
    division = models.ForeignKey(Division, on_delete=models.CASCADE)

Class Division(models.Model)
    name = models.CharField()

这就是我现在使用 django_dynamic_fixture 的方式:

from django.test import TestCase
from django_dynamic_fixture import G
from members.models import Member

class StatisticsTestCase(TestCase):
    def setUp(self):
        for is_active in [True, False]:
            for balance in [-100, 0, 100]:
                for division in Division.objects.all()
                    G(Member, is_active=is_active, balance=balance, division=division)

但这看起来很糟糕,很难阅读。有没有更简单的方法可以更好地阅读代码来创建对象的所有可能的属性组合?

我对不同的测试数据生成器持开放态度,只要它们能很好地与 Python 3 和 Django 配合使用。

【问题讨论】:

    标签: python django unit-testing fixtures


    【解决方案1】:

    Sara 的回答是一个很好的进步,但我对代码的可读性仍然不满意,所以我正在回答我自己的问题。

    variants = {
        'is_active': [True, False],
        'balance': [-100, 0, 100],
        'division': list(Division.objects.all())
    }
    
    names = sorted(variants)
    products = [dict(zip(names, prod)) for prod in it.product(*(variants[name] for name in names))]
    
    for combination in products:
        G(Member, **combination)
    

    现在,任何想要更改此测试的人都可以轻松查看使用哪些值测试了哪个属性。感谢 this q&a 获取 dict 解决方案。

    【讨论】:

    • 您的代码中有一个SyntaxError [*Division.objects.all()]。您无法在列表中解压缩列表。你说得对。我的看法很狭隘。
    • @xyres Division.objects.all() 返回一个django.db.models.query.QuerySet,可以像这样解包。但由于我是谈论可读性的人:list(Division.objects.all() 更好理解。感谢您指出。
    • 不,[*Division.objects.all()]SyntaxError。它与 Django QuerySet 无关,因为 SyntaxError 意味着它不是有效的 Python 语法。除了将参数传递给函数之外,您不能使用* 在任何地方解压。你可以在 Python shell 中尝试一下,看看我的意思。
    • @xyres 你可能没有意识到我的问题是关于 Python 3 的吗?因为它是并且在 Python 3 中 [*[1, 2]] 不是语法错误。
    【解决方案2】:
    import itertools
    
    combinations = list(itertools.product(*[[True, False], [-100, 0, 100]]))
    for division in Division.objects.all():
        for is_active, balance in combinations:
            G(Member, is_active=is_active, balance=balance, division=division)
    

    更新:

    import itertools
    
    combinations = list(itertools.product([True, False], [-100, 0, 100], Division.objects.all()))
    for is_active, balance, division in combinations:
        G(Member, is_active=is_active, balance=balance, division=division)
    

    【讨论】:

    • 这里 - product(*[[True, False], [-100, 0, 100]]) - 你为什么要在列表中传递参数然后解包列表?只是product([True, False], [-100, 0, 100]) 工作正常。也无需将生成器转换为list。生成器比普通列表高效得多。
    • 此外,您还可以通过将division 对象也放入combinations 来进一步改进答案。
    • 是的,好主意,我会写一个更新,至于你的第一个问题,我想说任何一种方式都有效,因为函数没有指定输入长度:docs.python.org/3/library/itertools.html#itertools.product
    • 嗯,我觉得这个读起来比较好,但还是不满意。现在我要建立一个长长的值列表,与它们的含义无关,但与它们的顺序有关。这对 3 个属性来说很好,但如果你有 10 个属性,最好有一个将值连接到标签的 dict。
    • 我也希望在一些测试数据/夹具生成器中找到它作为现成的功能;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 2018-09-25
    相关资源
    最近更新 更多