乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Django 教程 10: 測(cè)試 Django 網(wǎng)頁應(yīng)用

       寧靜致遠(yuǎn)oj1kn5 2019-06-13

      隨著網(wǎng)站的增長(zhǎng),他們?cè)絹碓诫y以手動(dòng)測(cè)試。不僅要進(jìn)行更多的測(cè)試,而且隨著組件之間的交互變得越來越復(fù)雜,一個(gè)區(qū)域的小改變可能會(huì)影響到其他區(qū)域,所以需要做更多的改變來確保一切正常運(yùn)行,并且在進(jìn)行更多更改時(shí)不會(huì)引入錯(cuò)誤。減輕這些問題的一種方法是編寫自動(dòng)化測(cè)試,每當(dāng)您進(jìn)行更改時(shí),都可以輕松可靠地運(yùn)行測(cè)試。本教程演示如何使用Django的測(cè)試框架自動(dòng)化您的網(wǎng)站的單元測(cè)試。

      先決條件:完成之前的所有教程主題,包括 Django教程 9:使用表單。
      目標(biāo):了解如何為基于 Django 的網(wǎng)站編寫單元測(cè)試。

      概覽節(jié)

      LocalLibrary 目前有頁面顯示所有書本和作者的列表,書本和作者項(xiàng)目的詳細(xì)視圖,續(xù)借BookInstances的頁面,以及創(chuàng)建,更新和刪除作者項(xiàng)目的頁面(如果您完成了Django 教程 9:使用表單中的自我挑戰(zhàn),也可以創(chuàng)建,更新和刪除書本記錄)。即使使用這個(gè)相對(duì)較小的站點(diǎn),手動(dòng)導(dǎo)航到每個(gè)頁面,并且表面地檢查一切是否按預(yù)期工作,可能需要幾分鐘。當(dāng)我們進(jìn)行更改,并擴(kuò)展網(wǎng)站時(shí),手動(dòng)檢查所有內(nèi)容 “正常” 工作所需的時(shí)間只會(huì)增長(zhǎng)。如果我們繼續(xù)這樣做,最終我們將花費(fèi)大部分時(shí)間進(jìn)行測(cè)試,并且很少有時(shí)間來改進(jìn)我們的代碼。

      自動(dòng)化測(cè)試可以真正幫助解決這個(gè)問題!顯而易見的好處,是它們可以比手動(dòng)測(cè)試運(yùn)行得更快,可以測(cè)試更底層級(jí)別的細(xì)節(jié),并且每次都測(cè)試完全相同的功能(人類測(cè)試員遠(yuǎn)遠(yuǎn)沒有這么可靠!)因?yàn)樗鼈兒芸焖?,自?dòng)化的測(cè)試可以更頻繁地執(zhí)行,如果測(cè)試失敗,他們會(huì)指出代碼未按預(yù)期執(zhí)行的位置。

      此外,自動(dòng)化測(cè)試可以充當(dāng)代碼的第一個(gè)真實(shí)“用戶”,迫使您嚴(yán)格定義和記錄網(wǎng)站的行為方式。它們通常是您的代碼示例,和文檔的基礎(chǔ)。由于這些原因,一些軟件開發(fā)過程,從測(cè)試定義和實(shí)現(xiàn)開始,之后編寫代碼以匹配所需的行為(例如,測(cè)試驅(qū)動(dòng)test-driven 和行為驅(qū)動(dòng) behaviour-driven的開發(fā))。

      本教程通過向 LocalLibrary 網(wǎng)站添加大量測(cè)試,來演示如何為 Django 編寫自動(dòng)化測(cè)試。

      測(cè)試的類型節(jié)

      測(cè)試和測(cè)試方法有許多類型,級(jí)別和分類。最重要的自動(dòng)化測(cè)試是:

        單元測(cè)試Unit tests
      • 驗(yàn)證各個(gè)組件的功能行為,通常是類別和功能級(jí)別。

      • 回歸測(cè)試
      • 測(cè)試重現(xiàn)歷史錯(cuò)誤。最初運(yùn)行每個(gè)測(cè)試,以驗(yàn)證錯(cuò)誤是否已修復(fù),然后重新運(yùn)行,以確保在以后更改代碼之后,未重新引入該錯(cuò)誤。

      • 集成測(cè)試
      • 驗(yàn)證組件分組在一起使用時(shí)的工作方式。集成測(cè)試了解組件之間所需的交互,但不一定了解每個(gè)組件的內(nèi)部操作。它們可能涵蓋整個(gè)網(wǎng)站的簡(jiǎn)單組件分組。

      注意: 其他常見類型的測(cè)試,包括黑盒,白盒,手動(dòng),自動(dòng),金絲雀,煙霧,一致性,驗(yàn)收,功能,系統(tǒng),性能,負(fù)載和壓力測(cè)試。查找它們以獲取更多信息。

      Django為測(cè)試提供了什么?節(jié)

      測(cè)試網(wǎng)站是一項(xiàng)復(fù)雜的任務(wù),因?yàn)樗啥鄬舆壿嫿M成 - 從 HTTP 級(jí)請(qǐng)求處理,查詢模型,到表單驗(yàn)證和處理,以及模板呈現(xiàn)。

      Django 提供了一個(gè)測(cè)試框架,其中包含基于 Python 標(biāo)準(zhǔn)unittest庫的小型層次結(jié)構(gòu)。盡管名稱如此,但該測(cè)試框架適用于單元測(cè)試和集成測(cè)試。 Django 框架添加了 API 方法和工具,以幫助測(cè)試 Web 和 Django 特定的行為。這允許您模擬請(qǐng)求,插入測(cè)試數(shù)據(jù)以及檢查應(yīng)用程序的輸出。 Django 還提供了一個(gè)API(LiveServerTestCase)和使用不同測(cè)試框架的工具,例如,您可以與流行的 Selenium 框架集成,以模擬用戶與實(shí)時(shí)瀏覽器交互。

      要編寫測(cè)試,您可以從任何 Django(或unittest)測(cè)試基類(SimpleTestCaseTransactionTestCaseTestCaseLiveServerTestCase)派生,然后編寫單獨(dú)的方法,來檢查特定功能,是否按預(yù)期工作(測(cè)試使用 “assert” 方法來測(cè)試表達(dá)式導(dǎo)致 TrueFalse值,或者兩個(gè)值相等,等等。)當(dāng)您開始測(cè)試運(yùn)行時(shí),框架將在派生類中執(zhí)行所選的測(cè)試方法。測(cè)試方法獨(dú)立運(yùn)行,具有在類中定義的常見設(shè)置和/或拆卸行為,如下所示。

      class YourTestClass(TestCase):
      
          def setUp(self):
              #Setup run before every test method.
              pass
      
          def tearDown(self):
              #Clean up run after every test method.
              pass
      
          def test_something_that_will_pass(self):
              self.assertFalse(False)
      
          def test_something_that_will_fail(self):
              self.assertTrue(False)

      大多數(shù)測(cè)試的最佳基類是 django.test.TestCase。此測(cè)試類在運(yùn)行測(cè)試之前,創(chuàng)建一個(gè)干凈的數(shù)據(jù)庫,并在自己的事務(wù)中,運(yùn)行每個(gè)測(cè)試函數(shù)。該類還擁有一個(gè)測(cè)試客戶端,您可以使用該客戶端,模擬在視圖級(jí)別與代碼交互的用戶。在下面的部分中,我們將集中討論使用此TestCase 基類創(chuàng)建的單元測(cè)試。

      注意: django.test.TestCase 類非常方便,但可能會(huì)導(dǎo)致某些測(cè)試,比它們需要的速度慢(并非每個(gè)測(cè)試,都需要設(shè)置自己的數(shù)據(jù)庫,或模擬視圖交互)。一旦熟悉了這個(gè)類可以做什么,您可能希望用可以用更簡(jiǎn)單的測(cè)試類,替換一些測(cè)試。

      你應(yīng)該測(cè)試什么?節(jié)

      您應(yīng)該測(cè)試自己代碼的所有方面,但不要測(cè)試 Python 或 Django 的一部分提供的任何庫或功能。

      例如,考慮下面定義的 Author模型。您不需要顯式測(cè)試 first_namelast_name 是否已在數(shù)據(jù)庫中正確儲(chǔ)存為CharField,因?yàn)檫@是 Django 定義的內(nèi)容(當(dāng)然,在實(shí)踐中,您將不可避免地在開發(fā)期間測(cè)試此功能)。你也不需要測(cè)試date_of_birth是否已被驗(yàn)證為日期字段,因?yàn)檫@也是 Django 中實(shí)現(xiàn)的東西。

      但是,您應(yīng)該檢查用于標(biāo)簽的文本(名字,姓氏,出生日期,死亡),以及為文本分配的字段大小(100個(gè)字符),因?yàn)檫@些是您的設(shè)計(jì)的一部分,可能會(huì)在將來被打破/改變。

      class Author(models.Model):
          first_name = models.CharField(max_length=100)
          last_name = models.CharField(max_length=100)
          date_of_birth = models.DateField(null=True, blank=True)
          date_of_death = models.DateField('Died', null=True, blank=True)
          
          def get_absolute_url(self):
              return reverse('author-detail', args=[str(self.id)])
          
          def __str__(self):
              return '%s, %s' % (self.last_name, self.first_name)

      同樣,您應(yīng)該檢查自定義方法 get_absolute_url()__str__() 是否符合要求,因?yàn)樗鼈兪悄拇a/業(yè)務(wù)邏輯。在get_absolute_url()的情況下,您可以相信 Django reverse()方法已經(jīng)正確實(shí)現(xiàn),因此您正在測(cè)試的是實(shí)際上已經(jīng)定義了關(guān)聯(lián)的視圖。

      注意: 精明的讀者可能會(huì)注意到,我們也希望將出生和死亡的日期限制在合理的值,并檢查出生后是否死亡。在 Django中,此約束將添加到表單類中(盡管您可以為字段定義驗(yàn)證器,這些字段似乎僅在表單級(jí)別使用,而不是在模型級(jí)別使用)。

      考慮到這些,讓我們開始研究如何定義和運(yùn)行測(cè)試。

      測(cè)試結(jié)構(gòu)概述節(jié)

      在我們?cè)敿?xì)討論“測(cè)試內(nèi)容”之前,讓我們先簡(jiǎn)要介紹一下測(cè)試的定位和方式。

      Django 使用 unittest 模塊的內(nèi)置測(cè)試查找,它將在任何使用模式test*.py 命名的文件中,查找當(dāng)前工作目錄下的測(cè)試。如果您正確命名文件,則可以使用您喜歡的任何結(jié)構(gòu)。我們建議您為測(cè)試代碼創(chuàng)建一個(gè)模塊,并為模型,視圖,表單和您需要測(cè)試的任何其他類型的代碼,分別創(chuàng)建文件。例如:

      catalog/
        /tests/
          __init__.py
          test_models.py
          test_forms.py
          test_views.py

      在 LocalLibrary 項(xiàng)目中,創(chuàng)建如上所示的文件結(jié)構(gòu)。__init__.py 應(yīng)該是一個(gè)空文件(這告訴 Python 該目錄是一個(gè)套件包)。您可以通過復(fù)制和重命名框架測(cè)試文件/catalog/tests.py,來創(chuàng)建三個(gè)測(cè)試文件。

      注意: 我們構(gòu)建 Django 骨架網(wǎng)站時(shí),會(huì)自動(dòng)創(chuàng)建骨架測(cè)試文件/catalog/tests.py 。將所有測(cè)試放入其中是完全“合法的”,但如果測(cè)試正確,您將很快得到一個(gè)非常龐大且難以管理的測(cè)試文件。

      刪除骨架文件,因?yàn)槲覀儾恍枰?/p>

      打開 /catalog/tests/test_models.py。 該文件應(yīng)導(dǎo)入django.test.TestCase,如下所示:

      from django.test import TestCase
      
      # Create your tests here.

      通常,您將為要測(cè)試的每個(gè)模型/視圖/表單添加測(cè)試類別,并使用個(gè)別方法來測(cè)試特定功能。在其他情況下,您可能希望有一個(gè)分開的類別,來測(cè)試特定用例,使用個(gè)別的測(cè)試函數(shù),來測(cè)試該用例的各個(gè)方面(例如,測(cè)試模型字段已正確驗(yàn)證的類,以及測(cè)試每個(gè)可能的失敗案例的函數(shù))。相同地,這樣的結(jié)構(gòu)非常適合您,但最好您能保持一致。

      將下面的測(cè)試類別,添加到文件的底部。該類別演示了,如何通過派生TestCase,構(gòu)建測(cè)試用例類。

      class YourTestClass(TestCase):
      
          @classmethod
          def setUpTestData(cls):
              print("setUpTestData: Run once to set up non-modified data for all class methods.")
              pass
      
          def setUp(self):
              print("setUp: Run once for every test method to setup clean data.")
              pass
      
          def test_false_is_false(self):
              print("Method: test_false_is_false.")
              self.assertFalse(False)
      
          def test_false_is_true(self):
              print("Method: test_false_is_true.")
              self.assertTrue(False)
      
          def test_one_plus_one_equals_two(self):
              print("Method: test_one_plus_one_equals_two.")
              self.assertEqual(1 + 1, 2)

      新的類別定義了兩個(gè)可用于測(cè)試之前的配置的方法(例如,創(chuàng)建測(cè)試所需的任何模型或其他對(duì)象):

      • setUpTestData() 用于類級(jí)別設(shè)置,在測(cè)試運(yùn)行開始的時(shí)侯,會(huì)調(diào)用一次。您可以使用它來創(chuàng)建在任何測(cè)試方法中,都不會(huì)修改或更改的對(duì)象。

      • setUp() 在每個(gè)測(cè)試函數(shù)之前被調(diào)用,以設(shè)置可能被測(cè)試修改的任何對(duì)象(每個(gè)測(cè)試函數(shù),都將獲得這些對(duì)象的 “新” 版本)。

      注意:測(cè)試類別還有一個(gè)我們還沒有使用的tearDown()方法。此方法對(duì)數(shù)據(jù)庫測(cè)試不是特別有用,因?yàn)?code>TestCase基類會(huì)為您處理數(shù)據(jù)庫拆卸。

      下面我們有一些測(cè)試方法,它們使用 Assert 函數(shù)來測(cè)試條件是真,假或相等(AssertTrue, AssertFalse, AssertEqual)。如果條件評(píng)估不如預(yù)期,則測(cè)試將失敗,并將錯(cuò)誤報(bào)告給控制臺(tái)。

      AssertTrue, AssertFalse, AssertEqual unittest 提供的標(biāo)準(zhǔn)斷言。框架中還有其他標(biāo)準(zhǔn)斷言,還有 Django 特定的斷言,來測(cè)試視圖是否重定向(assertRedirects),或測(cè)試是否已使用特定模板(assertTemplateUsed)等。

      注意:您通常不應(yīng)在測(cè)試中包含print() 函數(shù),如上所示。我們這樣做,只是為了讓您可以看到在控制臺(tái)中,調(diào)用設(shè)置功能的順序(在下一節(jié)中)。

      如何運(yùn)行測(cè)試節(jié)

      要運(yùn)行所有測(cè)試,最簡(jiǎn)單的方法,是使用以下命令:

      python3 manage.py test

      這將查找當(dāng)前目錄下,使用模式 test*.py 命名的所有文件,并運(yùn)行使用適當(dāng)基類定義的所有測(cè)試(這里我們有許多測(cè)試文件,但只有 /catalog/tests/test_models.py 目前包含任何測(cè)試。)。默認(rèn)情況下,測(cè)試將僅單獨(dú)報(bào)告測(cè)試失敗,然后是測(cè)試摘要。

      如果您收到類似于以下內(nèi)容的錯(cuò)誤:ValueError: Missing staticfiles manifest entry ... 這可能是因?yàn)槟J(rèn)情況下,測(cè)試不會(huì)運(yùn)行 collectstatic,而您的應(yīng)用程序正在使用需要它的儲(chǔ)存類別(有關(guān)更多信息,請(qǐng)參閱 manifest_strict)。有許多方法可以解決這個(gè)問題 - 最簡(jiǎn)單的方法,是在運(yùn)行測(cè)試之前,簡(jiǎn)單地運(yùn)行collectstatic:

      python3 manage.py collectstatic

      在 LocalLibrary 的根目錄中,運(yùn)行測(cè)試。您應(yīng)該看到如下所示的輸出。

      >python3 manage.py test
      
      Creating test database for alias 'default'...
      setUpTestData: Run once to set up non-modified data for all class methods.
      setUp: Run once for every test method to setup clean data.
      Method: test_false_is_false.
      .setUp: Run once for every test method to setup clean data.
      Method: test_false_is_true.
      FsetUp: Run once for every test method to setup clean data.
      Method: test_one_plus_one_equals_two.
      .
      ======================================================================
      FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass)
      ----------------------------------------------------------------------
      Traceback (most recent call last):
        File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true
          self.assertTrue(False)
      AssertionError: False is not true
      
      ----------------------------------------------------------------------
      Ran 3 tests in 0.075s
      
      FAILED (failures=1)
      Destroying test database for alias 'default'...

      在這里,我們看到有一個(gè)測(cè)試失敗,我們可以確切地看到哪個(gè)函數(shù)失敗了、為什么失敗(這個(gè)失敗是預(yù)期的,因?yàn)?False不是 True!)。

      提示: 從上面的測(cè)試輸出中,學(xué)到的最重要事情是,如果為對(duì)象和方法使用描述性/信息性名稱,它會(huì)更有價(jià)值。

      上面以粗體顯示的文本,通常不會(huì)出現(xiàn)在測(cè)試輸出中(這是由我們的測(cè)試中的print()函數(shù)生成的)。這顯示了如何為類調(diào)用setUpTestData()方法,并在每個(gè)方法之前調(diào)用setUp()。

      接下來的部分,將介紹如何運(yùn)行特定測(cè)試,以及如何控制測(cè)試顯示的信息量。

      顯示更多測(cè)試信息節(jié)

      如果您想獲得有關(guān)測(cè)試運(yùn)行的更多信息,可以更改詳細(xì)程度。例如,要列出測(cè)試成功和失?。ㄒ约坝嘘P(guān)如何設(shè)置測(cè)試數(shù)據(jù)庫的大量信息),您可以將詳細(xì)程度設(shè)置為 “2”,如下所示:

      python3 manage.py test --verbosity 2

      允許的詳細(xì)級(jí)別為 0, 1 ,2 和 3,默認(rèn)值為 “1”。

      運(yùn)行特定測(cè)試節(jié)

      如果要運(yùn)行測(cè)試的子集,可以通過指定包,模塊,TestCase子類或方法的完整路徑(包含點(diǎn))來執(zhí)行此操作:

      python3 manage.py test catalog.tests   # Run the specified module
      python3 manage.py test catalog.tests.test_models  # Run the specified module
      python3 manage.py test catalog.tests.test_models.YourTestClass # Run the specified class
      python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two  # Run the specified method

      LocalLibrary 測(cè)試節(jié)

      現(xiàn)在我們知道,如何運(yùn)行我們的測(cè)試,以及我們需要測(cè)試哪些東西,讓我們看一些實(shí)際的例子。

      注意: 我們不會(huì)編寫所有可能的測(cè)試,但這應(yīng)該可以讓您了解測(cè)試的工作原理,以及您可以做些什么。

      模型節(jié)

      如上所述,我們應(yīng)該測(cè)試我們?cè)O(shè)計(jì)的任何內(nèi)容,或由我們編寫的代碼定義的內(nèi)容,而不是已經(jīng)由 Django 或 Python 開發(fā)團(tuán)隊(duì)測(cè)試過的庫/代碼。

      例如,請(qǐng)考慮下面的作者模型 Author。在這里,我們應(yīng)該測(cè)試所有字段的標(biāo)簽,因?yàn)榧词刮覀儧]有明確指定它們中的大部分,我們也有一個(gè)設(shè)計(jì),說明這些值應(yīng)該是什么。如果我們不測(cè)試值,那么我們不知道字段標(biāo)簽,是否具有其預(yù)期值。同樣,雖然我們相信 Django 會(huì)創(chuàng)建一個(gè)指定長(zhǎng)度的字段,但值得為這個(gè)長(zhǎng)度指定一個(gè)測(cè)試,以確保它按計(jì)劃實(shí)現(xiàn)。

      class Author(models.Model):
          first_name = models.CharField(max_length=100)
          last_name = models.CharField(max_length=100)
          date_of_birth = models.DateField(null=True, blank=True)
          date_of_death = models.DateField('Died', null=True, blank=True)
          
          def get_absolute_url(self):
              return reverse('author-detail', args=[str(self.id)])
          
          def __str__(self):
              return '%s, %s' % (self.last_name, self.first_name)

      打開我們的 /catalog/tests/test_models.py,并用 Author模型的以下測(cè)試代碼,替換任何現(xiàn)有代碼。

      在這里,您將看到我們首先導(dǎo)入 TestCase,并使用描述性名稱,從中派生我們的測(cè)試類(AuthorModelTest),以便我們可以輕松識(shí)別測(cè)試輸出中的任何失敗測(cè)試。然后我們調(diào)用setUpTestData(),來創(chuàng)建一個(gè)我們將使用,但不在任何測(cè)試中修改的作者對(duì)象。

      from django.test import TestCase
      
      # Create your tests here.
      
      from catalog.models import Author
      
      class AuthorModelTest(TestCase):
      
          @classmethod
          def setUpTestData(cls):
              #Set up non-modified objects used by all test methods
              Author.objects.create(first_name='Big', last_name='Bob')
      
          def test_first_name_label(self):
              author=Author.objects.get(id=1)
              field_label = author._meta.get_field('first_name').verbose_name
              self.assertEquals(field_label,'first name')
      
          def test_date_of_death_label(self):
              author=Author.objects.get(id=1)
              field_label = author._meta.get_field('date_of_death').verbose_name
              self.assertEquals(field_label,'died')
      
          def test_first_name_max_length(self):
              author=Author.objects.get(id=1)
              max_length = author._meta.get_field('first_name').max_length
              self.assertEquals(max_length,100)
      
          def test_object_name_is_last_name_comma_first_name(self):
              author=Author.objects.get(id=1)
              expected_object_name = '%s, %s' % (author.last_name, author.first_name)
              self.assertEquals(expected_object_name,str(author))
      
          def test_get_absolute_url(self):
              author=Author.objects.get(id=1)
              #This will also fail if the urlconf is not defined.
              self.assertEquals(author.get_absolute_url(),'/catalog/author/1')

      字段測(cè)試檢查字段標(biāo)簽(verbose_name)的值,以及字符字段的大小,是否符合預(yù)期。這些方法都有描述性名稱,并遵循相同的模式:

      author=Author.objects.get(id=1)   # Get an author object to test
      field_label = author._meta.get_field('first_name').verbose_name   # Get the metadata for the required field and use it to query the required field data
      self.assertEquals(field_label,'first name')  # Compare the value to the expected result

      有趣的事情是:

      • 我們無法使用 author.first_name.verbose_name直接獲取 verbose_name,因?yàn)?code>author.first_name 是一個(gè)字符串(不是我們可以用來訪問其屬性的first_name 對(duì)象的句柄)。取而代之的是,我們需要使用作者的 _meta屬性,來獲取字段的實(shí)例,并使用它來查詢其他信息。
           

      • 我們選擇使用 assertEquals(field_label,'first name') ,而不是assertTrue(field_label == 'first name')。這樣做的原因是,如果測(cè)試失敗,前者的輸出,會(huì)告訴您標(biāo)簽實(shí)際上是什么,這使得調(diào)試問題變得更容易一些。

      注意: 已省略對(duì)last_namedate_of_birth標(biāo)簽的測(cè)試,以及 last_name字段長(zhǎng)度的測(cè)試?,F(xiàn)在按照上面顯示的命名約定和方法,添加您自己的版本。

      我們還需要測(cè)試我們的自定義方法。這些基本上只是檢查對(duì)象名稱,是否按照我們的預(yù)期,使用“姓氏”,“名字”格式構(gòu)建,并且我們?yōu)?code>Author獲取的 URL,是我們所期望的。

      def test_object_name_is_last_name_comma_first_name(self):
          author=Author.objects.get(id=1)
          expected_object_name = '%s, %s' % (author.last_name, author.first_name)
          self.assertEquals(expected_object_name,str(author))
              
      def test_get_absolute_url(self):
          author=Author.objects.get(id=1)
          #This will also fail if the urlconf is not defined.
          self.assertEquals(author.get_absolute_url(),'/catalog/author/1')

      立即運(yùn)行測(cè)試。如果您按照模型教程中的描述,創(chuàng)建了作者模型,則很可能會(huì)出現(xiàn)date_of_death標(biāo)簽的錯(cuò)誤,如下所示。測(cè)試失敗,是因?yàn)樗鼘懙氖瞧谕麡?biāo)簽定義遵循 Django 的約定,即沒有大寫標(biāo)簽的第一個(gè)字母(Django 會(huì)為你做這個(gè))。

      ======================================================================
      FAIL: test_date_of_death_label (catalog.tests.test_models.AuthorModelTest)
      ----------------------------------------------------------------------
      Traceback (most recent call last):
        File "D:\...\locallibrary\catalog\tests\test_models.py", line 32, in test_date_of_death_label
          self.assertEquals(field_label,'died')
      AssertionError: 'Died' != 'died'
      - Died
      ? ^
      + died
      ? ^

      這是一個(gè)非常小的錯(cuò)誤,但它確實(shí)強(qiáng)調(diào)了,編寫測(cè)試如何能夠更徹底地檢查,您可能做出的任何假設(shè)。

      注意: 將 date_of_death字段(/catalog/models.py)的標(biāo)簽更改為“death”并重新運(yùn)行測(cè)試。

      用于測(cè)試其他模型的模式,也類似于此,因此我們不會(huì)繼續(xù)進(jìn)一步討論這些模式。請(qǐng)隨意為其他模型,創(chuàng)建您自己的測(cè)試。

      表單節(jié)

      測(cè)試表單的理念,與測(cè)試模型的理念相同;您需要測(cè)試您編碼、或設(shè)計(jì)指定的任何內(nèi)容,但不測(cè)試底層框架,和其他第三方庫的行為。

      通常,這意味著您應(yīng)該測(cè)試表單,是否包含您想要的字段,并使用適當(dāng)?shù)臉?biāo)簽和幫助文本,顯示這些字段。您無需驗(yàn)證 Django 是否正確驗(yàn)證了字段類型(除非您創(chuàng)建了自己的自定義字段和驗(yàn)證) - 即您不需要測(cè)試電子郵件字段,是否只接受電子郵件。但是,您需要測(cè)試,您希望在字段上執(zhí)行的任何其他驗(yàn)證,以及您的代碼將為錯(cuò)誤生成的任何消息。

      考慮我們更新書本的表格。這只有一個(gè)繼續(xù)借閱的日期字段,它將包含我們需要驗(yàn)證的標(biāo)簽,和幫助文本。

      class RenewBookForm(forms.Form):
          """
          Form for a librarian to renew books.
          """
          renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
      
          def clean_renewal_date(self):
              data = self.cleaned_data['renewal_date']
      
              #Check date is not in past.
              if data < datetime.date.today():
                  raise ValidationError(_('Invalid date - renewal in past'))
              #Check date is in range librarian allowed to change (+4 weeks)
              if data > datetime.date.today() + datetime.timedelta(weeks=4):
                  raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
      
              # Remember to always return the cleaned data.
              return data

      打開我們的 /catalog/tests/test_forms.py 文件,并用RenewBookForm表單的以下測(cè)試代碼,替換任何現(xiàn)有代碼。我們首先導(dǎo)入我們的表單,和一些 Python 和 Django 庫,以幫助測(cè)試與時(shí)間相關(guān)的功能。然后,我們以與模型相同的方式,聲明我們的表單測(cè)試類,使用我們的 TestCase 派生測(cè)試類的描述性名稱。

      from django.test import TestCase
      
      # Create your tests here.
      
      import datetime
      from django.utils import timezone
      from catalog.forms import RenewBookForm
      
      class RenewBookFormTest(TestCase):
      
          def test_renew_form_date_field_label(self):
              form = RenewBookForm()        
              self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date')
      
          def test_renew_form_date_field_help_text(self):
              form = RenewBookForm()
              self.assertEqual(form.fields['renewal_date'].help_text,'Enter a date between now and 4 weeks (default 3).')
      
          def test_renew_form_date_in_past(self):
              date = datetime.date.today() - datetime.timedelta(days=1)
              form_data = {'renewal_date': date}
              form = RenewBookForm(data=form_data)
              self.assertFalse(form.is_valid())
      
          def test_renew_form_date_too_far_in_future(self):
              date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1)
              form_data = {'renewal_date': date}
              form = RenewBookForm(data=form_data)
              self.assertFalse(form.is_valid())
      
          def test_renew_form_date_today(self):
              date = datetime.date.today()
              form_data = {'renewal_date': date}
              form = RenewBookForm(data=form_data)
              self.assertTrue(form.is_valid())
              
          def test_renew_form_date_max(self):
              date = timezone.now() + datetime.timedelta(weeks=4)
              form_data = {'renewal_date': date}
              form = RenewBookForm(data=form_data)
              self.assertTrue(form.is_valid())

      前兩個(gè)函數(shù),測(cè)試字段的labelhelp_text,是否符合預(yù)期。我們必須使用字段字典訪問該字段(例如form.fields['renewal_date'])。請(qǐng)注意,我們還必須測(cè)試標(biāo)簽值,是否為None,因?yàn)榧词?Django 將呈現(xiàn)正確的標(biāo)簽,如果未明確設(shè)置該值,它也會(huì)返回None。

      其余函數(shù),測(cè)試表單對(duì)于續(xù)借日期,在可接受范圍內(nèi)是否有效,對(duì)于范圍外的值,是否無效。請(qǐng)注意我們?nèi)绾问褂?code>datetime.timedelta(),在當(dāng)前日期(datetime.date.today())周圍構(gòu)建測(cè)試日期值(在這種情況下指定天數(shù)或周數(shù))。然后我們只需創(chuàng)建表單,傳入我們的數(shù)據(jù),并測(cè)試它是否有效。

      注意: 這里我們實(shí)際上并沒有使用數(shù)據(jù)庫,或測(cè)試客戶端。考慮修改這些測(cè)試,以使用SimpleTestCase

      如果表單無效,我們還需要驗(yàn)證是否引發(fā)了正確的錯(cuò)誤,但這通常是作為視圖處理的一部分完成的,因此我們將在下一節(jié)中處理。

      這就是表單的全部;我們確實(shí)有其他一些的東西,但它們是由基于類的通用編輯視圖自動(dòng)創(chuàng)建的,應(yīng)該在那里進(jìn)行測(cè)試!運(yùn)行測(cè)試,并確認(rèn)我們的代碼仍然通過!

      視圖節(jié)

      為了驗(yàn)證我們的視圖行為,我們使用 Django 的測(cè)試客戶端。這個(gè)類,就像一個(gè)虛擬的Web瀏覽器,我們可以使用它,來模擬URL上的GETPOST請(qǐng)求,并觀察響應(yīng)。我們幾乎可以看到,關(guān)于響應(yīng)的所有內(nèi)容,從低層級(jí)的 HTTP(結(jié)果標(biāo)頭和狀態(tài)代碼),到我們用來呈現(xiàn)HTML的模板,以及我們傳遞給它的上下文數(shù)據(jù)。我們還可以看到重定向鏈(如果有的話),并在每一步檢查URL,和狀態(tài)代碼。這允許我們驗(yàn)證每個(gè)視圖,是否正在執(zhí)行預(yù)期的操作。

      讓我們從最簡(jiǎn)單的視圖開始,它提供了所有作者的列表。它顯示在 URL /catalog/authors/ 當(dāng)中(URL 配置中,名為 “authors” 的 URL)。

      class AuthorListView(generic.ListView):
          model = Author
          paginate_by = 10

      由于這是一個(gè)通用列表視圖,幾乎所有內(nèi)容,都由 Django 為我們完成??梢哉f,如果您信任 Django,那么您唯一需要測(cè)試的,是視圖可以通過正確的 URL 訪問,并且可以使用其名稱進(jìn)行訪問。但是,如果您使用的是測(cè)試驅(qū)動(dòng)的開發(fā)過程,則首先編寫測(cè)試,確認(rèn)視圖顯示所有作者,并將其分成10個(gè)。

      打開 /catalog/tests/test_views.py 文件,并用AuthorListView的以下測(cè)試代碼,替換任何現(xiàn)有文本。和以前一樣,我們導(dǎo)入模型,和一些有用的類。在setUpTestData()方法中,我們?cè)O(shè)置了許多Author對(duì)象,以便我們可以測(cè)試我們的分頁。

      from django.test import TestCase
      
      # Create your tests here.
      
      from catalog.models import Author
      from django.urls import reverse
      
      class AuthorListViewTest(TestCase):
      
          @classmethod
          def setUpTestData(cls):
              #Create 13 authors for pagination tests
              number_of_authors = 13
              for author_num in range(number_of_authors):
                  Author.objects.create(first_name='Christian %s' % author_num, last_name = 'Surname %s' % author_num,)
                 
          def test_view_url_exists_at_desired_location(self): 
              resp = self.client.get('/catalog/authors/') 
              self.assertEqual(resp.status_code, 200)  
                 
          def test_view_url_accessible_by_name(self):
              resp = self.client.get(reverse('authors'))
              self.assertEqual(resp.status_code, 200)
              
          def test_view_uses_correct_template(self):
              resp = self.client.get(reverse('authors'))
              self.assertEqual(resp.status_code, 200)
      
              self.assertTemplateUsed(resp, 'catalog/author_list.html')
              
          def test_pagination_is_ten(self):
              resp = self.client.get(reverse('authors'))
              self.assertEqual(resp.status_code, 200)
              self.assertTrue('is_paginated' in resp.context)
              self.assertTrue(resp.context['is_paginated'] == True)
              self.assertTrue( len(resp.context['author_list']) == 10)
      
          def test_lists_all_authors(self):
              #Get second page and confirm it has (exactly) remaining 3 items
              resp = self.client.get(reverse('authors')+'?page=2')
              self.assertEqual(resp.status_code, 200)
              self.assertTrue('is_paginated' in resp.context)
              self.assertTrue(resp.context['is_paginated'] == True)
              self.assertTrue( len(resp.context['author_list']) == 3)

      所有測(cè)試,都使用客戶端(屬于我們的TestCase的派生類)來模擬GET請(qǐng)求,并獲得響應(yīng)(resp)。第一個(gè)版本檢查特定 URL(注意,只是沒有域名的特定路徑),而第二個(gè)版本從 URL配置中的名稱生成 URL。

      resp = self.client.get('/catalog/authors/')
      resp = self.client.get(reverse('authors'))

      獲得響應(yīng)后,我們會(huì)查詢其狀態(tài)代碼,使用的模板,響應(yīng)是否已分頁,返回的項(xiàng)目數(shù)以及項(xiàng)目總數(shù)。


      我們?cè)谏厦嫜菔镜淖钣腥さ淖兞渴?code>resp.context,它是視圖傳遞給模板的上下文變量。這對(duì)測(cè)試非常有用,因?yàn)樗试S我們確認(rèn)模板正在獲取所需的所有數(shù)據(jù)。換句話說,我們可以檢查是否正在使用預(yù)期的模板,以及模板獲得的數(shù)據(jù),這對(duì)于驗(yàn)證任何渲染問題,是否真的僅僅歸因于模板有很大幫助。

      僅限登錄用戶的視圖

      在某些情況下,您需要測(cè)試僅限登錄用戶的視圖。例如,我們的LoanedBooksByUserListView與我們之前的視圖非常相似,但僅供登錄用戶使用,并且僅顯示當(dāng)前用戶借用的BookInstance記錄,具有出借中“on loan”狀態(tài),并且排序方式為“舊的優(yōu)先”。

      from django.contrib.auth.mixins import LoginRequiredMixin
      
      class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
          """
          Generic class-based view listing books on loan to current user.
          """
          model = BookInstance
          template_name ='catalog/bookinstance_list_borrowed_user.html'
          paginate_by = 10
      
          def get_queryset(self):
              return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')

      將以下測(cè)試代碼,添加到 /catalog/tests/test_views.py。這里我們首先使用SetUp()創(chuàng)建一些用戶登錄帳戶,和BookInstance對(duì)象(以及它們的相關(guān)書本,和其他記錄),我們稍后將在測(cè)試中使用它們。每個(gè)測(cè)試用戶都借用了一半的書本,但我們最初,將所有書本的狀態(tài)設(shè)置為“維護(hù)”。我們使用了SetUp()而不是setUpTestData(),因?yàn)槲覀兩院髸?huì)修改其中的一些對(duì)象。

      注意: 下面的setUp()代碼,會(huì)創(chuàng)建一個(gè)具有指定語言Language的書本,但您的代碼可能不包含語言模型Language,因?yàn)樗亲鳛樘魬?zhàn)創(chuàng)建的。如果是這種情況,只需注釋掉創(chuàng)建或?qū)胝Z言對(duì)象的代碼部分。您還應(yīng)該在隨后的RenewBookInstancesViewTest部分中,執(zhí)行此操作。

      import datetime
      from django.utils import timezone
              
      from catalog.models import BookInstance, Book, Genre, Language
      from django.contrib.auth.models import User #Required to assign User as a borrower
      
      class LoanedBookInstancesByUserListViewTest(TestCase):
      
          def setUp(self):
              #Create two users
              test_user1 = User.objects.create_user(username='testuser1', password='12345') 
              test_user1.save()
              test_user2 = User.objects.create_user(username='testuser2', password='12345') 
              test_user2.save()
              
              #Create a book
              test_author = Author.objects.create(first_name='John', last_name='Smith')
              test_genre = Genre.objects.create(name='Fantasy')
              test_language = Language.objects.create(name='English')
              test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language)
              # Create genre as a post-step
              genre_objects_for_book = Genre.objects.all()
              test_book.genre.set(genre_objects_for_book) #Direct assignment of many-to-many types not allowed.
              test_book.save()
      
              #Create 30 BookInstance objects
              number_of_book_copies = 30
              for book_copy in range(number_of_book_copies):
                  return_date= timezone.now() + datetime.timedelta(days=book_copy%5)
                  if book_copy % 2:
                      the_borrower=test_user1
                  else:
                      the_borrower=test_user2
                  status='m'
                  BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=the_borrower, status=status)
              
          def test_redirect_if_not_logged_in(self):
              resp = self.client.get(reverse('my-borrowed'))
              self.assertRedirects(resp, '/accounts/login/?next=/catalog/mybooks/')
      
          def test_logged_in_uses_correct_template(self):
              login = self.client.login(username='testuser1', password='12345')
              resp = self.client.get(reverse('my-borrowed'))
              
              #Check our user is logged in
              self.assertEqual(str(resp.context['user']), 'testuser1')
              #Check that we got a response "success"
              self.assertEqual(resp.status_code, 200)
      
              #Check we used correct template
              self.assertTemplateUsed(resp, 'catalog/bookinstance_list_borrowed_user.html')

      要驗(yàn)證如果用戶未登錄,視圖將重定向到登錄頁面,我們使用assertRedirects,如test_redirect_if_not_logged_in()中所示。要驗(yàn)證是否已為登錄用戶顯示該頁面,我們首先登錄我們的測(cè)試用戶,然后再次訪問該頁面,并檢查我們獲得的status_code為200(成功)。

      測(cè)試的其余部分,驗(yàn)證我們的觀點(diǎn),僅返回借給當(dāng)前借用人的書本。復(fù)制上面測(cè)試類末尾的(自解釋)代碼。

          def test_only_borrowed_books_in_list(self):
              login = self.client.login(username='testuser1', password='12345')
              resp = self.client.get(reverse('my-borrowed'))
              
              #Check our user is logged in
              self.assertEqual(str(resp.context['user']), 'testuser1')
              #Check that we got a response "success"
              self.assertEqual(resp.status_code, 200)
              
              #Check that initially we don't have any books in list (none on loan)
              self.assertTrue('bookinstance_list' in resp.context)
              self.assertEqual( len(resp.context['bookinstance_list']),0)
              
              #Now change all books to be on loan 
              get_ten_books = BookInstance.objects.all()[:10]
      
              for copy in get_ten_books:
                  copy.status='o'
                  copy.save()
              
              #Check that now we have borrowed books in the list
              resp = self.client.get(reverse('my-borrowed'))
              #Check our user is logged in
              self.assertEqual(str(resp.context['user']), 'testuser1')
              #Check that we got a response "success"
              self.assertEqual(resp.status_code, 200)
              
              self.assertTrue('bookinstance_list' in resp.context)
              
              #Confirm all books belong to testuser1 and are on loan
              for bookitem in resp.context['bookinstance_list']:
                  self.assertEqual(resp.context['user'], bookitem.borrower)
                  self.assertEqual('o', bookitem.status)
      
          def test_pages_ordered_by_due_date(self):
          
              #Change all books to be on loan
              for copy in BookInstance.objects.all():
                  copy.status='o'
                  copy.save()
                  
              login = self.client.login(username='testuser1', password='12345')
              resp = self.client.get(reverse('my-borrowed'))
              
              #Check our user is logged in
              self.assertEqual(str(resp.context['user']), 'testuser1')
              #Check that we got a response "success"
              self.assertEqual(resp.status_code, 200)
                      
              #Confirm that of the items, only 10 are displayed due to pagination.
              self.assertEqual( len(resp.context['bookinstance_list']),10)
              
              last_date=0
              for copy in resp.context['bookinstance_list']:
                  if last_date==0:
                      last_date=copy.due_back
                  else:
                      self.assertTrue(last_date <= copy.due_back)

      你也可以添加分頁測(cè)試,如果你愿意的話!

      使用表單測(cè)試視圖

      使用表單測(cè)試視圖,比上面的情況稍微復(fù)雜一些,因?yàn)槟枰獪y(cè)試更多代碼路徑:初始顯示,數(shù)據(jù)驗(yàn)證失敗后顯示,以及驗(yàn)證成功后顯示。好消息是,我們使用客戶端進(jìn)行測(cè)試的方式,與我們對(duì)僅顯示視圖的方式,幾乎完全相同。

      為了演示,讓我們?yōu)橛糜诶m(xù)借書本的視圖,編寫一些測(cè)試(renew_book_librarian()):

      from .forms import RenewBookForm
      
      @permission_required('catalog.can_mark_returned')
      def renew_book_librarian(request, pk):
          """
          View function for renewing a specific BookInstance by librarian
          """
          book_inst=get_object_or_404(BookInstance, pk = pk)
      
          # If this is a POST request then process the Form data
          if request.method == 'POST':
      
              # Create a form instance and populate it with data from the request (binding):
              form = RenewBookForm(request.POST)
      
              # Check if the form is valid:
              if form.is_valid():
                  # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
                  book_inst.due_back = form.cleaned_data['renewal_date']
                  book_inst.save()
      
                  # redirect to a new URL:
                  return HttpResponseRedirect(reverse('all-borrowed') )
      
          # If this is a GET (or any other method) create the default form
          else:
              proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
              form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
      
          return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})

      我們需要測(cè)試該視圖,僅供具有can_mark_returned權(quán)限的用戶使用,并且如果用戶嘗試?yán)m(xù)借不存在的BookInstance,則會(huì)將用戶重定向到HTTP 404錯(cuò)誤頁面。我們應(yīng)該檢查表單的初始值,是否以未來三周的日期為參考值,如果驗(yàn)證成功,我們將被重定向到 “所有借閱的書本” 視圖。作為驗(yàn)證 - 失敗測(cè)試的一部分,我們還將檢查我們的表單,是否發(fā)送了相應(yīng)的錯(cuò)誤消息。

      將測(cè)試類的第一部分(如下所示),添加到 /catalog/tests/test_views.py 的底部。這將創(chuàng)建兩個(gè)用戶和兩個(gè)書本實(shí)例,但只為一個(gè)用戶提供訪問該視圖所需的權(quán)限。在測(cè)試期間,授予權(quán)限的代碼以粗體顯示:

      from django.contrib.auth.models import Permission # Required to grant the permission needed to set a book as returned.
      
      class RenewBookInstancesViewTest(TestCase):
      
          def setUp(self):
              #Create a user
              test_user1 = User.objects.create_user(username='testuser1', password='12345')
              test_user1.save()
      
              test_user2 = User.objects.create_user(username='testuser2', password='12345')
              test_user2.save()        permission = Permission.objects.get(name='Set book as returned')
              test_user2.user_permissions.add(permission)
              test_user2.save()
      
              #Create a book
              test_author = Author.objects.create(first_name='John', last_name='Smith')
              test_genre = Genre.objects.create(name='Fantasy')
              test_language = Language.objects.create(name='English')
              test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language,)
              # Create genre as a post-step
              genre_objects_for_book = Genre.objects.all()
              test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed.
              test_book.save()
      
              #Create a BookInstance object for test_user1
              return_date= datetime.date.today() + datetime.timedelta(days=5)
              self.test_bookinstance1=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user1, status='o')
      
              #Create a BookInstance object for test_user2
              return_date= datetime.date.today() + datetime.timedelta(days=5)
              self.test_bookinstance2=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user2, status='o')

      將以下測(cè)試添加到測(cè)試類的底部。這些檢查只有具有正確權(quán)限的用戶(testuser2)才能訪問該視圖。我們檢查所有情況:當(dāng)用戶沒有登錄時(shí)、當(dāng)用戶登錄但沒有正確的權(quán)限,當(dāng)用戶有權(quán)限但不是借用人(應(yīng)該成功),以及當(dāng)他們嘗試訪問不存在的BookInstance,會(huì)發(fā)生什么。我們還檢查是否使用了正確的模板。

        def test_redirect_if_not_logged_in(self):
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
              #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
              self.assertEqual( resp.status_code,302)
              self.assertTrue( resp.url.startswith('/accounts/login/') )
              
          def test_redirect_if_logged_in_but_not_correct_permission(self):
              login = self.client.login(username='testuser1', password='12345')
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
              
              #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
              self.assertEqual( resp.status_code,302)
              self.assertTrue( resp.url.startswith('/accounts/login/') )
      
          def test_logged_in_with_permission_borrowed_book(self):
              login = self.client.login(username='testuser2', password='12345')
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance2.pk,}) )
              
              #Check that it lets us login - this is our book and we have the right permissions.
              self.assertEqual( resp.status_code,200)
      
          def test_logged_in_with_permission_another_users_borrowed_book(self):
              login = self.client.login(username='testuser2', password='12345')
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
              
              #Check that it lets us login. We're a librarian, so we can view any users book
              self.assertEqual( resp.status_code,200)
      
          def test_HTTP404_for_invalid_book_if_logged_in(self):
              import uuid 
              test_uid = uuid.uuid4() #unlikely UID to match our bookinstance!
              login = self.client.login(username='testuser2', password='12345')
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid,}) )
              self.assertEqual( resp.status_code,404)
              
          def test_uses_correct_template(self):
              login = self.client.login(username='testuser2', password='12345')
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
              self.assertEqual( resp.status_code,200)
      
              #Check we used correct template
              self.assertTemplateUsed(resp, 'catalog/book_renew_librarian.html')

      添加下一個(gè)測(cè)試方法,如下所示。這將檢查表單的初始日期,是將來三周。請(qǐng)注意我們?nèi)绾文軌蛟L問表單字段的初始值內(nèi)的值(以粗體顯示)。

          def test_form_renewal_date_initially_has_date_three_weeks_in_future(self):
              login = self.client.login(username='testuser2', password='12345')
              resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
              self.assertEqual( resp.status_code,200)
              
              date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3)
              self.assertEqual(resp.context['form'].initial['renewal_date'], date_3_weeks_in_future )

      下一個(gè)測(cè)試(將其添加到類中)會(huì)檢查如果續(xù)借成功,視圖會(huì)重定向到所有借書的列表。這里的不同之處在于,我們首次展示了,如何使用客戶端發(fā)布(POST)數(shù)據(jù)。 post數(shù)據(jù)是post函數(shù)的第二個(gè)參數(shù),并被指定為鍵/值的字典。

          def test_redirects_to_all_borrowed_book_list_on_success(self):
              login = self.client.login(username='testuser2', password='12345')
              valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2)
              resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future} )        self.assertRedirects(resp, reverse('all-borrowed') )

      重要:全部借用的視圖作為額外挑戰(zhàn),您的代碼可能會(huì)改為重定向到主頁'/'。如果是這樣,請(qǐng)將測(cè)試代碼的最后兩行,修改為與下面的代碼類似。請(qǐng)求中的follow=True,確保請(qǐng)求返回最終目標(biāo)URL(因此檢查/catalog/而不是/)。

      resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future},follow=True ) self.assertRedirects(resp, '/catalog/')

      將最后兩個(gè)函數(shù),復(fù)制到類中,如下所示。這些再次測(cè)試POST請(qǐng)求,但在這種情況下具有無效的續(xù)借日期。我們使用assertFormError() ,來驗(yàn)證錯(cuò)誤消息是否符合預(yù)期。

          def test_form_invalid_renewal_date_past(self):
              login = self.client.login(username='testuser2', password='12345')       
              date_in_past = datetime.date.today() - datetime.timedelta(weeks=1)
              resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':date_in_past} )
              self.assertEqual( resp.status_code,200)
              self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal in past')        
          def test_form_invalid_renewal_date_future(self):
              login = self.client.login(username='testuser2', password='12345')
              invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5)
              resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':invalid_date_in_future} )
              self.assertEqual( resp.status_code,200)
              self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')

      可以使用相似的技術(shù),來測(cè)試其他視圖。

      模板節(jié)

      Django 提供測(cè)試 API 來檢查您的視圖,是否正在調(diào)用正確的模板,并允許您驗(yàn)證,是否正在發(fā)送正確的信息。但是,沒有特定的 API,支持在 Django中測(cè)試 HTML輸出,是否按預(yù)期呈現(xiàn)。

      其他推薦的測(cè)試工具節(jié)

      Django 的測(cè)試框架,可以幫助您編寫有效的單元和集成測(cè)試 - 我們只涉及底層單元測(cè)試框架unittest可以做什么,而不去談 Django 的其他部分(例如,查看如何使用unittest.mock 修補(bǔ)第三方庫,以便您可以更徹底地測(cè)試自己的代碼)。

      雖然您可以使用許多其他測(cè)試工具,但我們只重點(diǎn)介紹兩個(gè):

      • Coverage: 此Python工具報(bào)告您的測(cè)試,實(shí)際執(zhí)行了多少代碼。當(dāng)開始使用時(shí),你正試圖找出你應(yīng)該測(cè)試的確切內(nèi)容,它會(huì)特別有用。

      • Selenium 是一個(gè)在真實(shí)瀏覽器中,自動(dòng)化測(cè)試的框架。它允許您模擬與站點(diǎn)交互的真實(shí)用戶,并為系統(tǒng)測(cè)試您的站點(diǎn),提供了一個(gè)很好的框架(從集成測(cè)試開始的下一步)。

      挑戰(zhàn)自己節(jié)

      有許多模型與視圖,我們可以用來測(cè)試。比如一個(gè)簡(jiǎn)單的任務(wù),試著為AuthorCreate視圖,創(chuàng)造一個(gè)測(cè)試案例。

      class AuthorCreate(PermissionRequiredMixin, CreateView):
          model = Author
          fields = '__all__'
          initial={'date_of_death':'12/10/2016',}
          permission_required = 'catalog.can_mark_returned'

      請(qǐng)記住,您需要檢查您指定的任何內(nèi)容、或設(shè)計(jì)的一部分。這將包括誰有權(quán)訪問,初始日期,使用的模板,以及視圖在成功時(shí),重定向的位置。

      總結(jié)節(jié)

      撰寫測(cè)試代碼既不有趣也不吸引人,因此在創(chuàng)造一個(gè)網(wǎng)站時(shí),經(jīng)常被留到最后才處理(或者完全不處理)。然而,它是一個(gè)基礎(chǔ)的部分,以保證你的程式碼,在更改之后是安全、可發(fā)布的,并且維護(hù)起來不會(huì)花費(fèi)太多成本。

      本教程中,我們演示了如何為模型、表單和視圖,編寫并運(yùn)行測(cè)試。最重要的是,我們已經(jīng)提供給您,應(yīng)該測(cè)試的內(nèi)容的簡(jiǎn)短摘要,這通常是您開始時(shí),最難解決的問題。還有很多東西要知道,但即使你已經(jīng)學(xué)到了什么,你也應(yīng)該能夠?yàn)槟愕木W(wǎng)站創(chuàng)建有效的單元測(cè)試。

      下一個(gè)、也是最后一個(gè)教程,展示了如何部署精彩的(并經(jīng)過全面測(cè)試的?。〥jango網(wǎng)站。

      也可以參考節(jié)

      本系列教程節(jié)

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多