Skip to main content
 首页 » 编程设计

python之Django queryset 结果对于测试是错误的

2024年08月16日38langtianya

我的模型是:

class AndroidOffer(models.Model): 
    name = models.CharField(max_length=128, db_index=True) 
    # ... 
    countries = models.ManyToManyField(Country) 

以及以下代码(我跳过了之前的过滤):

active_offers = active_offers.filter(countries__in=[country]) 

它生成这个 SQL 查询:

SELECT "offers_androidoffer"."id", "offers_androidoffer"."name", "offers_androidoffer"."title", "offers_androidoffer"."is_for_android", "offers_androidoffer"."is_for_ios", "offers_androidoffer"."url", "offers_androidoffer"."icon", "offers_androidoffer"."cost", "offers_androidoffer"."quantity", "offers_androidoffer"."hourly_completions", "offers_androidoffer"."is_active", "offers_androidoffer"."description", "offers_androidoffer"."comment", "offers_androidoffer"."priority", "offers_androidoffer"."offer_type", "offers_androidoffer"."package_name", "offers_androidoffer"."is_search_install", "offers_androidoffer"."search_query", "offers_androidoffer"."launches" FROM "offers_androidoffer" INNER JOIN "offers_androidoffer_platform_versions" ON ("offers_androidoffer"."id" = "offers_androidoffer_platform_versions"."androidoffer_id") INNER JOIN "offers_androidoffer_countries" ON ("offers_androidoffer"."id" = "offers_androidoffer_countries"."androidoffer_id") WHERE ("offers_androidoffer"."is_active" = True AND "offers_androidoffer"."quantity" > 0 AND NOT ("offers_androidoffer"."id" IN (SELECT U0."offer_id" FROM "offers_androidofferstate" U0 WHERE (U0."device_id" = 1 AND (U0."state" = 3 OR U0."state" = 4)))) AND NOT ("offers_androidoffer"."package_name" IN (SELECT V0."package_name" FROM "applications_app" V0 INNER JOIN "applications_deviceapp" V1 ON (V0."id" = V1."app_id") WHERE (V1."device_id" IN (SELECT U0."device_id" FROM "users_userdevice" U0 WHERE U0."user_id" = 2) AND NOT (V0."package_name" IN (SELECT U2."package_name" FROM "offers_androidofferstate" U0 INNER JOIN "offers_androidoffer" U2 ON (U0."offer_id" = U2."id") WHERE (U0."device_id" = 1 AND (U0."state" = 0 OR U0."state" = 1 OR U0."state" = 2))))))) AND "offers_androidoffer_platform_versions"."platformversion_id" IN (14) AND "offers_androidoffer_countries"."country_id" IN (6252001)) ORDER BY "offers_androidoffer"."priority" DESC; 

如果我在 Postgresql 控制台中运行此查询,它将返回 0 行,但是 active_offers 有 4 个结果(表中的所有行),就像我删除 AND "offers_androidoffer_countries"一样。"country_id"IN (6252001) 语句。

我从测试(APITestCase.client -> DRF view -> filter queryset)运行这段代码。 Django 版本为 2.0.2。

为什么它会忽略国家/地区过滤?

UPD. 我刚刚检查了简单的 TestCase(测试 -> 过滤器查询集)测试,它返回了正确的行数。因此,问题仅存在于 DRF 测试中。

UPD 2. 工作不正常的测试用例:

class AndroidOffersListTests(APITestCase): 
    fixtures = [ 
        'geo/fixtures/cities.json', 
        'offers/fixtures/users.json', 
        'offers/fixtures/devices.json', 
        'offers/fixtures/geo.json', 
        'offers/fixtures/apps.json', 
        'offers/fixtures/offers.json', 
    ] 
 
    def test_list_offers_1(self): 
        user_device = UserDevice.objects.get(pk=1) 
 
        token = AndroidOffersListTests.get_token_for_device(user_device) 
        self.client.credentials(HTTP_AUTHORIZATION='Token {}'.format(token)) 
 
        url = AndroidOffersListTests.get_url(user_device) 
        response = self.client.get(url) 
 
        self.assertEqual(status.HTTP_200_OK, response.status_code) 
        self.assertEqual(0, len(response.data)) # result is 4 

查看代码:

class AndroidOffersView(ListAPIView): 
    model = AndroidOffer 
    serializer_class = AndroidOffersSerializer 
    permission_classes = (IsAuthenticated,) 
 
    def get_queryset(self): 
        device = UserDevice.get_from_request(self.request) 
        if device is None: 
            raise PermissionDenied() 
 
        return AndroidOffer.get_offers_for_device(device) 

get_offers_for_device:

@staticmethod 
def get_offers_for_device(user_device): 
    active_offers = AndroidOffer.get_active_offers() 
 
    # Filter completed 
    completed_states = AndroidOfferState.get_completed_for_device(user_device) 
    completed_offers_ids = completed_states.values_list('offer__pk', flat=True) 
 
    active_offers = active_offers.exclude(pk__in=completed_offers_ids) 
 
    # Filter apps already installed on the user's devices 
    apps = user_device.user.apps 
 
    # Remove packages that are in progress 
    in_progress_states = AndroidOfferState.get_in_progress_for_device(user_device) 
    in_progress_packages = in_progress_states.values_list('offer__package_name', flat=True) 
    apps = apps.exclude(package_name__in=in_progress_packages) 
 
    packages = apps.values_list('package_name', flat=True) 
 
    active_offers = active_offers.exclude(package_name__in=packages) 
 
    # Filter by platform version 
    active_offers = active_offers.filter(platform_versions__in=[user_device.device.version]) 
 
    # Filter by country 
    country = user_device.last_geo_record.country 
    if country is not None: 
        active_offers = active_offers.filter(countries__in=[country]) 
 
    return active_offers 

正常工作的测试用例:

class AndroidOffersListTests(TestCase): 
    fixtures = [ 
        'geo/fixtures/cities.json', 
        'offers/fixtures/users.json', 
        'offers/fixtures/devices.json', 
        'offers/fixtures/geo.json', 
        'offers/fixtures/apps.json', 
        'offers/fixtures/offers.json', 
    ] 
 
    def test_list_offers_1(self): 
        user_device = UserDevice.objects.get(pk=1) 
 
        offers = AndroidOffer.get_offers_for_device(user_device) 
 
        self.assertEqual(0, offers.count()) # 0 — thats ok 

更新 3:当我在浏览器中运行相同的请求时,它工作正常:

请您参考如下方法:

您说此回复不正确:

self.assertEqual(0, len(response.data)) # result is 4 

但您还说此 JSON 响应正确:

{ 
    "count": 0, 
    "next": null, 
    "previous": null, 
    "results": [] 
} 

您在这里使用的是分页 API。 4 的长度取决于反序列化 json 中存在的键数:

>>> len(json.loads('{"count": 0, "next": null, "previous": null, "results": []}')) 
4 

请注意,您不需要自己实际调用 json.loads,DRF 框架已经在准备响应时为您处理了这个问题 - 即 response.data已经是一个命令了。

在“工作正常的测试用例”中,您直接处理查询集:

self.assertEqual(0, offers.count()) # 0 — thats ok 
                         ^ 
                         |____ here you go to the database, no serializer! 

如果您想从分页的 JSON api 检查结果的数量,那么您需要向下钻取该页面:

len_results = len(response.data['results']) 

对于预期返回 0 个结果的测试,这就足够了。但请注意 - 如果您曾经进行过预期生成的结果多于页面大小(在设置中配置)的测试,您可能还需要检查 count 和 next值。您必须向后续页面发出额外请求才能收集所有结果。