Вход в тест Django завершится неудачно, если не будет закомментирована или переименована совершенно не связанная, абсолютно тривиальная функция
Я получаю странную ошибку в моем тестовом коде Django.
Полный код:
from .models import MIN_BIRTH_YEAR
from .models import UserProfile
from django.contrib.auth.models import User
from django.test import TestCase
import factory
TEST_USERS = []
TEST_PASSWORD = 'password123abc'
class UserProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserProfile
birth_year = factory.Sequence(lambda n: n + MIN_BIRTH_YEAR - 1)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
profile = factory.RelatedFactory(UserProfileFactory, 'user')
username = factory.Sequence(lambda n: 'test_username{}'.format(n))
first_name = factory.Sequence(lambda n: 'test_first_name{}'.format(n))
last_name = factory.Sequence(lambda n: 'test_last_name{}'.format(n))
email = factory.Sequence(lambda n: 'test_email{}@example.com'.format(n))
password = factory.PostGenerationMethodCall('set_password', TEST_PASSWORD)
def create_insert_test_users():
for i in range(5):
TEST_USERS.append(UserFactory.create())
def _test_one_logged_in_user(test_instance, test_user_index):
"""
In addition to public information, private content for a single
logged-in user should be somewhere on the page.
"""
test_instance.client.logout()
test_user = TEST_USERS[test_user_index]
print('Attempting to login:')
profile = test_user.profile
print('test_user.id=' + str(test_user.id))
print(' username=' + test_user.username + ', password=' + TEST_PASSWORD)
print(' first_name=' + test_user.first_name + ', last_name=' + test_user.last_name)
print(' email=' + test_user.email)
print(' profile=' + str(profile))
print(' profile.birth_year=' + str(profile.birth_year))
Продолжение. Это строка логина, о которой я говорю. это _test_one_logged_in_user
функция вызывается от второй до последней строки (_test_one_logged_in_user(self, 0)
) ниже:
did_login_succeed = test_instance.client.login(
username=test_user.username,
password=TEST_PASSWORD)
test_instance.assertTrue(did_login_succeed)
##########################################
# GET PAGE AND TEST ITS CONTENTS HERE...
##########################################
class MainPageTestCase(TestCase):
"""Tests for the main page."""
def setUp(self_ignored):
"""Insert test users."""
create_insert_test_users()
def test_true_is_true(self):
"""Public information should be somewhere on the page."""
self.assertTrue(True)
def test_logged_in_users(self):
"""
In addition to public information, private content for logged in
users should also be somewhere on the page.
"""
_test_one_logged_in_user(self, 0)
_test_one_logged_in_user(self, 1)
Это отлично работает. Все проходит. Но измените имя test_true_is_true
в test_content_not_logged_in
def test_content_not_logged_in(self):
"""Public information should be somewhere on the page."""
self.assertTrue(True)
а также test_instance.client.login
сейчас возвращается False
... что приводит к утверждению под ним
test_instance.assertTrue(did_login_succeed)
терпеть неудачу: AssertionError: False is not true
, Если вы закомментируете всю функцию, она завершится успешно (логин вернется True
).
# def test_content_not_logged_in(self):
# """Public information should be somewhere on the page."""
# self.assertTrue(True)
Если вы раскомментируете его и переименуете в одно из следующих действий, это сработает:
test_xcontent_not_logged_in
test__content_not_logged_in
test_not_logged_in
Любой из них, и он терпит неудачу:
test_ctrue_is_true
test_cxontent_not_logged_in
test_contentnot_logged_in
test_contennot_logged_in
test_contenot_logged_in
test_contnot_logged_in
test_connot_logged_in
test_cnot_logged_in
test_c
(Я искалtest_c
и нашел что-то, но ничего не указывает на что-то особенно особенное.)
Это, кажется, подразумевает, что setUp
функция запускается один раз для test_content_not_logged_in
(тривиальная функция), а затем снова для test_logged_in_users
, И этот запуск дважды вызывает проблемы. Поэтому я изменил его, чтобы пользователи создавались только в том случае, если TEST_USER
массив пуст:
def create_insert_test_users():
if len(TEST_USERS) == 0:
for i in range(5):
TEST_USERS.append(UserFactory.create())
Но это все еще терпит неудачу, и я могу подтвердить, что это терпит неудачу с пользователем, имеющим идентификатор 1:
$ python -Wall manage.py test auth_lifecycle.test__view_main2
/home/jeffy/django_files/django_auth_lifecycle_venv/lib/python3.4/site.py:165: DeprecationWarning: 'U' mode is deprecated
f = open(fullname, "rU")
/home/jeffy/django_files/django_auth_lifecycle_venv/lib/python3.4/imp.py:32: PendingDeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
PendingDeprecationWarning)
Creating test database for alias 'default'...
.Attempting to login:
test_user.id=1
username=test_username1, password=password123abc
first_name=test_first_name1, last_name=test_last_name1
email=test_email1@example.com
profile=test_username1
profile.birth_year=1887
F
======================================================================
FAIL: test_logged_in_users (auth_lifecycle.test__view_main2.MainPageTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/jeffy/django_files/django_auth_lifecycle/auth_lifecycle/test__view_main2.py", line 74, in test_logged_in_users
_test_one_logged_in_user(self, 0)
File "/home/jeffy/django_files/django_auth_lifecycle/auth_lifecycle/test__view_main2.py", line 53, in _test_one_logged_in_user
test_instance.assertTrue(did_login_succeed)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.385s
FAILED (failures=1)
Destroying test database for alias 'default'...
models.py:
"""Defines a single extra user-profile field for the user-authentication
lifecycle demo project:
- Birth year, which must be between <link to MIN_BIRTH_YEAR> and
<link to MAX_BIRTH_YEAR>, inclusive.
"""
from datetime import datetime
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models
OLDEST_EVER_AGE = 127 #:Equal to `127`
YOUNGEST_ALLOWED_IN_SYSTEM_AGE = 13 #:Equal to `13`
MAX_BIRTH_YEAR = datetime.now().year - YOUNGEST_ALLOWED_IN_SYSTEM_AGE
"""Most recent allowed birth year for (youngest) users."""
MIN_BIRTH_YEAR = datetime.now().year - OLDEST_EVER_AGE
"""Most distant allowed birth year for (oldest) users."""
def _validate_birth_year(birth_year_str):
"""Validator for <link to UserProfile.birth_year>, ensuring the
selected year is between <link to OLDEST_EVER_AGE> and
<link to MAX_BIRTH_YEAR>, inclusive.
Raises:
ValidationError: When the selected year is invalid.
https://docs.djangoproject.com/en/1.7/ref/validators/
I am a recovered Hungarian Notation junkie (I come from Java). I
stopped using it long before I started with Python. In this
particular function, however, because of the necessary cast, it's
appropriate.
"""
birth_year_int = -1
try:
birth_year_int = int(str(birth_year_str).strip())
except TypeError:
raise ValidationError(u'"{0}" is not an integer'.format(birth_year_str))
if not (MIN_BIRTH_YEAR <= birth_year_int <= MAX_BIRTH_YEAR):
message = (u'{0} is an invalid birth year.'
u'Must be between {1} and {2}, inclusive')
raise ValidationError(message.format(
birth_year_str, MIN_BIRTH_YEAR, MAX_BIRTH_YEAR))
#It's all good.
class UserProfile(models.Model):
"""Extra information about a user: Birth year.
---NOTES---
Useful related SQL:
- `select id from auth_user where username <> 'admin';`
- `select * from auth_lifecycle_userprofile where user_id=(x,x,...);`
"""
# This line is required. Links UserProfile to a User model instance.
user = models.OneToOneField(User, related_name="profile")
# The additional attributes we wish to include.
birth_year = models.IntegerField(
blank=True,
verbose_name="Year you were born",
validators=[_validate_birth_year])
# Override the __str__() method to return out something meaningful
def __str__(self):
return self.user.username
3 ответа
Когда вы меняете название теста, вы меняете порядок его выполнения. Метод test_logged_in_users запускается BEFORE test_true_is_true, но выполняет AFTER test_c_whothing (предположительно потому, что он запускает их в альфа-порядке или в некотором порядке). Вот почему вы видите странность с изменениями имени.
Как вы выяснили, ваш метод setUp запускается для каждого теста. Когда ваш setUp запускается в первый раз, пользователи создаются и сохраняются как в БД, так и в TEST_USERS. При запуске второго теста ваша БД обновляется, и все ваши пользователи удаляются. Пользователи, представленные TEST_USERS (которые все еще находятся в вашем списке, потому что ваши глобальные переменные сохраняются в тестовых случаях) больше не существуют в БД.
Вы можете выполнить тест в исходном коде, сбросив TEST_USERS, например так:
def create_insert_test_users():
# global tells python to use the TEST_USERS above, not create a new one
global TEST_USERS
TEST_USERS = []
# Your code here...
Теперь TEST_USERS представляет новых реальных пользователей, которые соответствуют пользователям в БД. Вообще говоря, глобальные ошибки - плохая идея ( по нескольким причинам, путаница, которую вы испытываете, будучи среди них). Создание их на лету (как вы работаете в своем последнем обновлении) является гораздо лучшим решением.
TestCase распознает все тесты, ища методы, которые начинаются с test
Из документации:
Индивидуальные тесты определяются методами, имена которых начинаются с буквенного теста. Это соглашение об именах сообщает организатору теста, какие методы представляют тесты.
Поэтому, когда вы переименовываете a_trivial_function
это меняет, считается ли это тестом или нет.
Оригинальный код магазина TEST_USERS
локально, и кажется, что статически удерживаемый объект вызывал проблемы при совместном использовании между тестами. Я наивно думал, что важно хранить объекты локально, чтобы сравнить значения базы данных с ними. Это означает, что я не доверяю Django или Factory Boy правильно вставлять их в базу данных, и они справляются с этим просто отлично.
Вот обновленный код, который хранит только объекты в базе данных. Я также переместил подфункцию содержимого, содержащую login
прямо в нижнюю функцию.
from .models import MIN_BIRTH_YEAR
from .models import UserProfile
from django.contrib.auth.models import User
from django.test import TestCase
import factory
TEST_PASSWORD = 'password123abc'
TEST_USER_COUNT = 5
class UserProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserProfile
birth_year = factory.Sequence(lambda n: n + MIN_BIRTH_YEAR - 1)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
profile = factory.RelatedFactory(UserProfileFactory, 'user')
username = factory.Sequence(lambda n: 'test_username{}'.format(n))
#print('username=' + username)
first_name = factory.Sequence(lambda n: 'test_first_name{}'.format(n))
last_name = factory.Sequence(lambda n: 'test_last_name{}'.format(n))
email = factory.Sequence(lambda n: 'test_email{}@example.com'.format(n))
password = factory.PostGenerationMethodCall('set_password', TEST_PASSWORD)
class MainPageTestCase(TestCase):
"""Tests for the main page."""
def setUp(self_ignored):
"""Insert test users."""
#print('a User.objects.count()=' + str(User.objects.count()))
#http://factoryboy.readthedocs.org/en/latest/reference.html?highlight=create#factory.create_batch
UserFactory.create_batch(TEST_USER_COUNT)
#print('b User.objects.count()=' + str(User.objects.count()))
Продолжение:
def test_ctrue_is_true(self):
"""Public information should be somewhere on the page."""
self.assertTrue(True)
def test_some_logged_in_users(self):
"""
In addition to public information, private content for logged in
users should also be somewhere on the page.
"""
for n in range(2):
self.client.logout()
test_user = UserFactory()
print('Attempting to login:')
profile = test_user.profile
print('test_user.id=' + str(test_user.id))
print(' username=' + test_user.username + ', password=' + TEST_PASSWORD)
print(' first_name=' + test_user.first_name + ', last_name=' + test_user.last_name)
print(' email=' + test_user.email)
print(' profile=' + str(profile))
print(' profile.birth_year=' + str(profile.birth_year))
did_login_succeed = self.client.login(
username=test_user.username,
password=TEST_PASSWORD)
self.assertTrue(did_login_succeed)
##########################################
# GET PAGE AND TEST ITS CONTENTS HERE...
##########################################