Django加密应用程序第3部分:总结和测试
#网络开发人员 #python #django #crypto

Buy Me A Coffee

介绍

欢迎进入三部分技术教程系列的最后一部分,该系列正在建立一个Django项目,该项目使用户能够管理其加密货币投资组合。为了实现功能和其他功能,我们将利用Coingecko或任何其他加密货币API的API调用。

在第一部分中,我们设置了项目并创建了模型。在第二部分中,从那时开始,并添加了所有模板,视图和URL,并编码整个加密项目。因此,如果您还没有阅读以前的部分,我强烈建议您在继续此部分之前这样做。在这里,我们将使用PYTest编写模型,视图和模板的测试用例,专门讨论测试方法。我们还将编写大量测试,以确保我们的代码按预期运行并生成覆盖范围报告,以查看我们的代码中有多少所涵盖的测试。

可以在此处找到整个项目的代码 - > Github Repo for Django Crypto App

测试是软件开发的重要组成部分。它有助于我们确保我们的代码能够按预期工作,还可以帮助我们尽早捕捉错误。它还有助于我们确保代码可维护和可读。所以,让我们开始吧。

测试驱动的开发方法解释了

在Django中,可以在三个级别进行自动测试:URL测试,模型测试和查看测试。

URL测试

Django中的

URL测试用于检查URL模式中指定的端点是否正确配置并指向适当的视图。这很重要,因为如果端点不正确或指向错误的视图,则应用程序将无法处理请求,从而导致错误或不正确的行为。通常使用Django客户端类执行URL测试,该类模拟Web客户端并允许开发人员将请求发送到应用程序。

模型测试

Django中的模型测试用于检查数据库中存储的数据是否匹配预期的模式和模型中指定的约束。它有助于确保应用程序可以正确存储和检索数据,并且数据库正在按预期工作。通常使用DJANGO TESTCASE类及其各种断言方法执行模型测试,这些方法允许开发人员创建测试数据,将其保存到数据库中,并验证数据已正确保存。

>

查看测试

django中的查看测试用于检查views.py文件中定义的业务逻辑是否正常运行。它用于测试视图是否按预期处理请求并返回适当的响应。查看测试通常使用Django testcase类及其各种断言方法进行,该方法允许开发人员模拟请求并验证响应。

对于此加密货币应用程序,我使用了所有三种类型的自动测试。

对于URL测试,我创建了一系列使用Django客户端类的测试用例,将请求模拟到应用程序中的各个端点。每个测试用例向端点发送了一个请求,并验证了响应是预期的模板。

对于模型测试,我创建了一系列使用Django TestCase类创建测试数据的测试用例,将其保存到数据库中,并验证它已正确保存。每个测试案例都测试了一个特定模型或模型之间的关系,以确保数据被正确存储和检索。

用于查看测试,我创建了一系列测试用例,这些测试用例使用Django TestCase类模拟了应用程序中各种视图的请求。每个测试用例向视图发送了一个请求,并验证了响应是预期的结果。

写作测试

转到mainapp文件夹中的tests.py文件,并添加以下导入,因为我们将在测试中使用它们。

from decimal import Decimal
from unittest.mock import patch

from django.contrib.auth import authenticate, login
from django.contrib.auth import views as auth_views
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.test import Client, TestCase
from django.urls import resolve, reverse
from test_plus import TestCase

from .forms import CustomUserCreationForm
from .models import *
from .views import *

现在我们已经了解了理论,动机和方法,让我们开始编写测试。

测试URL

这是用于测试Web应用程序URL的测试类。测试类从Django的TestCase类继承,并且具有应用程序中每个URL的测试方法。

类具有setUp()方法,该方法可创建用于测试的测试用户。

测试方法是根据以下约定命名的:

  • test[PageName]测试了客户端可以访问带有给定名称的页面。
  • test[PageName]Url测试给定页面的URL解析为正确的视图功能。
  • test[PageName]Template测试了给定页面的正确模板。
  • test[PageName]ContainsCorrectHtml测试了正确的HTML内容显示在给定页面上。
  • test[PageName]FormCorrect测试了给定页面的正确表格。
  • test[PageName]RedirectsTo[OtherPageName]测试给定页面将重定向到正确的另一页。
  • test[PageName]RedirectIfAlreadyLoggedIn测试给定页面是否已登录,将其重定向到正确的页面。

测试方法使用Django的测试客户端向应用程序的URL提出请求并检查响应。他们使用Django的reverse()函数来生成应用程序视图的URL,并使用Django的resolve()函数将URL匹配以查看函数。他们还使用django的assertTemplateUsed()函数来检查是否使用了正确的模板,并使用django的assertContains()assertNotContains()函数检查响应中的特定HTML内容是否存在或不存在。

class UrlTest(TestCase):

    # CREATE THE TEST USER
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='testuser@example.com',
            password='12345'
        )


    """ 
    ----------------
    HOME PAGE TESTS
    -----------------
    """
    def testHomePage(self):
        response = self.client.get('/')
        self.assertEqual(response.status_code, 200)

    def testHomePageUrl(self):
        url = reverse('home')
        self.assertEqual(resolve(url).func, home_view)  

    def testHomePageTemplate(self):
        response = self.client.get('/')
        self.assertTemplateUsed(response, 'home.html')

    def testHomePageContainsCorrectHtml(self):
        response = self.client.get('/')
        self.assertContains(response, 'Top 10 CryptoCurrency Rankings')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')


    """ 
    ----------------
    LOGIN PAGE TESTS
    -----------------
    """
    def testLoginPage(self):
        response = self.client.get('/login/')
        self.assertEqual(response.status_code, 200)

    def testLoginPageUrl(self):
        url = reverse('login')
        self.assertEqual(resolve(url).func, login_view)

    def testLoginPageTemplate(self):
        response = self.client.get('/login/')
        self.assertTemplateUsed(response, 'login.html')

    def testLoginPageContainsCorrectHtml(self):
        response = self.client.get('/login/')
        self.assertContains(response, 'Login')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')

    def testLoginPageRedirectsToPortfolioPage(self):
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/login/')
        self.assertRedirects(response, '/portfolio/')

    def testLoginPageRedirectIfAlreadyLoggedIn(self):
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/login/')
        self.assertRedirects(response, '/portfolio/')

    def testLoginPageFormCorrect(self):
        response = self.client.get('/login/')
        form = response.context.get('form')
        self.assertIsInstance(form, AuthenticationForm)
        self.assertContains(response, 'csrfmiddlewaretoken')

    """ 
    ----------------
    SIGNUP PAGE TESTS
    -----------------
    """
    def testSignupPage(self):
        response = self.client.get('/signup/')
        self.assertEqual(response.status_code, 200)


    def testSignupPageUrl(self):
        url = reverse('signup')
        self.assertEqual(resolve(url).func, signup_view)

    def testSignupPageTemplate(self):
        response = self.client.get('/signup/')
        self.assertTemplateUsed(response, 'signup.html')

    def testSignupPageContainsCorrectHtml(self):
        response = self.client.get('/signup/')
        self.assertContains(response, 'Signup')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')

    def testSignupPageRedirectIfAlreadyLoggedIn(self):
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/signup/')
        self.assertRedirects(response, '/portfolio/')

    def testSignupPageFormCorrect(self):
        response = self.client.get('/signup/')
        form = response.context.get('form')
        self.assertIsInstance(form, UserCreationForm)
        self.assertContains(response, 'csrfmiddlewaretoken')

    """ 
    ----------------
    PORTFOLIO PAGE TESTS
    -----------------
    """
    def testPortfolioPageNoLogin(self):    
        # check if logged out user can access portfolio page
        response = self.client.get('/portfolio/')
        self.assertEqual(response.status_code, 302)

    def testPortfolioPageLogin(self):
        # check if logged in user can access portfolio page
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/portfolio/')
        self.assertEqual(response.status_code, 200)

    def testPortfolioPageUrl(self):
        url = reverse('portfolio')
        self.assertEqual(resolve(url).func, portfolio_view)

    def testPortfolioPageTemplate(self):
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/portfolio/')
        self.assertTemplateUsed(response, 'portfolio.html')

    def testPortfolioPageContainsCorrectHtml(self):
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/portfolio/')
        self.assertContains(response, 'Wallet')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')


    """    
    ----------------
    RESET PAGE TESTS
    -----------------
    """

    def testResetPasswordPageUrl(self):
        url = reverse('password_reset')
        self.assertEqual(resolve(url).func.view_class, auth_views.PasswordResetView)

    def testResetPasswordPageTemplate(self):
        response = self.client.get('/password_reset/')
        self.assertTemplateUsed(response, 'reset/password_reset.html')

    def testResetPasswordPageContainsCorrectHtml(self):
        response = self.client.get('/password_reset/')
        self.assertContains(response, 'Reset Password')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')

    def testResetPasswordDonePageUrl(self):
        url = reverse('password_reset_done')
        self.assertEqual(resolve(url).func.view_class, auth_views.PasswordResetDoneView)

    def testResetPasswordDonePageTemplate(self):
        response = self.client.get('/password_reset_done/')
        self.assertTemplateUsed(response, 'reset/password_reset_done.html')

    def testResetPasswordDonePageContainsCorrectHtml(self):
        response = self.client.get('/password_reset_done/')
        self.assertContains(response, 'An email has been sent with instructions to reset your password')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')

    def testResetPasswordCompletePageUrl(self):
        url = reverse('password_reset_complete')
        self.assertEqual(resolve(url).func.view_class, auth_views.PasswordResetCompleteView)

    def testResetPasswordCompletePageTemplate(self):
        response = self.client.get('/password_reset_complete/')
        self.assertTemplateUsed(response, 'reset/password_reset_complete.html')

    def testResetPasswordCompletePageContainsCorrectHtml(self):
        response = self.client.get('/password_reset_complete/')
        self.assertContains(response, 'Your password has been set.')
        self.assertNotContains(response, 'Hi there! I should not be on the page.')

    """    
    ----------------
    SEARCH PAGE TESTS
    -----------------
    """

    def testSearchPageNoLogin(self):
        response = self.client.get('/search/')
        self.assertEqual(response.status_code, 302)

    def testSearchPageUrl(self):
        url = reverse('search')
        self.assertEqual(resolve(url).func, search_view)

    # check if search page can only be accessed by POST request
    def testSearchPagePostOnly(self):
        self.client.login(username=self.user.username, password='12345')
        response = self.client.get('/search/')
        self.assertEqual(response.status_code, 405)

        response = self.client.post('/search/')
        self.assertEqual(response.status_code, 200)

测试模型

这些测试用例是使用Python Unittest框架编写的,旨在测试Django Web应用程序中不同模型的功能。

setup()方法是在每种测试方法之前运行的一种特殊方法。在此方法中,创建了两个用户对象 - 一个用作测试用户,另一个用作转介用户。

使用DEF关键字定义了单个测试方法,然后是测试方法的名称。每种测试方法都测试特定模型的特定方面,例如用户,加密货币,推荐,投资组合或配置文件模型。

在每种测试方法中,采取特定的操作来测试模型的某些方面,例如创建模型实例,设置其属性并检查返回期望值是否返回。例如,TestuserModeLcorrectData()方法检查了用户模型的属性(例如用户名和电子邮件)是否正确设置了。

主张语句用于检查预期值是否等于实际值。如果测试失败,则会增加错误,并且测试方法停止运行。

总体而言,这些测试用例旨在确保Django Web应用程序中的每个模型都可以正常运行并按预期行为。

class ModelTest(TestCase):
    # CREATE THE TEST USER
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser2',
            email='testuser2@example.com',
            password='12345'
        )

        self.referral = User.objects.create_user(
            username='referraluser',
            email='referreduser@example.com',
            password='12345'
        )

    """
    ----------------
    USER MODEL TESTS
    -----------------
    """

    def testUserModelCorrectData(self):
        test_user = self.user
        self.assertEqual(test_user.username, 'testuser2')
        self.assertEqual(test_user.email, 'testuser2@example.com')
        self.assertTrue(isinstance(test_user, User))
        self.assertTrue(test_user.is_active)
        self.assertFalse(test_user.is_staff)

    def testErrorOnDuplicateUsername(self):
        with self.assertRaises(IntegrityError):
            User.objects.create_user(
                username='testuser2',
                email='testuser2@example.com',
                password='12345'
        )

    def testErrorOnDuplicateEmail(self):
        with self.assertRaises(IntegrityError):
            User.objects.create_user(
                username='testuser3',
                email='testuser2@example.com',
                password='12345'
            )

    """ 
    ----------------
    CRYPTOCURRENCY MODEL TESTS
    -----------------
    """

    def testCryptocurrencyModel(self):
        cryptocurrency = Cryptocurrency.objects.create(
            user = User.objects.create(username='testuser'),
            id_from_api = 'bitcoin',
            name = 'Bitcoin', 
            symbol = 'BTC',
            current_price = 10000,
            quantity = 1
        )
        self.assertEqual(cryptocurrency.name, 'Bitcoin')
        self.assertEqual(cryptocurrency.symbol, 'BTC')
        self.assertEqual(cryptocurrency.current_price, 10000)
        self.assertEqual(cryptocurrency.quantity, 1)
        self.assertEqual(cryptocurrency.user.username, 'testuser')
        self.assertTrue(isinstance(cryptocurrency, Cryptocurrency))

    def testCryptocurrencyModelErrorOnDuplicate(self):
        Cryptocurrency.objects.create(
            user = User.objects.create(username='testuser'),
            id_from_api = 'bitcoin',
            name = 'Bitcoin', 
            symbol = 'BTC',
            current_price = 10000,
            quantity = 1
        )
        with self.assertRaises(IntegrityError):
            Cryptocurrency.objects.create(
                user = User.objects.create(username='testuser'),
                id_from_api = 'bitcoin',
                name = 'Bitcoin', 
                symbol = 'BTC',
                current_price = 10000,
                quantity = 1
            )
            self.fail('Cryptocurrency model should not allow duplicate cryptocurrencies')

    def testCryptocurrencyCurrentPrice(self):
        crypto = Cryptocurrency.objects.create(
            user=self.user,
            id_from_api='bitcoin',
            name='Bitcoin', 
            symbol='BTC',
            current_price=10000,
            quantity=1
        )
        self.assertEqual(crypto.current_price, 10000)

    def testCryptocurrencyStr(self):
        crypto = Cryptocurrency.objects.create(
            user=self.user,
            id_from_api='bitcoin',
            name='Bitcoin', 
            symbol='BTC',
            current_price=10000,
            quantity=1
        )
        self.assertEqual(str(crypto), 'Bitcoin (BTC)')

    def testCryptocurrencyQuantityDefaultValue(self):
        crypto = Cryptocurrency.objects.create(
            user=self.user,
            id_from_api='bitcoin',
            name='Bitcoin', 
            symbol='BTC',
            current_price=10000
        )
        self.assertEqual(crypto.quantity, 1)


    """
    ----------------
    REFERRAL MODEL TESTS
    -----------------
    """

    # make a referral and check if it is created correctly
    def testReferralModel(self):
        referral = Referal.objects.create(
            user=self.user,
            referrer=self.referral
        )
        self.assertEqual(referral.user.username, 'testuser2')
        self.assertEqual(referral.referrer.username, 'referraluser')
        self.assertTrue(isinstance(referral, Referal))



    """ 
    ----------------
    PORTFOLIO MODEL TESTS
    -----------------
    """

    def testPortfolioModel(self):
        portfolio = Portfolio.objects.create(
            user=self.user,
            total_value=10000
        )
        self.assertEqual(portfolio.user.username, 'testuser2')
        self.assertEqual(portfolio.total_value, 10000)
        self.assertTrue(isinstance(portfolio, Portfolio))


    """ 
    ----------------
    PROFILE MODEL TESTS
    -----------------
    """
    def testProfileModel(self):
        # referral code should be generated automatically using small uuid
        import shortuuid
        shortuuid.ShortUUID().random(length=10)

        referral_code = shortuuid.uuid()

        # make a dummy user
        dummy_user = User.objects.create_user(
            username='dummyprofile',
            email = 'dummyemail@example.com',
            password='12345'
        )


        profile = Profile.objects.create(
            user=dummy_user,
            referral_code=referral_code
        )

        self.assertEqual(profile.user.username, 'dummyprofile')
        self.assertEqual(profile.referral_code, referral_code)
        self.assertTrue(isinstance(profile, Profile))

测试视图


""" 
MAKING SEPERATE TEST CLASSES FOR VIEWS
"""

class LoginViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(username='testuser', email="example@example.com", password='testpass')

    def test_login_view_with_valid_credentials(self):
        url = reverse('login')
        response = self.client.post(url, {'username': 'testuser', 'password': 'testpass'})
        self.assertRedirects(response, reverse('portfolio'))

    def test_login_view_with_invalid_credentials(self):
        url = reverse('login')
        response = self.client.post(url, {'username': 'invaliduser', 'password': 'invalidpass'})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Invalid username or password.")


class TestAddToPortfolioView(TestCase):

    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(
            username='testuser', password='testpassword')
        self.client.login(username='testuser', password='testpassword')

    def test_add_to_portfolio_view_success(self):
        with patch('mainapp.views.requests.get') as mock_get:
            mock_data = {
                'name': 'Bitcoin',
                'id': 'bitcoin',
                'symbol': 'BTC',
                'market_data': {
                    'current_price': {
                        'usd': 50000.00
                    }
                }
            }
            mock_get.return_value.json.return_value = mock_data

            data = {
                'id': 'bitcoin',
                'quantity': 10
            }
            response = self.client.post(
                reverse('add_to_portfolio'), data=data)

        self.assertEqual(response.status_code, 302)
        self.assertRedirects(response, reverse('portfolio'))

        crypto_currency = Cryptocurrency.objects.get(
            user=self.user, id_from_api='bitcoin')
        self.assertEqual(crypto_currency.name, 'Bitcoin')
        self.assertEqual(crypto_currency.symbol, 'BTC')
        self.assertEqual(crypto_currency.quantity, 10)
        self.assertEqual(crypto_currency.current_price, 50000.00)

        portfolio = Portfolio.objects.get(user=self.user)
        self.assertEqual(portfolio.total_value, 500000.00)

    def test_add_to_portfolio_view_get_request(self):
        response = self.client.get(reverse('add_to_portfolio'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Need a crypto currency to add to your portfolio. Go back to the home page and search for a crypto currency.')

from django.contrib.auth.models import User
from django.test import Client, TestCase


class SearchViewTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(
            username='testuser', password='testpassword'
        )
        self.client.login(username='testuser', password='testpassword')

    def test_valid_search(self):
        response = self.client.post('/search/', {'search_query': 'bitcoin'})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'BTC')

    def test_invalid_search(self):
        response = self.client.post('/search/', {'search_query': 'invalid_crypto_currency'})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'No crypto currency found based on your search query.')


class DeleteFromPortfolioViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(username='testuser', password='testpass')
        self.crypto_currency = Cryptocurrency.objects.create(user=self.user, id_from_api='bitcoin', name='Bitcoin', symbol='BTC', current_price=Decimal('10000'), quantity=Decimal('2'))
        self.portfolio = Portfolio.objects.create(user=self.user, total_value=Decimal('20000'))

    def test_delete_from_portfolio_view(self):
        self.client.login(username='testuser', password='testpass')
        url = reverse('delete_from_portfolio', args=[self.crypto_currency.pk])
        response = self.client.post(url)
        self.assertRedirects(response, reverse('portfolio'))
        self.assertFalse(Cryptocurrency.objects.filter(pk=self.crypto_currency.pk).exists())
        self.portfolio.refresh_from_db()
        self.assertEqual(self.portfolio.total_value, Decimal('0'))

    def test_delete_from_portfolio_view_with_unauthenticated_user(self):
        url = reverse('delete_from_portfolio', args=[self.crypto_currency.pk])
        response = self.client.post(url)
        self.assertRedirects(response, reverse('login') + '?next=' + url)
        self.assertTrue(Cryptocurrency.objects.filter(pk=self.crypto_currency.pk).exists())
        self.portfolio.refresh_from_db()
        self.assertEqual(self.portfolio.total_value, Decimal('20000'))



class TestHomeView(TestCase):
    def setUp(self):
        self.client = Client()
        self.top_10_crypto_url_global = 'https://api.coingecko.com/api/v3/coins/markets?vs_currency=USD&order=market_cap_desc&per_page=10&page=1&sparkline=true'
        self.top_10_crypto_data_global = requests.get(self.top_10_crypto_url_global).json()
        self.user = User.objects.create_user(username='testuser', password='testpass')
        self.crypto = Cryptocurrency.objects.create(name='Bitcoin', symbol='BTC', id_from_api='bitcoin', user=self.user, current_price=Decimal('10000'), quantity=Decimal('2'))
        self.portfolio = Portfolio.objects.create(user=self.user, total_value=Decimal('20000'))
        self.url = reverse('home')

    def test_home_view_authenticated(self):
        self.client.login(username='testuser', password='testpass')
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'home.html')
        self.assertTrue('top_10_crypto_data_global' in response.context)
        self.assertTrue('user_cryptocurrencies' in response.context)
        self.assertTrue('user_portfolio' in response.context)
        self.assertTrue('crypto_price_changes' in response.context)

    def test_home_view_unauthenticated(self):
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'home.html')
        self.assertTrue('top_10_crypto_data_global' in response.context)
        self.assertFalse('user_cryptocurrencies' in response.context)
        self.assertFalse('user_portfolio' in response.context)
        self.assertFalse('crypto_price_changes' in response.context)

class SignupWithReferrerViewTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.referrer = User.objects.create_user(
            username='test_referrer', email='test_referrer@example.com', password='testpassword')
        self.referrer_profile = Profile.objects.get(user=self.referrer)
        self.referral_code = self.referrer_profile.referral_code

    def test_signup_with_referrer_view_GET(self):
        response = self.client.get(reverse('signup_with_referrer_view', args=[self.referral_code]))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'signup.html')

    def test_signup_with_referrer_view_POST(self):
        data = {
            'username': 'testuser',
            'email': 'testuser@example.com',
            'password1': 'testpassword',
            'password2': 'testpassword'
        }
        response = self.client.post(reverse('signup_with_referrer_view', args=[self.referral_code]), data)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(User.objects.count(), 2)
        # get the user object from the database

        self.assertEqual(Referal.objects.get(user__username='testuser').referrer, self.referrer)
        self.assertEqual(Profile.objects.get(user__username='test_referrer').bonus, 100)
        self.assertRedirects(response, reverse('login'))

class SignupViewTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.signup_url = reverse('signup')
        self.valid_data = {
            'username': 'johndoe',
            'email': 'johndoe@example.com',
            'password1': 'passw0rd',
            'password2': 'passw0rd'
        }

    def test_signup_view_get(self):
        response = self.client.get(self.signup_url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'signup.html')
        self.assertIsInstance(response.context['form'], CustomUserCreationForm)

    def test_signup_view_post_invalid_data(self):
        invalid_data = self.valid_data.copy()
        invalid_data['password2'] = 'different_password'
        response = self.client.post(self.signup_url, invalid_data)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'signup.html')
        self.assertIsInstance(response.context['form'], CustomUserCreationForm)
        self.assertEqual(User.objects.count(), 0)

class LogoutViewTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(
            username='testuser', password='testpass'
        )
        self.client.login(username='testuser', password='testpass')

    def test_logout_view(self):
        # make sure user is logged in
        response = self.client.get(reverse('home'))
        self.assertEqual(response.status_code, 200)
        # this is only visible to logged in users below the Top 10 Cryptocurrencies table
        self.assertTrue('24 Hour Price Changes' in str(response.content))

        # logout
        response = self.client.get(reverse('logout'))
        self.assertEqual(response.status_code, 302)

        # check if user is logged out
        response = self.client.get(reverse('home'))
        self.assertEqual(response.status_code, 200)
        self.assertFalse('testuser' in str(response.content))

class LoginViewTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.login_url = reverse('login')
        self.user = User.objects.create_user(
            username='testuser',
            email='testuser@test.com',
            password='testpass'
        )

    def test_login_view_get(self):
        response = self.client.get(self.login_url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'login.html')

    def test_login_view_post_valid_credentials(self):
        response = self.client.post(self.login_url, {
            'username': 'testuser',
            'password': 'testpass',
        })
        self.assertRedirects(response, reverse('portfolio'))
        user = authenticate(username='testuser', password='testpass')
        self.assertIsNotNone(user)
        self.assertEqual(user, self.user)
        self.assertTrue(user.is_authenticated)

    def test_login_view_post_invalid_credentials(self):
        response = self.client.post(self.login_url, {
            'username': 'wronguser',
            'password': 'wrongpass',
        })
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'login.html')
        self.assertContains(response, "Invalid username or password.")

    def tearDown(self):
        self.user.delete()

这些是Django Web应用程序的单元测试,测试了应用程序中的各种视图。这是对要测试的每种观点和测试目的的详细说明:

  1. 咀嚼16:
    • test_login_view_with_valid_credentials:此测试检查用户是否可以成功登录具有有效凭据的应用程序。它通过有效的用户名和密码将POST请求发送到登录URL,并希望将其重定向到投资组合页面(假设成功登录)。
    • test_login_view_with_invalid_credentials:此测试检查用户是否无法使用无效的凭据登录。它通过无效的用户名和密码将POST请求发送到登录URL,并希望收到200个状态代码(假设登录失败),并在登录页面上查看错误消息。
  2. TestAddToPortfolioView
    • test_add_to_portfolio_view_success:此测试检查用户是否可以成功地将加密货币添加到其投资组合中。它将POST请求发送到具有有效的加密货币ID和数量的添加到输入Folio URL,并期望将其重定向到Portfolio页面(假设添加成功)。它还检查了添加的加密货币及其详细信息(名称,符号,数量和当前价格)是否已保存在数据库中,以及用户的投资组合值是否已相应更新。
    • test_add_to_portfolio_view_get_request:此测试检查用户是否无法通过GET请求向其投资组合添加加密货币。它将GET请求发送到添加到端口文件URL,并期望在页面上收到200个状态代码和错误消息。
  3. SearchViewTestCase
    • test_valid_search:此测试检查用户是否可以通过使用有效的搜索查询向搜索URL发送POST请求来成功搜索加密货币。它希望收到200个状态代码,并在搜索结果页面上查看加密货币的符号。
    • test_invalid_search:此测试检查在搜索无效加密货币时,用户是否会收到错误消息。它通过无效的搜索查询将POST请求发送到搜索URL,并期望在搜索结果页面上接收200个状态代码和错误消息。
  4. DeleteFromPortfolioViewTest
    • 此测试案例正在测试称为DeleteFromPortfolioView的视图。预计该视图将接收带有加密货币对象的主要键的POST请求。该视图应从数据库中删除该加密货币对象,并相应地更新用户的投资组合值。
    • setUp方法创建一个测试客户端,测试用户,测试加密货币对象和数据库中的测试组合对象。
    • test_delete_from_portfolio_view方法测试用户身份验证时的视图。它使用测试客户端在用户中登录用户,并使用测试加密货币对象的主要键向视图提出帖子请求,并断言响应将重定向到投资组合页面。然后,它检查了数据库中的测试加密货币对象是否不再存在,并且投资组合的总值为0。
    • 当用户未经身份验证时,test_delete_from_portfolio_view_with_unauthenticated_user方法测试视图。它通过测试加密货币对象的主要键向视图提出了邮政请求,并断言响应重定向到登录页面。然后,它检查数据库中的测试加密货币对象是否仍然存在,并且投资组合的总值仍然为20000。
  5. TestHomeView
    • 此测试案例正在测试称为HomeView的视图。该视图有望渲染一个称为home.html的模板,并显示前10个加密货币,用户的加密货币,用户的投资组合以及加密货币价格的百分比变化的列表。
    • setUp方法创建了一个测试客户端,测试用户,测试加密货币对象和数据库中的测试组合对象。它还向外部API提出请求以获取前10个加密货币。
    • test_home_view_authenticated方法在对用户进行身份验证时测试视图。它使用测试客户端在用户中登录用户,向视图提出获取请求,并断言响应状态代码为200,并且使用了正确的模板。它还检查响应上下文是否包含前10个加密货币,用户的加密货币,用户的投资组合以及加密货币价格的百分比变化。
    • test_home_view_unauthenticated方法在未认证用户时测试视图。它提出了对视图的请求,并断言响应状态代码为200,并且使用了正确的模板。它还检查响应上下文是否包含前10个加密货币,但不包含用户的加密货币,用户的投资组合或加密货币价格的百分比变化。
  6. SignupWithReferrerViewTestCase
    • 此测试案例检查用于使用推荐代码注册的视图的行为。在setUp()方法中,创建了测试用户(转介程序),并从其配置文件中获得转介代码。
    • test_signup_with_referrer_view_GET()方法使用推荐代码将get请求发送到视图,并检查响应状态代码为200,而使用'signup.html'模板。
    • test_signup_with_referrer_view_POST()方法发送了一个带有有效用户数据和转介代码的发布请求。它检查响应状态代码是302,表明成功的重定向,用户对象计数为2,并且测试用户的转介器是setUp()中创建的转介人。此外,它检查了推荐人的奖励现在是100,并且响应重定向到“ login”视图。
  7. SignupViewTestCase
    • 此测试案例检查用于普通用户注册的视图的行为。 setUp()方法初始化了客户端对象,视图的URL和有效的用户数据。
    • test_signup_view_get()方法将get请求发送到视图,并检查响应状态代码为200,使用'signup.html'模板,上下文表格为CustomUserCreationForm
    • test_signup_view_post_invalid_data()方法发送了一个带有无效用户数据的发布请求,它检查了响应状态代码是否为200,使用了“ Insighup.html”模板,并且使用了type customusercreationform的表单。此外,它检查是否没有创建用户对象。
  8. LogoutViewTestCase
    • 此测试案例检查用于注销的视图的行为。 setUp()方法初始化客户端对象,创建测试用户并将其记录在。
    • 中。
    • test_logout_view()方法将get请求发送到主页,并检查响应状态代码为200,并且存在'24小时价格变化'元素。
    • 然后,它将get请求发送到注销视图,并检查响应状态代码是302。最后,它将另一个get请求发送到主页,并检查响应状态代码是200,并且测试用户的名称为不存在。
  9. LoginViewTestCase
    • 此测试案例检查用于用户登录的视图的行为。 setUp()方法初始化了客户端对象,视图的URL,并创建了测试用户。 test_login_view_get()方法将get请求发送到视图,并检查响应状态代码为200,并且使用了'login.html'模板。
    • test_login_view_post_valid_credentials()方法发送带有有效用户凭据的发布请求,并检查响应是否成功地重定向到投资组合视图,用户对象已进行身份验证,并且用户对象与setUp中创建的对象相同( )。
    • test_login_view_post_invalid_credentials()方法发送了一个带有无效用户凭据的发布请求,并检查响应状态代码为200,使用“ login.html”模板,并显示了错误消息。最后,拆卸方法删除测试用户对象。

如何运行测试

所有测试都写在mainapp文件夹中的tests.py文件中。

# to run all tests
python manage.py test

# to run tests for a specific class
python manage.py test -k <class-name>

# start coverage
coverage run --source='.' --omit=mainapp\tests.py manage.py test mainapp

# generate coverage report
coverage html

# check the htmlcov folder and open the index.html file

Image description

Image description

Image description

coverage

结论

这是该系列的第三部分和最后一部分。在这里,我们学会了如何为我们的Django项目以及TDD方法编写测试。我们看到了如何为模型,视图和反向URL编写测试,并为我们的项目撰写了60多个测试用例,以实现90%+测试覆盖范围。

这个项目对我来说是一次很棒的学习经历。我学到了很多关于Django的知识,也学会了如何使用Coingecko API。我还学会了如何使用unittest模块为我的Django项目编写测试。希望您喜欢阅读这篇文章。如果您有任何疑问或建议,请随时在下面发表评论。谢谢您的阅读! ð