开始
加载和管理数据
处理数据
政府
引用和资源
2023年8月1日更新
给我们反馈
您可以使用单元测试来帮助改善你的笔记本的代码的质量和一致性。单元测试是一种测试方法独立的单位代码,如功能,尽早并且经常。代码更快,这有助于你发现问题及早发现代码错误的假设,简化你的整体编码工作。
这篇文章是一个基本的介绍单元测试与功能。先进概念,如单元测试类和接口的使用存根,模拟,测试单元测试时,同时也支持笔记本,超出了本文的范围。本文还不包括其他类型的测试方法,如集成测试,系统测试,验收测试,或非功能性测试方法如性能测试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文档
创建函数(SQL和Python)
创建视图