Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
擊上方 "程序員小樂"關注公眾號, 星標或置頂一起成長
者:HelloGitHub-追夢人物
一個完整的項目,無論是個人的還是公司的,自動化的單元測試是必不可少,否則以后任何的功能改動將成為你的災難。
假設你正在維護公司的一個項目,這個項目已經開發了幾十個 API 接口,但是沒有任何的單元測試?,F在你的 leader 讓你去修改幾個接口并實現一些新的功能,你接到需求后高效地完成了開發任務,然后手動測試了一遍改動的接口和新實現的功能,確保沒有任何問題后,滿心歡喜地提交了代碼。
代碼上線后出了 BUG,分析原因發現原來是新的改動導致某個舊 API 接口出了問題,因為上線前只對改動的接口做了測試,所以未能發現這個問題。你的 leader 批評了你,你因為事故記了過,年終只能拿個 3.25,非常凄慘。
但是如果我們有全面的單元測試,上述情況就有很大概率避免。只需要在代碼發布前運行一遍單元測試,受影響的功能立即就會報錯,這樣就能在代碼部署前發現問題,從而避免線上事故。
當然以上故事純屬虛構,說這么多只是希望大家在開發時養成良好的習慣,一是寫優雅的代碼,二是一定要測試自己寫的代碼。
在上一部教程 Django博客教程(第二版) 的 單元測試:測試 blog 應用、單元測試:測試評論應用、Coverage.py 統計測試覆蓋率 中,我們詳細講解了 django 單元測試框架的使用方式。這里我們再對 djnago 的測試框架做一個回顧整體回顧,至于如何編寫和運行測試,后面將會進行詳細的講解,如果想對 django 的單元測試做更基礎的了解,推薦回去看看關于測試的 3 篇教程以及 django 的官方文檔。
下面是 djnago 單元測試框架的一些要點:
接下來我們就為博客的 API 接口來編寫單元測試。對 API 接口來說,我們主要關心的就是:對特定的請求返回正確的響應。我們先來梳理一下需要測試的接口和功能點。
博客主要的接口都集中在 PostViewSet 和 CommentViewSet 兩個視圖集中。
CommentViewSet 只有一個接口,功能比較簡單,我們首先以它為例來講解單元測試的編寫方式。
測試接口的一般步驟:
我們以測試創建評論的代碼 test_create_valid_comment 為例:
# filename="comments/tests/test_api.py
from django.apps import apps
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from blog.models import Category, Post
from comments.models import Comment
class CommentViewSetTestCase(APITestCase):
def setUp(self):
self.url = reverse("v1:comment-list")
# 斷開 haystack 的 signal,測試生成的文章無需生成索引
apps.get_app_config("haystack").signal_processor.teardown()
user = User.objects.create_superuser(
username="admin", email="admin@hellogithub.com", password="admin"
)
cate = Category.objects.create(name="測試")
self.post = Post.objects.create(
title="測試標題", body="測試內容", category=cate, author=user,
)
def test_create_valid_comment(self):
data = {
"name": "user",
"email": "user@example.com",
"text": "test comment text",
"post": self.post.pk,
}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
comment = Comment.objects.first()
self.assertEqual(comment.name, data["name"])
self.assertEqual(comment.email, data["email"])
self.assertEqual(comment.text, data["text"])
self.assertEqual(comment.post, self.post)
首先,接口的 URL 地址為:reverse("v1:comment-list")。reverse 函數通過視圖函數名來解析對應的 URL,視圖函數名的格式為:"<namespace>:<basename>-<action name>"。
其中 namespace 是 include 函數指定的 namespace 參數值,例如:
path("api/v1/", include((router.urls, "api"), namespace="v1"))
basename 是 router 在 register 視圖集時指定的參數 basename 的值,例如:
router.register(r"posts", blog.views.PostViewSet, basename="post")
action name 是 action 裝飾器指定的 url_name 參數的值,或者默認的 list、retrieve、create、update、delete 標準 action 名,例如:
# filename="blog/views.py
@action(
methods=["GET"], detail=False, url_path="archive/dates", url_name="archive-date"
)
def list_archive_dates(self, request, *args, **kwargs):
pass
因此,reverse("v1:comment-list") 將被解析為 /api/v1/comments/。
接著我們向這個 URL 發送 POST 請求:response=self.client.post(self.url, data),因為繼承自 django-reset-framework 提供的測試類 APITestCase,因此可以直接通過 self.client 來發送請求,其中 self.client 是 django-rest-framework 提供的 APIClient 的一個實例,專門用來發送 HTTP 測試請求。
最后就是對請求的響應結果 response 做檢查。創建評論成功后返回的狀態碼應該是 201,接口返回的數據在 response.data 屬性中,我們對接口返回的狀態碼和部分數據進行了斷言,確保符合預期的結果。
當然以上是評論創建成功的情況,我們測試時不能只測試正常情況,更要關注邊界情況和異常情況,我們再來增加一個評論數據格式不正確導致創建失敗的測試案例:
# filename="comments/tests/test_api.py
def test_create_invalid_comment(self):
invalid_data = {
"name": "user",
"email": "user@example.com",
"text": "test comment text",
"post": 999,
}
response = self.client.post(self.url, invalid_data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Comment.objects.count(), 0)
套路還是一樣的,第一步向接口發請求,然后對預期返回的響應結果進行斷言。這里由于評論數據不正確(關聯的 id 為 999 的 post 不存在),因此預期返回的狀態碼是 400,同時數據庫中不應該有創建的評論。
盡管 PostViewSet 包含的接口比較多,但是每個接口測試的套路和上面講的是一樣的,依葫蘆畫瓢就行了。因為 PostViewSet 測試代碼較多,這里僅把各個測試案例對應的方法列出來,具體的測試邏輯省略掉。如需了解詳細可查看 GitHub 上項目的源碼:
# filename="blog/tests/test_api.py
from datetime import datetime
from django.apps import apps
from django.contrib.auth.models import User
from django.core.cache import cache
from django.urls import reverse
from django.utils.timezone import utc
from rest_framework import status
from rest_framework.test import APITestCase
from blog.models import Category, Post, Tag
from blog.serializers import PostListSerializer, PostRetrieveSerializer
from comments.models import Comment
from comments.serializers import CommentSerializer
class PostViewSetTestCase(APITestCase):
def setUp(self):
# 斷開 haystack 的 signal,測試生成的文章無需生成索引
apps.get_app_config("haystack").signal_processor.teardown()
# 清除緩存,防止限流
cache.clear()
# 設置博客數據
# post3 category2 tag2 2020-08-01 comment1 comment2
# post2 category1 tag1 2020-07-31
# post1 category1 tag1 2020-07-10
def test_list_post(self):
"""
這個方法測試文章列表接口,預期的響應狀態碼為 200,數據為文章列表序列化后的結果
"""
url = reverse("v1:post-list")
def test_list_post_filter_by_category(self):
"""
這個方法測試獲取某個分類下的文章列表接口,預期的響應狀態碼為 200,數據為文章列表序列化后的結果
"""
url = reverse("v1:post-list")
def test_list_post_filter_by_tag(self):
"""
這個方法測試獲取某個標簽下的文章列表接口,預期的響應狀態碼為 200,數據為文章列表序列化后的結果
"""
url = reverse("v1:post-list")
def test_list_post_filter_by_archive_date(self):
"""
這個方法測試獲取歸檔日期下的文章列表接口,預期的響應狀態碼為 200,數據為文章列表序列化后的結果
"""
url = reverse("v1:post-list")
def test_retrieve_post(self):
"""
這個方法測試獲取單篇文章接口,預期的響應狀態碼為 200,數據為單篇文章序列化后的結果
"""
url = reverse("v1:post-detail", kwargs={"pk": self.post1.pk})
def test_retrieve_nonexistent_post(self):
"""
這個方法測試獲取一篇不存在的文章,預期的響應狀態碼為 404
"""
url = reverse("v1:post-detail", kwargs={"pk": 9999})
def test_list_archive_dates(self):
"""
這個方法測試獲取文章的歸檔日期列表接口
"""
url = reverse("v1:post-archive-date")
def test_list_comments(self):
"""
這個方法測試獲取某篇文章的評論列表接口,預期的響應狀態碼為 200,數據為評論列表序列化后的結果
"""
url = reverse("v1:post-comment", kwargs={"pk": self.post3.pk})
def test_list_nonexistent_post_comments(self):
"""
這個方法測試獲取一篇不存在的文章的評論列表,預期的響應狀態碼為 404
"""
url = reverse("v1:post-comment", kwargs={"pk": 9999})
我們以 test_list_post_filter_by_archive_date 為例做一個講解,其它的測試案例代碼邏輯大同小異。
# filename="blog/tests/test_api.py
def test_list_post_filter_by_archive_date(self):
# 解析文章列表接口的 URL
url = reverse("v1:post-list")
# 發送請求,我們這里給 get 方法的第二個參數傳入了一個字典,這個字典代表了 get 請求的查詢參數。
# 例如最終的請求的 URL 會被編碼成:/posts/?created_year=2020&created_month=7
response = self.client.get(url, {"created_year": 2020, "created_month": 7})
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 如何檢查返回的數據是否正確呢?對這個接口的請求,
# 我們預期返回的結果是 post2 和 post1 這兩篇發布于2020年7月的文章序列化后的數據。
# 因此,我們使用 PostListSerializer 對這兩篇文章進行了序列化,
# 然后和返回的結果 response.data["results"] 進行比較。
serializer = PostListSerializer(instance=[self.post2, self.post1], many=True)
self.assertEqual(response.data["results"], serializer.data)
接下來運行測試:
"Linux/macOS"
$ pipenv run coverage run manage.py test
"Windows"
...\> pipenv run coverage run manage.py test
大部分測試都通過了,但是也有一個測試失敗了,也就是說我們通過測試發現了一個 BUG:
======================================================================FAIL: test_list_archive_dates (blog.tests.test_api.PostViewSetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\user\SpaceLocal\Workspace\G_Courses\HelloDjango\HelloDjango-rest-framework-tutorial\blog\tests\test_api.py", line 123, in test_list_archive_dates
self.assertEqual(response.data, ["2020-08", "2020-07"])
AssertionError: Lists differ: ['2020-08-01', '2020-07-01'] != ['2020-08', '2020-07']
失敗的是 test_list_archive_dates 這個測試案例,文章歸檔日期接口返回的數據不符合我們的預期,我們預期得到 yyyy-mm 格式的日期列表,但接口返回的是 yyyy-mm-dd,這是我們之前開發時沒有發現的,通過測試將問題暴露了,這也從一定程度上印證了我們之前強調的測試的作用。
既然已經發現了問題,就來修復它。我相信修復這個 bug 對你來說應該已經是輕而易舉的事了,因此留作練習吧,這里不再講解。
重新運行一遍測試,得到 ok 的狀態。
Ran 55 tests in 8.997s
OK
說明全部測試通過。
以上測試充分了嗎?單憑肉眼自然很難發現,Coverage.py 統計測試覆蓋率 中我們配置了 Coverage.py 并介紹了它的用法,直接運行下面的命令就可以查看代碼的測試覆蓋程度:
"Linux/macOS"
$ pipenv run coverage report
"Windows"
...\> pipenv run coverage report
覆蓋結果如下:
Name Stmts Miss Branch BrPart Cover Missing
-----------------------------------------------------------------
blog\serializers.py 46 5 0 0 89% 82-86
blog\utils.py 21 2 4 1 88% 29->30, 30-31
blog\views.py 119 5 4 0 94% 191, 200, 218-225
comments\views.py 25 1 2 0 96% 59
-----------------------------------------------------------------
TOTAL 1009 13 34 1 98%
可以看到測試覆蓋率整體達到了 98%,但是仍有 4 個文件部分代碼未被測試,命令行中只給出了未被測試覆蓋的代碼行號(Missing 列),不是很直觀,運行下面的命令可以生成一個 HTML 報告,可視化地查看未被測試覆蓋的代碼片段:
"Linux/macOS"
$ pipenv run coverage html
"Windows"
...\> pipenv run coverage html
命令執行后會在項目根目錄生成一個 htmlcov 文件夾,用瀏覽器打開里面的 index.html 頁面就可以查看測試覆蓋情況的詳細報告了。
HTML 報告頁面示例:
未覆蓋的代碼通過紅色高亮背景標出,非常直觀??梢钥吹?blog/views.py 中 CategoryViewSet 和 TagViewSet 未進行測試,按照上面介紹的測試方法補充測試就可以啦。這兩個視圖集都非常的簡單,測試的任務就留作練習了。
blog/serializers.py 中的 HighlightedCharField 未測試,還有 blog/utils.py 中新增的 UpdatedAtKeyBit 未測試,我們編寫相應的測試案例。
UpdatedAtKeyBit 就只有一個 get_data 方法,這個方法預期的邏輯是:從緩存中取得以 self.key 為鍵的緩存值(緩存被設置時的時間),如果緩存未命中,就取當前時間,并將這個時間寫入緩存。
將預期的邏輯寫成測試代碼如下,需要注意的一點是因為這個輔助類不涉及 django 數據庫方面的操作,因此我們直接繼承自更為簡單的 unittest.TestCase,這可以提升測試速度:
# filename="blog/tests/test_utils.py
import unittest
from datetime import datetime
from django.core.cache import cache
from ..utils import Highlighter, UpdatedAtKeyBit
class UpdatedAtKeyBitTestCase(unittest.TestCase):
def test_get_data(self):
# 未緩存的情況
key_bit = UpdatedAtKeyBit()
data = key_bit.get_data()
self.assertEqual(data, str(cache.get(key_bit.key)))
# 已緩存的情況
cache.clear()
now = datetime.utcnow()
now_str = str(now)
cache.set(key_bit.key, now)
self.assertEqual(key_bit.get_data(), now_str)
我們在講解自定義系列化字段的時候講過,序列化字段通過調用 to_representation 方法,將傳入的值進行序列化。HighlightedCharField 的預期邏輯就是調用 to_representation 方法后將傳入的值進行高亮處理。
HighlightedCharField 涉及到一些高級操作,主要是因為 to_representation 方法中涉及到對 HTTP 請求request 的操作。正常的視圖函數調用時,視圖函數會接收到傳入的 request 參數,然后 django-rest-framework 會將 request 傳給序列化器(Serializer)的 _context 屬性,序列化器中的任何序列化字段均可以通過直接訪問 context 屬性而間接訪問到 _context 屬性,從而拿到 request 對象。
但是在單元測試中,可能沒有這樣的視圖函數調用,因此 _context 的設置并不會自動進行,需要我們模擬視圖函數調用時的行為,手動進行設置。主要包括 2 點:
具體的代碼如下,詳細講解請看相關代碼行的注釋:
# filename="blog/tests/test_serializer.py
import unittest
from blog.serializers import HighlightedCharField
from django.test import RequestFactory
from rest_framework.request import Request
class HighlightedCharFieldTestCase(unittest.TestCase):
def test_to_representation(self):
field = HighlightedCharField()
# RequestFactory 專門用來構造 request 對象。
# 這個 RequestFactory 生成的 request 代表了一個對 URL / 訪問的 get 請求,
# 并包含 URL 參數 text=關鍵詞。
# 請求訪問的完整 URL 就是 /?text=關鍵詞
request = RequestFactory().get("/", {"text": "關鍵詞"})
# django-rest-framework 對 django 內置的 request 進行了包裝,
# 因此這里要手動使用 drf 提供的 Request 類對 django 的 request 進行一層包裝。
drf_request = Request(request=request)
# 設置 HighlightedCharField 實例 _context 屬性的值,這樣在其內部就可以通過
# self.context["request"] 拿到請求對象 request
setattr(field, "_context", {"request": drf_request})
document = "無關文本關鍵詞無關文本,其他別的關鍵詞別的無關的詞。"
result = field.to_representation(document)
expected = (
'無關文本<span class="highlighted">關鍵詞</span>無關文本,'
'其他別的<span class="highlighted">關鍵詞</span>別的無關的詞。'
)
self.assertEqual(result, expected)
再次運行一遍測試覆蓋率的檢查命令,這次得到的測試覆蓋率就是 100% 了:
Name Stmts Miss Branch BrPart Cover Missing
---------------------------------------------------
---------------------------------------------------
TOTAL 1047 0 32 0 100%
當然,需要提醒一點的是,測試覆蓋率 100% 并不能說明程序就沒有 BUG 了。線上可能出現各種奇奇怪怪的問題,這些問題可能并沒有寫成測試案例,所以也就沒有測試到。但無論如何,目前我們已經進行了較為充分的測試,就可以考慮發布一個版本了。如果以后再線上遇到什么問題,或者想到了新的測試案例,可以隨時補充進單元測試,以后程序出 BUG 的幾率就會越來越低了。
在我的日常工作中,我是一名專業程序員。我使用c++、c#和Javascript。我是一個開發團隊的一員,他們使用單元測試來驗證我們的代碼是否按照它應該的方式工作。
在本文中,我將通過討論以下主題來研究如何使用Python創建單元測試。
我使用FizzBuzz編碼方式創建了單元測試示例。編碼類型是程序員的練習。在這個練習中,程序員試圖解決一個特定的問題。但主要目標不是解決問題,而是練習編程。FizzBuz是一個簡單的代碼類型,非常適合解釋和展示Python中的單元測試。
單元測試
單元測試是程序員為測試程序的一小部分而編寫的自動化測試。單元測試應該運行得很快。與文件系統、數據庫或網絡交互的測試不是單元測試。
為了在Python中創建第一個FizzBuzz單元測試,我定義了一個繼承自unittest.TestCase的類。這個unittest模塊可以在Python的標準安裝中獲得。
import unittest
class FizzBuzzTest(unittest.TestCase):
def test_one_should_return_one(self):
fizzbuzz=FizzBuzz()
result=fizzbuzz.filter(1)
self.assertEqual('1', result)
def test_two_should_return_two(self):
fizzbuzz=FizzBuzz()
result=fizzbuzz.filter(2)
self.assertEqual('2', result)
第一個測試用例驗證數字1是否通過了FizzBuzz過濾器,它將返回字符串' 1 '。使用self驗證結果。assertEqual方法。方法的第一個參數是預期的結果,第二個參數是實際的結果。
測試用例
我們在測試用例FizzBuzzTest類中調用test_one_should_return_one()方法。測試用例是測試程序特定部分的實際測試代碼。
第一個測試用例驗證數字1是否通過了FizzBuzz過濾器,它將返回字符串' 1 '。使用self驗證結果。assertEqual方法。方法的第一個參數是預期的結果,第二個參數是實際的結果。
如果您查看這兩個測試用例,您會看到它們都創建了FizzBuzz類的一個實例。第一個在第6行,另一個在第11行。
我們可以從這兩個方法中重構FizzBuzz實例的創建,從而改進代碼。
import unittest
class FizzBuzzTest(unittest.TestCase):
def setUp(self):
self.fizzbuzz=FizzBuzz()
def tearDown(self):
pass
def test_one_should_return_one(self):
result=self.fizzbuzz.filter(1)
self.assertEqual('1', result)
def test_two_should_return_two(self):
result=self.fizzbuzz.filter(2)
self.assertEqual('2', result)
我們使用setUp方法創建FizzBuzz類的實例。TestCase基類的設置在每個測試用例之前執行。
另一個方法tearDown是在每個單元測試執行之后調用的。你可以用它來清理或關閉資源。
測試夾具
方法的設置和拆卸是測試夾具的一部分。測試夾具用于配置和構建被測試單元。每個測試用例都可以使用這些通用條件。在本例中,我使用它創建FizzBuzz類的實例。
要運行單元測試,我們需要一個測試運行器。
測試運行器
測試運行程序是執行所有單元測試并報告結果的程序。Python的標準測試運行器可以使用以下命令在終端上運行。
python -m unittest test_fizzbuzz.py
測試套件
單元測試詞匯表的最后一個術語是測試套件。測試套件是測試用例或測試套件的集合。通常一個測試套件包含應該一起運行的測試用例。
測試用例應該被很好地設計??荚嚨拿Q和結構是最重要的。
測試用例名稱
測試的名稱非常重要。它就像一個總結考試內容的標題。如果測試失敗,你首先看到的就是它。因此,名稱應該清楚地表明哪些功能不起作用。
測試用例名稱的列表應該讀起來像摘要或場景列表。這有助于讀者理解被測單元的行為。
構造測試用例方法體
一個設計良好的測試用例由三部分組成。第一部分,安排、設置要測試的對象。第二部分,Act,練習被測單元。最后,第三部分,斷言,對應該發生的事情提出主張。
有時,我在單元測試中添加這三個部分作為注釋,以使其更清楚。
import unittest
class FizzBuzzTest(unittest.TestCase):
def test_one_should_return_one(self):
# Arrange
fizzbuzz=FizzBuzz()
# Act
result=fizzbuzz.filter(1)
# Assert
self.assertEqual('1', result)
每個測試用例的單個斷言
盡管在一個測試用例中可能有很多斷言。我總是嘗試使用單個斷言。
原因是,當斷言失敗時,測試用例的執行就會停止。因此,您永遠不會知道測試用例中的下一個斷言是否成功。
在上一節中,我們使用了unittest模塊。Python的默認安裝安裝這個模塊。unittest模塊于2001年首次引入?;贙ent Beck和Eric Gamma開發的流行的Java單元測試框架JUnit。
另一個模塊pytest是目前最流行的Python單元測試框架。與unittest框架相比,它更具有python風格。您可以將測試用例定義為函數,而不是從基類派生。
因為pytest不在默認的Python安裝中,所以我們使用Python的包安裝程序PIP來安裝它。通過在終端中執行以下命令,可以安裝pytest。
pip install pytest
下面我將第一個FizzBuzz測試用例轉換為pytest。
def test_one_should_return_one():
fizzbuzz=FizzBuzz()
result=fizzbuzz.filter(1)
assert '1'==result
有三個不同點。首先,您不需要導入任何模塊。其次,您不需要實現一個類并從基類派生。最后,您可以使用標準的Python assert方法來代替自定義的方法。
測試裝置
您還記得,單元測試模塊使用setUp和tearDown來配置和構建測試中的單元。相反,pytest使用@pytest.fixture屬性。在您的測試用例中,您可以使用用該屬性裝飾的方法的名稱作為參數。
pytest框架在運行時將它們連接起來,并將fizzBuzz實例注入測試用例中。
@pytest.fixture
def fizzBuzz():
return FizzBuzz()
def test_one_should_return_one(fizzBuzz):
result=fizzBuzz.filter(1)
assert result=='1'
def test_two_should_return_two(fizzBuzz):
result=fizzBuzz.filter(2)
assert result=='2'
如果您想要模擬單元測試tearDown()方法的行為,可以使用相同的方法來實現。不使用return,而是使用yield關鍵字。然后,您可以將清理代碼放在yield之后。
@pytest.fixture
def fizzBuzz():
yield FizzBuzz()
# put your clean up code here
pytest標記
標記是可以在測試各種函數時使用的屬性。例如,如果您將跳過標記添加到您的測試用例中,測試運行器將跳過測試。
@pytest.mark.skip(reason="WIP")
def test_three_should_return_fizz(fizzBuzz):
result=fizzBuzz.filter(3)
assert result=='Fizz'
pytest插件生態系統
pytest有很多插件可以添加額外的功能。到我寫這篇文章的時候,已經有將近900個插件了。例如,pytest-html和pytest-sugar。
pytest-html
pytest- HTML是pytest的插件,它為測試結果生成HTML報告。當您在構建服務器上運行單元測試時,這非常有用。
pytest-sugar
pytest-sugar改變pytest的默認外觀和感覺。它會添加一個進度條,并立即顯示失敗的測試。
有一些工具可以創建代碼覆蓋率報告。這個代碼覆蓋率報告顯示了您的單元測試執行了哪些代碼。
我使用Coverage和pytest-cov來創建代碼覆蓋率報告。覆蓋率是度量代碼覆蓋率的通用包。模塊pytest-cov是pytest的一個插件,用于連接到Coverage。
都可以使用pip安裝。
pip install coverage
pip install pytest-cov
在您安裝了這兩個命令之后,您可以使用這兩個命令生成覆蓋率報告。在終端或命令中運行它們。
coverage run -m pytest
coverage html
第一個生成覆蓋率數據。第二個命令將數據轉換為HTML報告。Coverage將報告存儲在文件系統的htmlcov文件夾中。
如果你在瀏覽器中打開index.html,它會顯示每個文件覆蓋率的概覽。
如果您選擇一個文件,它將顯示下面的屏幕。覆蓋率向源代碼添加了一個指示,顯示單元測試覆蓋了哪一行。
下面我們看到我們的單元測試并沒有涵蓋第12行和第16行。
分支覆蓋度量
覆蓋率還支持分支覆蓋率度量。有了分支覆蓋率,如果您的程序中有一行可以跳轉到下一行以上,覆蓋率跟蹤是否訪問了這些目的地。
您可以通過執行以下命令來創建帶有分支覆蓋率的覆蓋率報告。
pytest——cov-report html:htmlcov——cov-branch——cov=alarm
我指示pytest生成一個帶有分支覆蓋的HTML覆蓋報告。它應該將結果存儲在htmlcov中。而不是為所有文件生成覆蓋率報告,我告訴覆蓋率只使用alarm.py。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。