Django4.0 開始-編寫你的第一個Django應(yīng)用,第5部分

2022-03-16 18:06 更新

自動測試化是什么?

測試代碼,是用來檢查你的代碼能否正常運行的程序。
測試在不同的層次中都存在。有些測試只關(guān)注某個很小的細(xì)節(jié)(某個模型的某個方法的返回值是否滿足預(yù)期?),而另一些測試可能檢查對某個軟件的一系列操作(某一用戶輸入序列是否造成了預(yù)期的結(jié)果?)。其實這和我們在 教程第 2 部分里做的并沒有什么不同,我們使用 ?shell ?來測試某一方法的功能,或者運行某個應(yīng)用并輸入數(shù)據(jù)來檢查它的行為。
真正不同的地方在于,自動化 測試是由某個系統(tǒng)幫你自動完成的。當(dāng)你創(chuàng)建好了一系列測試,每次修改應(yīng)用代碼后,就可以自動檢查出修改后的代碼是否還像你曾經(jīng)預(yù)期的那樣正常工作。你不需要花費大量時間來進(jìn)行手動測試。

為什么你需要寫測試?

但是,為什么需要測試呢?又為什么是現(xiàn)在呢?
你可能覺得學(xué) Python/Django 對你來說已經(jīng)很滿足了,再學(xué)一些新東西的話看起來有點負(fù)擔(dān)過重并且沒什么必要。畢竟,我們的投票應(yīng)用看起來已經(jīng)完美工作了。寫一些自動測試并不能讓它工作的更好。如果寫一個投票應(yīng)用是你想用 Django 完成的唯一工作,那你確實沒必要學(xué)寫測試。但是如果你還想寫更復(fù)雜的項目,現(xiàn)在就是學(xué)習(xí)測試寫法的最好時機(jī)了。

測試將節(jié)約你的時間

在某種程度上,能夠「判斷出代碼是否正常工作」的測試,就稱得上是個令人滿意的了。在更復(fù)雜的應(yīng)用程序中,組件之間可能會有數(shù)十個復(fù)雜的交互。

對其中某一組件的改變,也有可能會造成意想不到的結(jié)果。判斷「代碼是否正常工作」意味著你需要用大量的數(shù)據(jù)來完整的測試全部代碼的功能,以確保你的小修改沒有對應(yīng)用整體造成破壞——這太費時間了。

尤其是當(dāng)你發(fā)現(xiàn)自動化測試能在幾秒鐘之內(nèi)幫你完成這件事時,就更會覺得手動測試實在是太浪費時間了。當(dāng)某人寫出錯誤的代碼時,自動化測試還能幫助你定位錯誤代碼的位置。

有時候你會覺得,和富有創(chuàng)造性和生產(chǎn)力的業(yè)務(wù)代碼比起來,編寫枯燥的測試代碼實在是太無聊了,特別是當(dāng)你知道你的代碼完全沒有問題的時候。

然而,編寫測試還是要比花費幾個小時手動測試你的應(yīng)用,或者為了找到某個小錯誤而胡亂翻看代碼要有意義的多。

測試不僅能發(fā)現(xiàn)錯誤,而且能預(yù)防錯誤

「測試是開發(fā)的對立面」,這種思想是不對的。

如果沒有測試,整個應(yīng)用的行為意圖會變得更加的不清晰。甚至當(dāng)你在看自己寫的代碼時也是這樣,有時候你需要仔細(xì)研讀一段代碼才能搞清楚它有什么用。

而測試的出現(xiàn)改變了這種情況。測試就好像是從內(nèi)部仔細(xì)檢查你的代碼,當(dāng)有些地方出錯時,這些地方將會變得很顯眼——就算你自己沒有意識到那里寫錯了。

測試使你的代碼更有吸引力

你也許遇到過這種情況:你編寫了一個絕贊的軟件,但是其他開發(fā)者看都不看它一眼,因為它缺少測試。沒有測試的代碼不值得信任。 Django 最初開發(fā)者之一的 Jacob Kaplan-Moss 說過:“項目規(guī)劃時沒有包含測試是不科學(xué)的。”
其他的開發(fā)者希望在正式使用你的代碼前看到它通過了測試,這是你需要寫測試的另一個重要原因。

測試有利于團(tuán)隊協(xié)作

前面的幾點都是從單人開發(fā)的角度來說的。復(fù)雜的應(yīng)用可能由團(tuán)隊維護(hù)。測試的存在保證了協(xié)作者不會不小心破壞了了你的代碼(也保證你不會不小心弄壞他們的)。如果你想作為一個 Django 程序員謀生的話,你必須擅長編寫測試!

基礎(chǔ)測試策略

有好幾種不同的方法可以寫測試。
一些開發(fā)者遵循 "測試驅(qū)動" 的開發(fā)原則,他們在寫代碼之前先寫測試。這種方法看起來有點反直覺,但事實上,這和大多數(shù)人日常的做法是相吻合的。我們會先描述一個問題,然后寫代碼來解決它?!笢y試驅(qū)動」的開發(fā)方法只是將問題的描述抽象為了 Python 的測試樣例。
更普遍的情況是,一個剛接觸自動化測試的新手更傾向于先寫代碼,然后再寫測試。雖然提前寫測試可能更好,但是晚點寫起碼也比沒有強(qiáng)。
有時候很難決定從哪里開始下手寫測試。如果你才寫了幾千行 Python 代碼,選擇從哪里開始寫測試確實不怎么簡單。如果是這種情況,那么在你下次修改代碼(比如加新功能,或者修復(fù) Bug)之前寫個測試是比較合理且有效的。
所以,我們現(xiàn)在就開始寫吧。

開始寫我們的第一個測試

首先得有個bug

幸運的是,我們的 ?polls ?應(yīng)用現(xiàn)在就有一個小 bug 需要被修復(fù):我們的要求是如果 ?Question ?是在一天之內(nèi)發(fā)布的,? Question.was_published_recently()? 方法將會返回 ?True ?,然而現(xiàn)在這個方法在 ?Question ?的 ?pub_date ?字段比當(dāng)前時間還晚時也會返回 ?True?(這是個 Bug)。
用?djadmin:`shell`?命令確認(rèn)一下這個方法的日期bug

...\> py manage.py shell
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
True

因為將來發(fā)生的是肯定不是最近發(fā)生的,所以代碼明顯是錯誤的。

創(chuàng)建一個測試來暴露這個bug

我們剛剛在 ?shell ?里做的測試也就是自動化測試應(yīng)該做的工作。所以我們來把它改寫成自動化的吧。
按照慣例,Django 應(yīng)用的測試應(yīng)該寫在應(yīng)用的 ?tests.py? 文件里。測試系統(tǒng)會自動的在所有以 ?tests ?開頭的文件里尋找并執(zhí)行測試代碼。
將下面的代碼寫入 ?polls ?應(yīng)用里的 ?tests.py? 文件內(nèi):

import datetime

from django.test import TestCase
from django.utils import timezone

from .models import Question


class QuestionModelTests(TestCase):

    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is in the future.
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)

我們創(chuàng)建了一個 ?django.test.TestCase? 的子類,并添加了一個方法,此方法創(chuàng)建一個 ?pub_date是未來某天的 ?Question ?實例。然后檢查它的 ?was_published_recently()? 方法的返回值——它應(yīng)該是 ?False?。

運行測試

在終端中,我們通過輸入以下代碼運行測試:

...\> py manage.py test polls

你將會看到運行結(jié)果:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

發(fā)生了什么呢?以下是自動化測試的運行過程:

  • ?python manage.py test polls? 將會尋找 polls 應(yīng)用里的測試代碼
  • 它找到了 ?django.test.TestCase? 的一個子類
  • 它創(chuàng)建一個特殊的數(shù)據(jù)庫供測試使用
  • 它在類中尋找測試方法——以 ?test ?開頭的方法。
  • 在 ?test_was_published_recently_with_future_question? 方法中,它創(chuàng)建了一個 ?pub_date ?值為 30 天后的 ?Question ?實例。
  • 接著使用 ?assertls()? 方法,發(fā)現(xiàn) ?was_published_recently()? 返回了 ?True?,而我們期望它返回 ?False?。

測試系統(tǒng)通知我們哪些測試樣例失敗了,和造成測試失敗的代碼所在的行號。

修復(fù)這個bug

我們早已知道,當(dāng) ?pub_date ?為未來某天時, ?Question.was_published_recently()? 應(yīng)該返回 ?False?。我們修改 ?models.py? 里的方法,讓它只在日期是過去式的時候才返回 ?True?:

def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

然后重新運行測試:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

發(fā)現(xiàn) bug 后,我們編寫了能夠暴露這個 bug 的自動化測試。在修復(fù) bug 之后,我們的代碼順利的通過了測試。

將來,我們的應(yīng)用可能會出現(xiàn)其他的問題,但是我們可以肯定的是,一定不會再次出現(xiàn)這個 bug,因為只要運行一遍測試,就會立刻收到警告。我們可以認(rèn)為應(yīng)用的這一小部分代碼永遠(yuǎn)是安全的。

更全面的測試

我們已經(jīng)搞定一小部分了,現(xiàn)在可以考慮全面的測試 ?was_published_recently()? 這個方法以確定它的安全性,然后就可以把這個方法穩(wěn)定下來了。事實上,在修復(fù)一個 bug 時不小心引入另一個 bug 會是非常令人尷尬的。
我們在上次寫的類里再增加兩個測試,來更全面的測試這個方法:

def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() returns False for questions whose pub_date
    is older than 1 day.
    """
    time = timezone.now() - datetime.timedelta(days=1, seconds=1)
    old_question = Question(pub_date=time)
    self.assertIs(old_question.was_published_recently(), False)

def test_was_published_recently_with_recent_question(self):
    """
    was_published_recently() returns True for questions whose pub_date
    is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)

現(xiàn)在,我們有三個測試來確保 ?Question.was_published_recently()? 方法對于過去,最近,和將來的三種情況都返回正確的值。
再次申明,盡管 ?polls現(xiàn)在是個小型的應(yīng)用,但是無論它以后變得到多么復(fù)雜,無論他和其他代碼如何交互,我們可以在一定程度上保證我們?yōu)橹帉憸y試的方法將按照預(yù)期的方式運行。

測試視圖

我們的投票應(yīng)用對所有問題都一視同仁:它將會發(fā)布所有的問題,也包括那些 ?pub_date ?字段值是未來的問題。我們應(yīng)該改善這一點。如果 ?pub_date ?設(shè)置為未來某天,這應(yīng)該被解釋為這個問題將在所填寫的時間點才被發(fā)布,而在之前是不可見的。

針對視圖的測試

為了修復(fù)上述 bug ,我們這次先編寫測試,然后再去改代碼。事實上,這是一個「測試驅(qū)動」開發(fā)模式的實例,但其實這兩者的順序不太重要。

在我們的第一個測試中,我們關(guān)注代碼的內(nèi)部行為。我們通過模擬用戶使用瀏覽器訪問被測試的應(yīng)用來檢查代碼行為是否符合預(yù)期。

在我們動手之前,先看看需要用到的工具們。

Django測試工具之Client

Django 提供了一個供測試使用的 ?Client ?來模擬用戶和視圖層代碼的交互。我們能在 ?tests.py? 甚至是 ?shell ?中使用它。
我們依照慣例從 ?shell ?開始,首先我們要做一些在 ?tests.py? 里不是必須的準(zhǔn)備工作。第一步是在 ?shell ?中配置測試環(huán)境:

...\> py manage.py shell
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

?setup_test_environment()? 安裝了一個模板渲染器,這將使我們能夠檢查響應(yīng)上的一些額外屬性,如 ?response.context?,否則將無法使用此功能。請注意,這個方法 不會 建立一個測試數(shù)據(jù)庫,所以下面的內(nèi)容將針對現(xiàn)有的數(shù)據(jù)庫運行,輸出結(jié)果可能略有不同,這取決于你已經(jīng)創(chuàng)建了哪些問題。如果你在 ?settings.py? 中的 ?TIME_ZONE ?不正確,你可能會得到意外的結(jié)果。如果你不記得之前的配置,請在繼續(xù)之前檢查。

然后我們需要導(dǎo)入 ?django.test.TestCase? 類(在后續(xù) ?tests.py? 的實例中我們將會使用 ?django.test.TestCase? 類,這個類里包含了自己的 client 實例,所以不需要這一步):

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()

搞定了之后,我們可以要求 client 為我們工作了:

>>> # get a response from '/'
>>> response = client.get('/')
Not Found: /
>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#x27;s up?</a></li>\n    \n    </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

改善視圖代碼

現(xiàn)在的投票列表會顯示將來的投票( ?pub_date ?值是未來的某天)。我們來修復(fù)這個問題。
在 教程的第 4 部分 里,我們介紹了基于 ?ListView ?的視圖類:

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]

我們需要改進(jìn) ?get_queryset()? 方法,讓他它能通過將 Question 的 pub_data 屬性與 ?timezone.now()? 相比較來判斷是否應(yīng)該顯示此 Question。首先我們需要一行 import 語句:

from django.utils import timezone

然后我們把 ?get_queryset ?方法改寫成下面這樣:

def get_queryset(self):
    """
    Return the last five published questions (not including those set to be
    published in the future).
    """
    return Question.objects.filter(
        pub_date__lte=timezone.now()
    ).order_by('-pub_date')[:5]

?Question.objects.filter(pub_date__lte=timezone.now())? 返回一個查詢集,其中包含 ?pub_date小于或等于 - 即早于或等于 - ?timezone.now? 的問題。

測試新視圖

啟動服務(wù)器、在瀏覽器中載入站點、創(chuàng)建一些發(fā)布時間在過去和將來的 ?Questions ?,然后檢驗只有已經(jīng)發(fā)布的 ?Questions ?會展示出來,現(xiàn)在你可以對自己感到滿意了。你不想每次修改可能與這相關(guān)的代碼時都重復(fù)這樣做 —— 所以讓我們基于以上 ?shell ?會話中的內(nèi)容,再編寫一個測試。

將下面的代碼添加到 ?polls/tests.py ?:

from django.urls import reverse

然后我們寫一個公用的快捷函數(shù)用于創(chuàng)建投票問題,再為視圖創(chuàng)建一個測試類:

def create_question(question_text, days):
    """
    Create a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)


class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        """
        If no questions exist, an appropriate message is displayed.
        """
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_past_question(self):
        """
        Questions with a pub_date in the past are displayed on the
        index page.
        """
        question = create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            [question],
        )

    def test_future_question(self):
        """
        Questions with a pub_date in the future aren't displayed on
        the index page.
        """
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_future_question_and_past_question(self):
        """
        Even if both past and future questions exist, only past questions
        are displayed.
        """
        question = create_question(question_text="Past question.", days=-30)
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            [question],
        )

    def test_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        question1 = create_question(question_text="Past question 1.", days=-30)
        question2 = create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            [question2, question1],
        )

讓我們更詳細(xì)地看下以上這些內(nèi)容。
首先是一個快捷函數(shù) ?create_question?,它封裝了創(chuàng)建投票的流程,減少了重復(fù)代碼。
?test_no_questions方法里沒有創(chuàng)建任何投票,它檢查返回的網(wǎng)頁上有沒有 "No polls are available." 這段消息和 ?latest_question_list ?是否為空。注意到 ?django.test.TestCase? 類提供了一些額外的 ?assertion ?方法,在這個例子中,我們使用了 ?assertContains()? 和 ?assertQuerysetEqual() ?。

在 ?test_past_question ?方法中,我們創(chuàng)建了一個投票并檢查它是否出現(xiàn)在列表中。
在 ?test_future_question ?中,我們創(chuàng)建 ?pub_date ?在未來某天的投票。數(shù)據(jù)庫會在每次調(diào)用測試方法前被重置,所以第一個投票已經(jīng)沒了,所以主頁中應(yīng)該沒有任何投票。
剩下的那些也都差不多。實際上,測試就是假裝一些管理員的輸入,然后通過用戶端的表現(xiàn)是否符合預(yù)期來判斷新加入的改變是否破壞了原有的系統(tǒng)狀態(tài)。

測試 DetailView

我們的工作似乎已經(jīng)很完美了?不,還有一個問題:就算在發(fā)布日期時未來的那些投票不會在目錄頁 index 里出現(xiàn),但是如果用戶知道或者猜到正確的 URL ,還是可以訪問到它們。所以我們得在 ?DetailView里增加一些約束:

class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

然后,我們應(yīng)該增加一些測試來檢驗 ?pub_date在過去的 ?Question能夠被顯示出來,而 ?pub_date在未來的則不可以:

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        future_question = create_question(question_text='Future question.', days=5)
        url = reverse('polls:detail', args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        past_question = create_question(question_text='Past Question.', days=-5)
        url = reverse('polls:detail', args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)

更多的測試思路

我們應(yīng)該給 ?ResultsView ?也增加一個類似的 ?get_queryset方法,并且為它創(chuàng)建測試。這和我們之前干的差不多,事實上,基本就是重復(fù)一遍。
我們還可以從各個方面改進(jìn)投票應(yīng)用,但是測試會一直伴隨我們。比方說,在目錄頁上顯示一個沒有選項 ?Choices的投票問題就沒什么意義。我們可以檢查并排除這樣的投票題。測試可以創(chuàng)建一個沒有選項的投票,然后檢查它是否被顯示在目錄上。當(dāng)然也要創(chuàng)建一個有選項的投票,然后確認(rèn)它確實被顯示了。
恩,也許你想讓管理員能在目錄上看見未被發(fā)布的那些投票,但是普通用戶看不到。不管怎么說,如果你想要增加一個新功能,那么同時一定要為它編寫測試。不過你是先寫代碼還是先寫測試那就隨你了。
在未來的某個時刻,你一定會去查看測試代碼,然后開始懷疑:「這么多的測試不會使代碼越來越復(fù)雜嗎?」。別著急,我們馬上就會談到這一點。

當(dāng)需要測試的時候,測試用例越多越好

貌似我們的測試多的快要失去控制了。按照這樣發(fā)展下去,測試代碼就要變得比應(yīng)用的實際代碼還要多了。而且測試代碼大多都是重復(fù)且不優(yōu)雅的,特別是在和業(yè)務(wù)代碼比起來的時候,這種感覺更加明顯。
但是這沒關(guān)系! 就讓測試代碼繼續(xù)肆意增長吧。大部分情況下,你寫完一個測試之后就可以忘掉它了。在你繼續(xù)開發(fā)的過程中,它會一直默默無聞地為你做貢獻(xiàn)的。
但有時測試也需要更新。想象一下如果我們修改了視圖,只顯示有選項的那些投票,那么只前寫的很多測試就都會失敗。但這也明確地告訴了我們哪些測試需要被更新,所以測試也會測試自己。
最壞的情況是,當(dāng)你繼續(xù)開發(fā)的時候,發(fā)現(xiàn)之前的一些測試現(xiàn)在看來是多余的。但是這也不是什么問題,多做些測試也不錯。
如果你對測試有個整體規(guī)劃,那么它們就幾乎不會變得混亂。下面有幾條好的建議:

  • 對于每個模型和視圖都建立單獨的 ?TestClass?
  • 每個測試方法只測試一個功能
  • 給每個測試方法起個能描述其功能的名字

深入代碼測試

在本教程中,我們僅僅是了解了測試的基礎(chǔ)知識。你能做的還有很多,而且世界上有很多有用的工具來幫你完成這些有意義的事。
舉個例子,在上述的測試中,我們已經(jīng)從代碼邏輯和視圖響應(yīng)的角度檢查了應(yīng)用的輸出,現(xiàn)在你可以從一個更加 "in-browser" 的角度來檢查最終渲染出的 HTML 是否符合預(yù)期,使用 Selenium 可以很輕松的完成這件事。這個工具不僅可以測試 Django 框架里的代碼,還可以檢查其他部分,比如說你的 JavaScript。它假裝成是一個正在和你站點進(jìn)行交互的瀏覽器,就好像有個真人在訪問網(wǎng)站一樣!Django 它提供了 ?LiveServerTestCase來和 Selenium 這樣的工具進(jìn)行交互。
如果你在開發(fā)一個很復(fù)雜的應(yīng)用的話,你也許想在每次提交代碼時自動運行測試,也就是我們所說的持續(xù)集成 continuous integration ,這樣就能實現(xiàn)質(zhì)量控制的自動化,起碼是部分自動化。
一個找出代碼中未被測試部分的方法是檢查代碼覆蓋率。它有助于找出代碼中的薄弱部分和無用部分。如果你無法測試一段代碼,通常說明這段代碼需要被重構(gòu)或者刪除。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號