Django 3.0.8 测试用例失败
Posted
技术标签:
【中文标题】Django 3.0.8 测试用例失败【英文标题】:Django 3.0.8 Test Cases Failing 【发布时间】:2020-12-15 01:29:36 【问题描述】:我正在尝试遵循Simple is Better than Complex 教程。具体来说,我刚刚完成了Part 4。我在运行测试时遇到以下 4 种失败:
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.............................FFF.F..................................
======================================================================
FAIL: test_contains_form (accounts.tests.test_view_password_reset.PasswordResetTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\admin\Desktop\Development\Django\myproject\myproject\accounts\tests\tes
t_view_password_reset.py", line 30, in test_contains_form
self.assertIsInstance(form, PasswordResetForm)
AssertionError: None is not an instance of <class 'django.contrib.auth.forms.PasswordRes
etForm'>
======================================================================
FAIL: test_csrf (accounts.tests.test_view_password_reset.PasswordResetTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\admin\Desktop\Development\Django\myproject\myproject\accounts\tests\tes
t_view_password_reset.py", line 26, in test_csrf
self.assertContains(self.response, 'csrfmiddlewaretoken')
File "C:\Users\admin\Desktop\Development\Django\myproject\venv\lib\site-packages\djang
o\test\testcases.py", line 454, in assertContains
self.assertTrue(real_count != 0, msg_prefix + "Couldn't find %s in response" % text_
repr)
AssertionError: False is not true : Couldn't find 'csrfmiddlewaretoken' in response
======================================================================
FAIL: test_form_inputs (accounts.tests.test_view_password_reset.PasswordResetTests)
The view must contain two inputs: csrf and email
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\admin\Desktop\Development\Django\myproject\myproject\accounts\tests\tes
t_view_password_reset.py", line 36, in test_form_inputs
self.assertContains(self.response, '<input', 2)
File "C:\Users\admin\Desktop\Development\Django\myproject\venv\lib\site-packages\djang
o\test\testcases.py", line 449, in assertContains
self.assertEqual(
AssertionError: 0 != 2 : Found 0 instances of '<input' in response (expected 2)
======================================================================
FAIL: test_view_function (accounts.tests.test_view_password_reset.PasswordResetTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\admin\Desktop\Development\Django\myproject\myproject\accounts\tests\tes
t_view_password_reset.py", line 23, in test_view_function
self.assertEquals(view.func.view_class, auth_views.PasswordResetView)
AssertionError: <class 'django.contrib.auth.views.PasswordResetDoneView'> != <class 'dja
ngo.contrib.auth.views.PasswordResetView'>
----------------------------------------------------------------------
Ran 68 tests in 9.235s
FAILED (failures=4)
Destroying test database for alias 'default'...
这是我的文件结构:
├───myproject
│ │ db.sqlite3
│ │ manage.py
│ │
│ ├───accounts
│ │ │ admin.py
│ │ │ apps.py
│ │ │ forms.py
│ │ │ models.py
│ │ │ views.py
│ │ │ __init__.py
│ │ │
│ │ ├───migrations
│ │ │ │ __init__.py
│ │ │ │
│ │ │ └───__pycache__
│ │ │
│ │ ├───tests
│ │ │ │ test_form_signup.py
│ │ │ │ test_mail_password_reset.py
│ │ │ │ test_view_password_change.py
│ │ │ │ test_view_password_reset.py
│ │ │ │ test_view_signup.py
│ │ │ │ __init__.py
│ │ │ │
│ │ │ └───__pycache__
│ │ │
│ │ └───__pycache__
│ │
│ ├───boards
│ │ │ admin.py
│ │ │ apps.py
│ │ │ forms.py
│ │ │ models.py
│ │ │ views.py
│ │ │ __init__.py
│ │ │
│ │ ├───migrations
│ │ │ │ 0001_initial.py
│ │ │ │ __init__.py
│ │ │ │
│ │ │ └───__pycache__
│ │ │
│ │ ├───templatetags
│ │ │ │ form_tags.py
│ │ │ │ __init__.py
│ │ │ │
│ │ │ └───__pycache__
│ │ │
│ │ ├───tests
│ │ │ │ test_templatetags.py
│ │ │ │ test_views.py
│ │ │ │ __init__.py
│ │ │ │
│ │ │ └───__pycache__
│ │ │
│ │ └───__pycache__
│ │
│ ├───myproject
│ │ │ asgi.py
│ │ │ settings.py
│ │ │ urls.py
│ │ │ wsgi.py
│ │ │ __init__.py
│ │ │
│ │ └───__pycache__
│ │
│ ├───static
│ │ ├───css
│ │ │ accounts.css
│ │ │ app.css
│ │ │ bootstrap.min.css
│ │ │
│ │ ├───img
│ │ │ moroccan-flower-dark.png
│ │ │ y-so-serious.png
│ │ │
│ │ ├───js
│ │ │ bootstrap.min.js
│ │ │ jquery-3.5.1.min.js
│ │ │ popper.min.js
│ │
│ └───templates
│ │ base.html
│ │ base_accounts.html
│ │ home.html
│ │ login.html
│ │ new_topic.html
│ │ password_change.html
│ │ password_change_done.html
│ │ password_reset.html
│ │ password_reset_complete.html
│ │ password_reset_confirm.html
│ │ password_reset_done.html
│ │ password_reset_email.html
│ │ password_reset_subject.txt
│ │ signup.html
│ │ topics.html
│ │
│ └───includes
│ form.html
这是myproject\accounts\tests\test_view_password_reset.py
中似乎引发错误的部分:
from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.contrib.auth import views as auth_views
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
from django.contrib.auth.models import User
from django.core import mail
from django.urls import reverse
from django.urls import resolve
from django.test import TestCase
class PasswordResetTests(TestCase):
def setUp(self):
url = reverse('password_reset_done')
self.response = self.client.get(url)
def test_status_code(self):
self.assertEquals(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/reset/done/')
self.assertEquals(view.func.view_class, auth_views.PasswordResetView)
def test_csrf(self):
self.assertContains(self.response, 'csrfmiddlewaretoken')
def test_contains_form(self):
form = self.response.context.get('form')
self.assertIsInstance(form, PasswordResetForm)
def test_form_inputs(self):
'''
The view must contain two inputs: csrf and email
'''
self.assertContains(self.response, '<input', 2)
self.assertContains(self.response, 'type="email"', 1)
class SuccessfulPasswordResetTests(TestCase):
def setUp(self):
email = 'john@doe.com'
User.objects.create_user(username='john', email=email, password='123abcdef')
url = reverse('password_reset')
self.response = self.client.post(url, 'email': email)
def test_redirection(self):
'''
A valid form submission should redirect the user to `password_reset_done` view
'''
url = reverse('password_reset_done')
self.assertRedirects(self.response, url)
def test_send_password_reset_email(self):
self.assertEqual(1, len(mail.outbox))
class InvalidPasswordResetTests(TestCase):
def setUp(self):
url = reverse('password_reset')
self.response = self.client.post(url, 'email': 'donotexist@email.com')
def test_redirection(self):
'''
Even invalid emails in the database should
redirect the user to `password_reset_done` view
'''
url = reverse('password_reset_done')
self.assertRedirects(self.response, url)
def test_no_reset_email_sent(self):
self.assertEqual(0, len(mail.outbox))
class PasswordResetDoneTests(TestCase):
def setUp(self):
url = reverse('password_reset_done')
self.response = self.client.get(url)
def test_status_code(self):
self.assertEquals(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/reset/done/')
self.assertEquals(view.func.view_class, auth_views.PasswordResetDoneView)
class PasswordResetConfirmTests(TestCase):
def setUp(self):
user = User.objects.create_user(username='john', email='john@doe.com', password='123abcdef')
'''
create a valid password reset token
based on how django creates the token internally:
https://github.com/django/django/blob/1.11.5/django/contrib/auth/forms.py#L280
'''
self.uid = urlsafe_base64_encode(force_bytes(user.pk))
self.token = default_token_generator.make_token(user)
url = reverse('password_reset_confirm', kwargs='uidb64': self.uid, 'token': self.token)
self.response = self.client.get(url, follow=True)
def test_status_code(self):
self.assertEquals(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/reset/uidb64/token/'.format(uidb64=self.uid, token=self.token))
self.assertEquals(view.func.view_class, auth_views.PasswordResetConfirmView)
def test_csrf(self):
self.assertContains(self.response, 'csrfmiddlewaretoken')
def test_contains_form(self):
form = self.response.context.get('form')
self.assertIsInstance(form, SetPasswordForm)
def test_form_inputs(self):
'''
The view must contain two inputs: csrf and two password fields
'''
self.assertContains(self.response, '<input', 3)
self.assertContains(self.response, 'type="password"', 2)
class InvalidPasswordResetConfirmTests(TestCase):
def setUp(self):
user = User.objects.create_user(username='john', email='john@doe.com', password='123abcdef')
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
'''
invalidate the token by changing the password
'''
user.set_password('abcdef123')
user.save()
url = reverse('password_reset_confirm', kwargs='uidb64': uid, 'token': token)
self.response = self.client.get(url)
def test_status_code(self):
self.assertEquals(self.response.status_code, 200)
def test_html(self):
password_reset_url = reverse('password_reset')
self.assertContains(self.response, 'invalid password reset link')
self.assertContains(self.response, 'href="0"'.format(password_reset_url))
class PasswordResetCompleteTests(TestCase):
def setUp(self):
url = reverse('password_reset_complete')
self.response = self.client.get(url)
def test_status_code(self):
self.assertEquals(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/reset/complete/')
self.assertEquals(view.func.view_class, auth_views.PasswordResetCompleteView)
我的项目\urls.py
from django.contrib import admin
from django.urls import path
from django.contrib.auth import views as auth_views
from accounts import views as accounts_views
from boards import views
from django.conf.urls import url
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^signup/$', accounts_views.signup, name='signup'),
url(r'^login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
url(r'^logout/$', auth_views.LogoutView.as_view(), name='logout'),
url(r'^boards/(?P<pk>\d+)/$', views.board_topics, name='board_topics'),
url(r'^boards/(?P<pk>\d+)/new/$', views.new_topic, name='new_topic'),
path('admin/', admin.site.urls),
#Password Reset URLs
url(r'^reset/$',
auth_views.PasswordResetView.as_view(
template_name='password_reset.html',
email_template_name='password_reset_email.html',
subject_template_name='password_reset_subject.txt'
),
name='password_reset'),
url(r'^reset/done/$',
auth_views.PasswordResetDoneView.as_view(template_name='password_reset_done.html'),
name='password_reset_done'),
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]1,13-[0-9A-Za-z]1,20)/$',
auth_views.PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'),
name='password_reset_confirm'),
url(r'^reset/complete/$',
auth_views.PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'),
name='password_reset_complete'),
url(r'^settings/password/$', auth_views.PasswordChangeView.as_view(template_name='password_change.html'),
name='password_change'),
url(r'^settings/password/done/$', auth_views.PasswordChangeDoneView.as_view(template_name='password_change_done.html'),
name='password_change_done'),
]
accounts\views.py
from django.contrib.auth import login as auth_login
from django.shortcuts import render, redirect
from .forms import SignUpForm
# Create your views here.
def signup(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save()
auth_login(request, user)
return redirect('home')
else:
form = SignUpForm()
return render(request, 'signup.html', 'form': form)
boards\views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.http import Http404
from django.contrib.auth.models import User
from django.shortcuts import render, redirect, get_object_or_404
from .forms import NewTopicForm
from .models import Board, Topic, Post
# Create your views here.
def home(request):
boards = Board.objects.all()
return render(request, 'home.html', 'boards': boards)
def board_topics(request, pk):
board = get_object_or_404(Board, pk=pk)
return render(request, 'topics.html', 'board': board)
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
user = User.objects.first() # TODO: get the currently logged in user
if request.method == 'POST':
form = NewTopicForm(request.POST)
if form.is_valid():
topic = form.save(commit=False)
topic.board = board
topic.starter = user
topic.save()
post = Post.objects.create(
message=form.cleaned_data.get('message'),
topic=topic,
created_by=user
)
return redirect('board_topics', pk=board.pk) # TODO: redirect to the created topic page
else:
form = NewTopicForm()
return render(request, 'new_topic.html', 'board': board, 'form': form)
模板\base.html
% load static %<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>% block title %Django Boards% endblock %</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed&display=swap" rel="stylesheet">
<link rel="stylesheet" href="% static 'css/bootstrap.min.css' %">
<link rel="stylesheet" href="% static 'css/app.css' %">
% block stylesheet %% endblock %
</head>
<body>
% block body %
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="% url 'home' %">Django Boards</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainMenu" aria-controls="mainMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainMenu">
% if user.is_authenticated %
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
user.username
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="userMenu">
<a class="dropdown-item" href="#">My account</a>
<a class="dropdown-item" href="#">Change password</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="% url 'logout' %">Log out</a>
</div>
</li>
</ul>
% else %
<form class="form-inline ml-auto">
<a href="% url 'login' %" class="btn btn-outline-secondary">Log in</a>
<a href="% url 'signup' %" class="btn btn-primary ml-2">Sign up</a>
</form>
% endif %
</div>
</div>
</nav>
<div class="container">
<ol class="breadcrumb my-4">
% block breadcrumb %
% endblock %
</ol>
% block content %
% endblock %
</div>
% endblock body %
<script src="% static 'js/jquery-3.5.1.min.js' %"></script>
<script src="% static 'js/popper.min.js' %"></script>
<script src="% static 'js/bootstrap.min.js' %"></script>
</body>
</html>
模板\base_accounts.html
% extends 'base.html' %
% load static %
% block stylesheet %
<link rel="stylesheet" href="% static 'css/accounts.css' %">
% endblock %
% block body %
<div class="container">
<h1 class="text-center logo my-4">
<a href="% url 'home' %">Django Boards</a>
</h1>
% block content %
% endblock %
</div>
% endblock %
模板\包含\form.html
% load form_tags widget_tweaks %
% if form.non_field_errors %
<div class="alert alert-danger" role="alert">
% for error in form.non_field_errors %
<p% if forloop.last % class="mb-0"% endif %> error </p>
% endfor %
</div>
% endif %
% for field in form %
<div class="form-group">
field.label_tag
% render_field field class=field|input_class %
% for error in field.errors %
<div class="invalid-feedback">
error
</div>
% endfor %
% if field.help_text %
<small class="form-text text-muted">
field.help_text|safe
</small>
% endif %
</div>
% endfor %
模板\password_reset.html
% extends 'base_accounts.html' %
% block title %Reset your password% endblock %
% block content %
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6 col-sm-8">
<div class="card">
<div class="card-body">
<h3 class="card-title">Reset your password</h3>
<p>Enter your email address and we will send you a link to reset your password.</p>
<form method="post" novalidate>
% csrf_token %
% include 'includes/form.html' %
<button type="submit" class="btn btn-primary btn-block">Send password reset email</button>
</form>
</div>
</div>
</div>
</div>
% endblock %
模板\password_reset_complete.html
% extends 'base_accounts.html' %
% block title %Password changed!% endblock %
% block content %
<div class="row justify-content-center">
<div class="col-lg-6 col-md-8 col-sm-10">
<div class="card">
<div class="card-body">
<h3 class="card-title">Password changed!</h3>
<div class="alert alert-success" role="alert">
You have successfully changed your password! You may now proceed to log in.
</div>
<a href="% url 'login' %" class="btn btn-secondary btn-block">Return to log in</a>
</div>
</div>
</div>
</div>
% endblock %
模板\password_reset_confirm.html
% extends 'base_accounts.html' %
% block title %
% if validlink %
Change password for form.user.username
% else %
Reset your password
% endif %
% endblock %
% block content %
<div class="row justify-content-center">
<div class="col-lg-6 col-md-8 col-sm-10">
<div class="card">
<div class="card-body">
% if validlink %
<h3 class="card-title">Change password for @ form.user.username </h3>
<form method="post" novalidate>
% csrf_token %
% include 'includes/form.html' %
<button type="submit" class="btn btn-success btn-block">Change password</button>
</form>
% else %
<h3 class="card-title">Reset your password</h3>
<div class="alert alert-danger" role="alert">
It looks like you clicked on an invalid password reset link. Please try again.
</div>
<a href="% url 'password_reset' %" class="btn btn-secondary btn-block">Request a new password reset link</a>
% endif %
</div>
</div>
</div>
</div>
% endblock %
模板\password_reset_done.html
% extends 'base_accounts.html' %
% block title %Reset your password% endblock %
% block content %
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6 col-sm-8">
<div class="card">
<div class="card-body">
<h3 class="card-title">Reset your password</h3>
<p>Check your email for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.</p>
<a href="% url 'login' %" class="btn btn-secondary btn-block">Return to log in</a>
</div>
</div>
</div>
</div>
% endblock %
模板\login.html
% extends 'base_accounts.html' %
% block title %Log in to Django Boards% endblock %
% block content %
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6 col-sm-8">
<div class="card">
<div class="card-body">
<h3 class="card-title">Log in</h3>
<form method="post" novalidate>
% csrf_token %
% include 'includes/form.html' %
<button type="submit" class="btn btn-primary btn-block">Log in</button>
</form>
</div>
<div class="card-footer text-muted text-center">
New to Django Boards? <a href="% url 'signup' %">Sign up</a>
</div>
</div>
<div class="text-center py-2">
<small>
<a href="#" class="text-muted">Forgot your password?</a>
</small>
</div>
</div>
</div>
% endblock %
【问题讨论】:
【参考方案1】:我的项目\urls.py
更改此行url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]1,13-[0-9A-Za-z]1,20)/$'
致url(r'^reset/<uidb64>/<token>/'
如果仍然卡住,请添加$
url(r'^reset/<uidb64>/<token>/$'
【讨论】:
以上是关于Django 3.0.8 测试用例失败的主要内容,如果未能解决你的问题,请参考以下文章
Python接口测试实战4(下) - 框架完善:用例基类,用例标签,重新运行上次失败用例