開始
加載和管理數據
處理數據
政府
引用和資源
更新6月29日,2023年
給我們反饋
您可以使用單元測試來幫助改善你的筆記本的代碼的質量和一致性。單元測試是一種測試方法獨立的單位代碼,如功能,盡早並且經常。代碼更快,這有助於你發現問題及早發現代碼錯誤的假設,簡化你的整體編碼工作。
這篇文章是一個基本的介紹單元測試與功能。先進概念,如單元測試類和接口的使用存根,模擬,測試單元測試時,同時也支持筆記本,超出了本文的範圍。本文還不包括其他類型的測試方法,如集成測試,係統測試,驗收測試,或非功能性測試方法如性能測試orgydF4y2Ba可用性測試。
本文演示了以下幾點:
如何組織功能和他們的單元測試。
如何編寫函數在Python中,R, Scala中,以及在SQL用戶定義函數,設計良好的單元測試。
如何調用這些函數從Python, R, Scala和SQL筆記本。
如何在Python中編寫單元測試,R, Scala使用流行的測試框架pytest對於Python,testthat為R,——scalateScala。還如何編寫SQL,單元測試SQL用戶定義函數(udf) SQL。
如何從Python運行這些單元測試,R, Scala和SQL筆記本。
有一些常見的方法來組織你的函數和他們的單元測試與筆記本。每個方法都有它的好處和挑戰。
對於Python, R, Scala筆記本,常見的方法包括以下幾點:
筆記本電腦以外的存儲功能和他們的單元測試。。
好處:你可以調用這些函數和外部的筆記本。測試框架設計更好地運行測試之外的筆記本。
挑戰:這種方法不支持Scala筆記本。這種方法要求磚回購。這種方法還增加了數量的文件來跟蹤和維護。
存儲函數在一個筆記本和單元測試在一個單獨的筆記本。。
好處:這些功能在筆記本電腦更容易重用。
挑戰:跟蹤和維護筆記本電腦的數量增加。不能使用這些功能以外的筆記本。這些功能也可以測試以外的筆記本更加困難。
存儲函數和他們的單元測試在同一筆記本。。
好處:函數和他們的單元測試是存儲在一個筆記本更容易跟蹤和維護。
挑戰:這些函數可以在筆記本電腦更加難以重用。不能使用這些功能以外的筆記本。這些功能也可以測試以外的筆記本更加困難。
對於Python和R筆記本,磚建議存儲功能和他們的筆記本電腦以外的單元測試。Scala的筆記本,磚建議包括函數在一個筆記本和他們的單元測試在一個單獨的筆記本。
SQL筆記本,磚建議您存儲函數作為SQL用戶定義函數(udf) SQL模式(也稱為數據庫)。然後您可以從SQL調用這些SQL udf和他們的單元測試筆記本。
本節描述一組簡單的函數,確定以下例子:
是否存在一個表在數據庫中。
是否存在一個列在一個表中。
中有多少行中的一個列的值列。
這些函數的目的是簡單的,這樣你就可以專注於本文中的單元測試的細節,而不是關注函數本身。
得到最好的單元測試的結果,一個函數應該返回一個可預測的結果,是一個數據類型。例如,檢查是否存在,函數應該返回一個布爾值的真假。返回的行數,存在,函數應該返回一個非負整數。它不應該在第一個示例中,返回錯誤如果東西不存在或事物本身如果它確實存在。同樣,對於第二個示例,它不應該返回的行數或假如果沒有行存在的存在。
您可以將這些功能添加到現有的數據磚工作區如下,在Python中,R, Scala或SQL。
下麵的代碼假設設置數據磚回購,添加了一個回購,磚的回購開放工作區。
創建一個文件命名myfunctions.py在回購,並將以下內容添加到文件。其他的例子在本文中期望這個文件命名myfunctions.py。您可以使用不同的名稱為自己的文件。
myfunctions.py
進口pyspark從pyspark.sql進口SparkSession從pyspark.sql.functions進口上校#因為這個文件不是一個磚筆記本,你#必須創建一個火花會話。磚的筆記本#默認為您創建一個火花會話。火花=SparkSession。構建器\。瀏覽器名稱(“誠信測試”)\。getOrCreate()#指定數據庫中指定的表存在嗎?deftableExists(的表,dbName):返回火花。目錄。tableExists(f”{dbName}。{的表}”)#在給定DataFrame指定的列存在嗎?defcolumnExists(dataFrame,columnName):如果columnName在dataFrame。列:返回真正的其他的:返回假#有多少行中的指定值指定列在給定DataFrame #嗎?defnumRowsInColumnForValue(dataFrame,columnName,columnValue):df=dataFrame。過濾器(上校(columnName)= =columnValue)返回df。數()
創建一個文件命名myfunctions.r在回購,並將以下內容添加到文件。其他的例子在本文中期望這個文件命名myfunctions.r。您可以使用不同的名稱為自己的文件。
myfunctions.r
圖書館(SparkR)#指定數據庫中指定的表存在嗎?table_exists< -函數(table_name,db_name){tableExists(粘貼(db_name,“。”,table_name,9月=”“))}#在給定DataFrame指定的列存在嗎?column_exists< -函數(dataframe,column_name){column_name%在%colnames(dataframe)}#有多少行中的指定值指定列在給定DataFrame #嗎?num_rows_in_column_for_value< -函數(dataframe,column_name,column_value){df=過濾器(dataframe,dataframe[[column_name]]= =column_value)數(df)}
創建一個Scala的筆記本命名myfunction用下麵的內容。其他的例子在本文中預計這個筆記本myfunction。你可以為你自己的筆記本使用不同的名稱。
myfunction
進口org。apache。火花。sql。DataFrame進口org。apache。火花。sql。功能。上校/ /指定數據庫中指定的表存在嗎?deftableExists(的表:字符串,dbName:字符串):布爾={返回火花。目錄。tableExists(dbName+“。”+的表)}/ /在給定DataFrame指定的列存在嗎?defcolumnExists(dataFrame:DataFrame,columnName:字符串):布爾={瓦爾nameOfColumn=零為(nameOfColumn< -dataFrame。列){如果(nameOfColumn= =columnName){返回真正的}}返回假}/ /有多少行中的指定值指定列/ /在給定DataFrame嗎?defnumRowsInColumnForValue(dataFrame:DataFrame,columnName:字符串,columnValue:字符串):長={瓦爾df=dataFrame。過濾器(上校(columnName)= = =columnValue)返回df。數()}
下麵的代碼假設您具備第三方樣本數據集鑽石在一個模式命名默認的在一個目錄命名主要可以從你磚的工作區。如果你想使用的目錄或模式有不同的名字,然後改變一個或兩個以下使用語句來匹配。
默認的
主要
使用
創建一個SQL的筆記本並將以下內容添加到這個新的筆記本。然後附加集群和筆記本運行筆記本添加以下SQL udf來指定的目錄和模式。
請注意
SQL udftable_exists和column_exists隻有統一編目工作。SQL UDF支持統一目錄公共預覽。
table_exists
column_exists
使用目錄主要;使用模式默認的;創建或取代函數table_exists(catalog_name字符串,db_name字符串,table_name字符串)返回布爾返回如果((選擇數(*)從係統。information_schema。表在哪裏table_catalog=table_exists。catalog_name和table_schema=table_exists。db_name和table_name=table_exists。table_name)>0,真正的,假);創建或取代函數column_exists(catalog_name字符串,db_name字符串,table_name字符串,column_name字符串)返回布爾返回如果((選擇數(*)從係統。information_schema。列在哪裏table_catalog=column_exists。catalog_name和table_schema=column_exists。db_name和table_name=column_exists。table_name和column_name=column_exists。column_name)>0,真正的,假);創建或取代函數num_rows_for_clarity_in_diamonds(clarity_value字符串)返回長整型數字返回選擇數(*)從主要。默認的。鑽石在哪裏清晰=clarity_value
本節描述的代碼調用前麵的函數。例如,您可以使用這些函數計算表中存在一個指定值的行數在specfied列。然而,實際上你想檢查表是否存在,表列是否真的存在,在你繼續。下麵的代碼檢查這些條件。
如果你從前麵部分功能添加到你的磚工作空間,您可以從您的工作空間中調用這些函數如下。
創建一個Python筆記本與前麵相同的文件夾中myfunctions.py文件在你回購,並將以下內容添加到筆記本。改變變量值的表名,模式(數據庫)的名字,列名、列值。然後附加集群和筆記本運行筆記本看結果。
從myfunction進口*的表=“鑽石”dbName=“默認”columnName=“清晰”columnValue=“VVS2”#如果表存在於指定的數據庫……如果tableExists(的表,dbName):df=火花。sql(f“SELECT *{dbName}。{的表}”)#和指定的列存在於表…如果columnExists(df,columnName):#然後報告中的指定值的行數,列。numRows=numRowsInColumnForValue(df,columnName,columnValue)打印(f“有{numRows}行”{的表}“在那裏”{columnName}“=”{columnValue}’。”)其他的:打印(f“列{columnName}“不存在表”{的表}”模式(數據庫)”{dbName}’。”)其他的:打印(f“表{的表}不存在的模式(數據庫)”{dbName}’。”)
創建一個R筆記本與前麵相同的文件夾中myfunctions.r文件在你回購,並將以下內容添加到筆記本。改變變量值的表名,模式(數據庫)的名字,列名、列值。然後附加集群和筆記本運行筆記本看結果。
圖書館(SparkR)源(“myfunctions.r”)table_name< -“鑽石”db_name< -“默認”column_name< -“清晰”column_value< -“VVS2”#如果表存在於指定的數據庫……如果(table_exists(table_name,db_name)){df=sql(粘貼(“SELECT * FROM”,db_name,“。”,table_name,9月=”“))#和指定的列存在於表…如果(column_exists(df,column_name)){#然後報告中的指定值的行數,列。num_rows=num_rows_in_column_for_value(df,column_name,column_value)打印(粘貼(“有”,num_rows,“行表”,table_name,“在哪裏”,column_name,“=”,column_value,”’。”,9月=”“))}其他的{打印(粘貼(“列”,column_name,“不存在表”,table_name,“”模式(數據庫)’”,db_name,”’。”,9月=”“))}}其他的{打印(粘貼(“表”,table_name,“不存在模式(數據庫)’”,db_name,”’。”,9月=”“))}
創建另一個Scala的筆記本與前麵相同的文件夾中myfunctionScala的筆記本,並將以下內容添加到這個新的筆記本。
在這個新的筆記本的第一個細胞,添加以下代碼,調用運行%魔法。這魔法使的內容myfunction你的新筆記本筆記本。
%。/ myfunction
在這個新的筆記本的第二個細胞,添加以下代碼。改變變量值的表名,模式(數據庫)的名字,列名、列值。然後附加集群和筆記本運行筆記本看結果。
瓦爾的表=“鑽石”瓦爾dbName=“默認”瓦爾columnName=“清晰”瓦爾columnValue=“VVS2”/ /如果表存在於指定的數據庫……如果(tableExists(的表,dbName)){瓦爾df=火花。sql(“SELECT * FROM”+dbName+“。”+的表)/ /指定的列存在於表…如果(columnExists(df,columnName)){/ /然後報告中的指定值的行數,列。瓦爾numRows=numRowsInColumnForValue(df,columnName,columnValue)println(“有”+numRows+“行”+的表+“在哪裏”+columnName+“=”+columnValue+”’。”)}其他的{println(“列”+columnName+“不存在表”+的表+“數據庫”+dbName+”’。”)}}其他的{println(“表”+的表+“不存在在數據庫”+dbName+”’。”)}
將下麵的代碼添加一個新的細胞在前麵的筆記本或一個細胞在一個單獨的筆記本。改變模式或如果需要匹配你的目錄名稱,然後運行這個細胞看到結果。
選擇情況下——如果表存在於指定的目錄和模式……當table_exists(“主要”,“默認”,“鑽石”)然後在那張桌子,指定的列存在…(選擇情況下當column_exists(“主要”,“默認”,“鑽石”,“清晰”)然後——然後報告中的指定值的行數,列。printf(“有% d行表“main.default.diamonds”“清晰”=“VVS2”。”,num_rows_for_clarity_in_diamonds(“VVS2”))其他的printf(”專欄“清晰”表中不存在‘main.default.diamonds’。”)結束)其他的printf(“表main.default.diamonds並不存在。”)結束
本節描述代碼,測試每個功能的描述對這篇文章的開始。如果你將來對函數進行任何更改,您可以使用單元測試來確定這些功能仍然像您預期的那樣工作。
如果你添加的功能對本文的開頭磚的工作空間,您可以添加這些功能單元測試工作區如下。
創建另一個文件命名test_myfunctions.py與前麵相同的文件夾中myfunctions.py文件在你回購,並將以下內容添加到文件。默認情況下,pytest查找. py文件的名字test_(或結尾_t)測試。同樣,默認情況下,pytest看起來這些文件的內部函數的名字開始test_進行測試。
test_myfunctions.py
pytest
. py
test_
_t
總的來說,這是一個最佳實踐不運行單元測試與生產函數,處理數據。來說,這是特別重要的功能添加、刪除或修改數據。保護你的生產數據泄露你的單元測試以意想不到的方式,你應該針對非生產數據運行單元測試。一個常見的方法是創建假的數據盡可能的生產數據。下麵的代碼示例創建假數據要運行單元測試。
進口pytest進口pyspark從myfunction進口*從pyspark.sql進口SparkSession從pyspark.sql.types進口StructType,StructField,IntegerType,FloatType,StringType的表=“鑽石”dbName=“默認”columnName=“清晰”columnValue=“SI2”#因為這個文件不是一個磚筆記本,你#必須創建一個火花會話。磚的筆記本#默認為您創建一個火花會話。火花=SparkSession。構建器\。瀏覽器名稱(“誠信測試”)\。getOrCreate()#創建虛假數據的單元測試運行。#一般來說,這是一個最佳實踐不運行單元測試#對生產函數,處理數據。模式=StructType([\StructField(“_c0”,IntegerType(),真正的),\StructField(“克拉”,FloatType(),真正的),\StructField(“切”,StringType(),真正的),\StructField(“顏色”,StringType(),真正的),\StructField(“清晰”,StringType(),真正的),\StructField(“深度”,FloatType(),真正的),\StructField(“表”,IntegerType(),真正的),\StructField(“價格”,IntegerType(),真正的),\StructField(“x”,FloatType(),真正的),\StructField(“y”,FloatType(),真正的),\StructField(“z”,FloatType(),真正的),\])數據=((1,0.23,“理想”,“E”,“SI2”,61.5,55,326年,3.95,3.98,2.43),\(2,0.21,“溢價”,“E”,“SI1”,59.8,61年,326年,3.89,3.84,2.31)]df=火花。createDataFrame(數據,模式)#表存在嗎?deftest_tableExists():斷言tableExists(的表,dbName)是真正的#列存在嗎?deftest_columnExists():斷言columnExists(df,columnName)是真正的#有至少一行指定列中的值嗎?deftest_numRowsInColumnForValue():斷言numRowsInColumnForValue(df,columnName,columnValue)>0
創建另一個文件命名test_myfunctions.r與前麵相同的文件夾中myfunctions.r文件在你回購,並將以下內容添加到文件。默認情況下,testthat查找。r文件的名字測試進行測試。
test_myfunctions.r
testthat
。r
測試
圖書館(testthat)源(“myfunctions.r”)table_name< -“鑽石”db_name< -“默認”column_name< -“清晰”column_value< -“SI2”#創建虛假數據的單元測試運行。#一般來說,這是一個最佳實踐不運行單元測試#對生產函數,處理數據。模式< -structType(structField(“_c0”,“整數”),structField(“克拉”,“浮動”),structField(“切”,“字符串”),structField(“顏色”,“字符串”),structField(“清晰”,“字符串”),structField(“深度”,“浮動”),structField(“表”,“整數”),structField(“價格”,“整數”),structField(“x”,“浮動”),structField(“y”,“浮動”),structField(“z”,“浮動”))數據< -列表(列表(作為。整數(1),0.23,“理想”,“E”,“SI2”,61.5,作為。整數(55),作為。整數(326年),3.95,3.98,2.43),列表(作為。整數(2),0.21,“溢價”,“E”,“SI1”,59.8,作為。整數(61年),作為。整數(326年),3.89,3.84,2.31))df< -createDataFrame(數據,模式)#表存在嗎?test_that(“表存在。”,{expect_true(table_exists(table_name,db_name))})#列存在嗎?test_that(“表中的列的存在。”,{expect_true(column_exists(df,column_name))})#有至少一行指定列中的值嗎?test_that(“至少有一行查詢結果”。,{expect_true(num_rows_in_column_for_value(df,column_name,column_value)>0)})
在新的筆記本的第一個細胞,添加以下代碼,調用運行%魔法。這魔法使的內容myfunction你的新筆記本筆記本。
運行%
在第二單元中,添加以下代碼。這段代碼定義了單元測試和指定如何運行它們。
進口org。——scalate。_進口org。apache。火花。sql。類型。{StructType,StructField,IntegerType,FloatType,StringType}進口scala。集合。JavaConverters。_類DataTests擴展AsyncFunSuite{瓦爾的表=“鑽石”瓦爾dbName=“默認”瓦爾columnName=“清晰”瓦爾columnValue=“SI2”/ /創建虛假數據的單元測試運行。/ /總而言之,這是一個最佳實踐不運行單元測試/ /對生產函數,處理數據。瓦爾模式=StructType(數組(StructField(“_c0”,IntegerType),StructField(“克拉”,FloatType),StructField(“切”,StringType),StructField(“顏色”,StringType),StructField(“清晰”,StringType),StructField(“深度”,FloatType),StructField(“表”,IntegerType),StructField(“價格”,IntegerType),StructField(“x”,FloatType),StructField(“y”,FloatType),StructField(“z”,FloatType)))瓦爾數據=Seq(行(1,0.23,“理想”,“E”,“SI2”,61.5,55,326年,3.95,3.98,2.43),行(2,0.21,“溢價”,“E”,“SI1”,59.8,61年,326年,3.89,3.84,2.31))。asJava瓦爾df=火花。createDataFrame(數據,模式)/ /表存在嗎?測試(“表存在”){斷言(tableExists(的表,dbName)= =真正的)}/ /列存在嗎?測試(“列存在”){斷言(columnExists(df,columnName)= =真正的)}/ /有至少一行指定列中的值嗎?測試(“至少有一個匹配的行){斷言(numRowsInColumnForValue(df,columnName,columnValue)>0)}}nocolor。nodurations。nostacks。統計數據。運行(新DataTests)
這段代碼的例子使用了FunSuite在——scalate風格的測試。其他可用的測試方式,請參閱為您的項目選擇的測試方式。
FunSuite
添加單元測試之前,您應該意識到,這是一個最佳實踐不運行單元測試與生產函數,處理數據。來說,這是特別重要的功能添加、刪除或修改數據。保護你的生產數據泄露你的單元測試以意想不到的方式,你應該針對非生產數據運行單元測試。一個常見的方法是運行單元測試的觀點而不是表。
創建一個視圖中,您可以調用創建視圖命令從一個新的細胞在前麵的筆記本或一個單獨的筆記本。下麵的示例假設您有一個現有表命名鑽石在一個模式(數據庫)命名默認的在一個目錄命名主要。如有需要,修改這些名稱匹配自己的細胞,然後隻運行。
鑽石
使用目錄主要;使用模式默認的;創建視圖view_diamonds作為選擇*從鑽石;
在您創建視圖,添加以下選擇聲明自己的新細胞在前麵的筆記本或自己的新細胞在一個單獨的筆記本。根據需要更改名稱匹配自己的。
選擇
選擇如果(table_exists(“主要”,“默認”,“view_diamonds”),printf(“通過:表main.default.view_diamonds存在。”),printf(“失敗:表main.default.view_diamonds並不存在。”));選擇如果(column_exists(“主要”,“默認”,“view_diamonds”,“清晰”),printf(“通過:表中的列“清晰”存在‘main.default.view_diamonds’。”),printf(“失敗:列“清晰”表中不存在‘main.default.view_diamonds’。”));選擇如果(num_rows_for_clarity_in_diamonds(“VVS2”)>0,printf(“通過:表main.default.view_diamonds至少有一行,列“清晰”=‘VVS2’。”),printf(“失敗:桌上main.default.view_diamonds沒有至少一行,列“清晰”=‘VVS2’。”));
本節描述如何運行單元測試,你編碼的前部分。當你運行單元測試,得到的結果顯示,單元測試通過和失敗。
如果你從前麵添加單元測試部分磚的工作空間,您可以運行這些單元測試從您的工作空間。您可以運行這些單元測試手動orgydF4y2Ba在一個時間表。
在相同的文件夾中創建一個Python筆記本前test_myfunctions.py文件在你回購,並添加以下內容。
在新的筆記本的第一個細胞,添加以下代碼,然後運行單元,它調用%皮普魔法。這個神奇的安裝pytest。
%皮普
% pip安裝pytest
在第二單元中,添加以下代碼,替換< my-repo-name >回購的文件夾名稱,然後運行單元。結果表明,單元測試通過和失敗。
< my-repo-name >
進口pytest進口操作係統進口sysrepo_name=“< my-repo-name >”#這個筆記本的路徑,例如“/工作區/回購/ {username} / {repo-name}”。notebook_path=dbutils。筆記本。entry_point。getDbutils()。筆記本()。getContext()。notebookPath()。得到()#回購的根目錄的名字。repo_root=操作係統。路徑。目錄名(操作係統。路徑。目錄名(notebook_path))#準備運行pytest回購。操作係統。是指(f“/工作區/{repo_root}/{repo_name}”)打印(操作係統。getcwd())#跳過寫佩克一個隻讀的文件係統上的文件。sys。dont_write_bytecode=真正的# pytest運行。retcode=pytest。主要([“。”,“v”,“p”,“沒有:cacheprovider”])#失敗細胞執行是否有測試失敗。斷言retcode= =0,“pytest調用失敗了。有關詳細信息,請參閱日誌”。
在相同的文件夾中創建一個R筆記本前麵test_myfunctions.r文件在你回購,並添加以下內容。
在第一個單元格中,添加以下代碼,然後運行單元,它調用install.packages函數。這個函數安裝testthat。
install.packages
install.packages(“testthat”)
在第二單元中,添加以下代碼,然後運行單元。結果表明,單元測試通過和失敗。
圖書館(testthat)源(“myfunctions.r”)test_dir(“。”,記者=“龍頭”)
在筆記本上運行第一然後第二單元前一節。結果表明,單元測試通過和失敗。
在筆記本上運行三種細胞從前麵的部分。結果顯示每個單元測試是否通過或失敗。
如果你不再需要視圖運行單元測試後,您可以刪除視圖。刪除該視圖中,您可以將以下代碼添加到一個新的細胞前筆記本,然後運行在一個細胞。
下降視圖view_diamonds;
提示
你可以把你的筆記本運行的結果(包括單元測試結果)集群司機日誌。您還可以指定集群的一個位置日誌交付。
你可以建立一個持續集成和持續交付或部署(CI / CD)係統,如GitHub動作,自動運行單元測試代碼更改。例如,看到GitHub的報道行為軟件工程最佳實踐筆記本。
pytest主頁
pytest操作指南
pytest參考指南
軟件工程最佳實踐筆記本
使用dbx Visual Studio代碼
testthat主頁
testthat函數引用
——scalate主頁
——scalate用戶指南
——scalate Scaladoc文檔
創建函數
創建視圖