1. 程式人生 > 實用技巧 >用 Jest 進行單元測試

用 Jest 進行單元測試

1.模組介紹

1.1 模組的概念

模組是什麼 ?

模組就是一系列功能(函式)的集合體

模組的分類

  • 內建模組 c語言寫的模組
  • 第三方模組 別人寫好的模組
  • 自定義模組 自己自定義的

一個python檔案本身就是一個模組 , 檔名m.py , 模組名叫m

ps:模組分為4中形式
    1.使用python編寫的.py檔案
    2.已被編譯為共享庫或DLL的C或C++擴充套件
    3.把一系列模組組織到一起的資料夾(注:資料夾下有一個__init__.py檔案,該資料夾稱之為包
    4.使用C編寫並連結到python直譯器的內建模組

1.2 為什麼要用模組

  1. 內建與第三方的模組拿來就用,無需定義,這種拿來主義,可以極大的提升自己的開發效率
  2. 可以將程式的各部分功能提取出來放到一模組中為大家共享使用
  3. 減少了程式碼冗餘,程式組織結構更加清晰

2. 模組的兩種匯入方式

2.1 import

[建立模組]

# m.py
print('======m=====')
x = 100
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x

[匯入模組]

# demo.py
import m     # 匯入m模組

x = 1
y = 2

# ======m=====  
# 列印l ======m===== ,說明匯入模組,會執行模組裡面的程式碼

[思考] 首次模組會發生什麼事情

1.產生m.py的名稱空間
2.執行m.py程式碼,將m.py執行過程中產生的名字都丟到m的名稱空間
3.在當前檔案中產生一個名字m,指向1中產生的名稱空間

[之後匯入]

之後的匯入,都是直接引用第一次匯入產生的m.py名稱空間,不會重複執行程式碼

[深入理解]

# demo.py
import m

print(m.x)            # 100
print(m.get)          # <function get at 0x000001FD80567620>
print(m.change)       # <function change at 0x000001FD81616510>

[強調]

  1. 模組名.名字,是指名道姓的問某一個模組要名字對應的值,不會與當前名稱空間的名字發生衝突
  2. 無論是檢視還是修改操作的都是以定義階段為準(原模組本身),與呼叫位置無關
# demo.py
import m

x = 333
m.get()     # get ======== 100

m.change()  # 改變的是m名稱空間裡面的x值
print(x)    # 333

print(m.x)  # 101

[補充]

# 1.可以以逗號為分隔符在一行匯入多個模組
# 建議如下所示匯入多個模組,層次清晰
import time
import random
import m

# 不建議在一行同時匯入多個模組
import time, random, m

# 2.匯入模組的規範

# 1.python內建模組
# 2.第三方模組
# 3.程式設計師自定義模組

import time
import sys
import 第三方模組1
import 第三方模組2
import 自定義模組1
import 自定義模組1

# 3.import ... as ...
# 為匯入的模組起一個別名,以後可以通過別名.名字,取到模組裡面的名字
# 適用於比較長的第三方模組

import jhcsdfveubvf as f

# 4.模組是第一類物件,和變數名,函式名一樣,可以被賦值,可以當引數,返回值,容器的元素

# 5.自定義模組的命名應該採用純小寫+下劃線風格,py2中還是駝峰體

# 6.可以在函式中匯入模組

2.2 from ... import ....

# foo.py
print('======foo=====')
x = 0
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x
# run.py

from foo import X          # x=模組foo中值0的記憶體地址
from foo import get
from foo import change

print(x)                   # 0
print(get)                 # <function get at 0x0000029FBD8E7620>
print(change)              # <function change at 0x0000029FBE996510>

# 注意:x,get,change這三個名字是在run的名稱空間中,但是指向的都是foo裡面值的記憶體地址,並不是名字
# 只是他們的名字相同又指向相同的記憶體地址,但是不在一個名稱空間,所以不會相互影響

[匯入模組發生的事]

# 1、產一個模組的名稱空間
# 2、執行foo.py將執行過程中產生的名字都丟到模組的名稱空間去
# 3、在當前名稱空間拿到一個名字,該名字指向模組名稱空間中的某一個記憶體地址

[強調]

# run.py

from foo import X          # x=模組foo中值0的記憶體地址
from foo import get
from foo import change

print(x)                   # 0
print(get)                 # <function get at 0x0000029FBD8E7620>
print(change)              # <function change at 0x0000029FBE996510>

x = 3333       # 改變全域性名稱空間中x的指向,現在x指向值3333的記憶體地址
print(x)       # 3333

get()          # 仍然列印x=0,因為get中的x是引用foo名稱空間中x指向的值,foo中x的指向並沒有變
change()       # 改變foo中x的記憶體地址的指向
get()          # 列印1

print(x)       # 雖然foo中x的指向傳送改變了,但是run中的這個x並不會發生變化,還是原來的記憶體地址0

from foo import x   # 這個時候x就指向,foo裡面x指向的新地址了
print(x)            # 列印 1

[補充]

#一行匯入多個名字(不推薦)
from foo import X, get,change

# *:匯入模組中的所有名字,當用到一個模組中超級多的名字時,可以用*,但是最好也要規避掉重名的問題
from foo import *|
print(x)

# 起別名
from foo import get as g    # 針對get起了一個別名g

[瞭解]

# 瞭解 : __all__ = ["名字1","名字2",.....],列表的元素全是字串格式的名字
# 匯入*,實際上就是把列表中的名字全匯入進入了,可以通過__all__控制*代表的名字有哪些

2.3 兩種方式的比較

impot匯入模組在使用時必須加字首"模組."
優點:肯定不會與當前名稱空間中的名字衝突
缺點:加字首顯得麻煩

from.. . impat.. .匯入模組在使用時不用加字首
優點:程式碼更精簡
缺點:容易與當前名稱空間混淆

3. 迴圈匯入

極力不推薦在程式中設計有模組的迴圈匯入,但是如果真的遇到,記住兩種解決方式

m1.py

print("正在匯入m1")

from m2 import y

x = 1

m2.py

print("正在匯入m2")

from m1 import x

y = 2

run.py

import m1


# 執行程式碼,首先會載入m1的名稱空間,然後執行裡面的程式碼,當執行到第二句的時候,此時m1的名稱空間中還沒有x這個名字,由於是首次匯入會載入m2的名稱空間,執行m2的程式碼,所以控制檯會列印正在匯入m1,正在匯入m2,當運行當m2的第二句程式碼時,由於m1不是首次匯入,所以不會再載入名稱空間,也不會執行程式碼,但是m2問m1要x這個名字,由於m1中的x這個名字還沒有載入到名稱空間,所以程式報錯

[解決方法]

# 方式1:
# 將m1,和m2中的from m2 import y,from m1 import x,放在檔案的最後一行,確保匯入的時候,名字已經完全載入到名稱空間了

# 方式2:
# 將模組匯入的程式碼,放在函式體中,這樣保證在執行程式碼的時候,函式未呼叫函式體裡面的程式碼不執行

4. 搜尋模組路徑的優先順序

無論是 import還是from ...import 在匯入模組時都涉及到查詢問題

[優先順序]

  1. 記憶體 (內建模組,預設會自動載入到記憶體中)
  2. 硬碟 : 按照sys.path(環境變數)中存放的檔案的順序依次查詢要匯入的模組
import sys
print(sys.path)

['E:\\project\\python\\s29code\\day21', 'E:\\project\\python\\s29code', 'E:\\developTools\\pycharm\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_display', 'E:\\developTools\\Anaconda3\\python36.zip', 'E:\\developTools\\Anaconda3\\DLLs', 'E:\\developTools\\Anaconda3\\lib', 'E:\\developTools\\Anaconda3', 'E:\\developTools\\Anaconda3\\lib\\site-packages', 'E:\\developTools\\Anaconda3\\lib\\site-packages\\win32', 'E:\\developTools\\Anaconda3\\lib\\site-packages\\win32\\lib', 'E:\\developTools\\Anaconda3\\lib\\site-packages\\Pythonwin', 'E:\\developTools\\pycharm\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend']

# 其中第一個資料夾是當前執行檔案所在的資料夾  E:\\project\\python\\s29code\\day21
# 其中第二個資料夾是當前專案的資料夾  E:\\project\\python\\s29code
# 這個值是pycharm自動新增的,所以當他不存在,值裡面的壓縮包就當資料夾看待

[補充]

通過sys.modules檢視已將載入到記憶體的模組,內容格式 {'模組名':'記憶體地址'}

print('foo' in sys.modules)   # 判斷foo模組是否在當前記憶體中
print('m1' in sys.modules)    # 判斷foo模組是否在當前記憶體中

{'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module 'importlib._bootstrap' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module 'importlib._bootstrap_external' (frozen)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'nt': <module 'nt' (built-in)>,...}

[python的優化機制]

匯入模組會發生模組名和對應的記憶體地址的捆綁關係,但是你通過del 模組名
是無法從記憶體中把那塊空間回收的,因為python匯入一個模組是較耗費記憶體的
如果你中途從記憶體中刪除了,再匯入就會再開闢記憶體空間,極大的消耗記憶體
同樣你在函式內部中匯入模組,函式呼叫結束後,匯入的模組在記憶體中也不會回收
只有噹噹前執行檔案結束,無其他檔案對模組中的名字有引用,模組才會從記憶體中消失

5. 模組規範寫法

我們在編寫py檔案時,需要時刻提醒自己,該檔案既是給自己用的,也有可能會被其他人使用,

因而程式碼的可讀性與易維護性顯得十分重要,為此我們在編寫一個模組時最好按照統一的規範去編寫,如下

"The module is used to..."  # 模組的文件描述

import sys  # 匯入模組

x = 1  # 定義全域性變數,如果非必須,則最好使用區域性變數,這樣可以提高程式碼的易維護性,並且可以節省記憶體提高效能


class Foo:  # 定義類,並寫好類的註釋
    'Class Foo is used to...'
    pass


def test():  # 定義函式,並寫好函式的註釋
    'Function test is used to…'
    pass


if __name__ == '__main__':  # 主程式
    test()  # 在被當做指令碼執行時,執行此處的程式碼

6. py檔案的用途

6.1 當做模組匯入

# foo.py
print('======m=====')
x = 0
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x
# run.py
import foo      # 首次匯入模組,執行程式碼
#print(foo)

print(foo.x)    # 0
foo.change()    # 把x指向的記憶體地址改變了,指向了1
print(foo.x)    # 1

x = 1
y = 2

print('11111')
print('22222')

# 名稱空間的回收順序
首次匯入foo模組,foo程式碼會執行,但是程式碼執行結束後它的名稱空間沒有被回收,因為裡面的名字被run.py引用了,緊接著run.py中的程式碼執行完畢後,它的名稱空間裡面的名字沒有被其他引用,執行完後,run.py的名稱空間就被回收了,foo的名稱空間也被回收了

# 總結: 模組沒有被其他檔案引用就被回收了

5.2 當做程式執行

如果一個模組在開發測試的時候,肯定會要執行,但是如果被別人匯入的時候,不想執行裡面的函式怎麼辦呢?

# foo.py
print('======m=====')
x = 100
def get():
    print("get ========%s" % x)


def change():
    global x
    x += 1
    return x

if __name__ == '__main__':
    # 當作程式執行的時候做的事情
    get()
    change()
else:
    # 被當做模組匯入的時候做的事情
    pass
# 每一個py檔案內部都有一個__name__變數,
# 當檔案當做程式執行的時候,__name__ == __main__
# 當檔案當做模組匯入時,__name__ == 模組名,即去掉字尾的檔名