ノートブックの単体テスト
単体テストを使用して、ノートブックのコードの品質と一貫性を向上させることができます。 単体テストは、関数などの自己完結型のコードユニットを早期および頻繁にテストするためのアプローチです。 これにより、コードの問題をより迅速に見つけ出し、コードに関する誤った仮定をより早く明らかにし、全体的なコーディング作業を合理化することができます。
この記事では、関数に関して基本的な単体テストの概要について説明します。 単体テスト クラスやインターフェイスなどの高度な概念、 スタブ、 モック、 テスト ハーネスの使用は、ノートブックの単体テストでもサポートされますが、この記事の範囲外です。 この記事では、統合テスト、システムテスト 、受け入れテスト、パフォーマンス テスト や使いやすさテストなどの非機能的なテスト方法など、他の種類のテスト メソッドについても説明しません。
この記事では、以下の作業のデモンストレーションを行います。
- 関数とその単体テストを整理する方法。
- Python、R、Scala、および SQL のユーザー定義関数で関数を記述する方法。単体テスト用に適切に設計されています。
- Python、R、Scala、SQL ノートブックからこれらの関数を呼び出す方法。
- Python 用の一般的なテスト フレームワーク pytest 、R 用の testthat 、 ScalaTest for Scala を使用して、Python、R、Scala で単体テストを記述する方法。 また、SQL ユーザー定義関数 (SQL UDF) を単体テストする SQL を記述する方法もあります。
- Python、R、Scala、SQL ノートブックからこれらの単体テストを実行する方法。
関数と単体テストを整理する
ノートブックを使用して関数とその単体テストを整理するための一般的な方法がいくつかあります。 それぞれのアプローチに利点と課題があります。
Python、R、Scala ノートブックの場合、一般的なアプローチには次のようなものがあります:
- ノートブックの外部に関数とその単体テストを格納します。
- 利点: ノートブックの内外でこれらの関数を呼び出すことができます。 テスト フレームワークは、ノートブックの外部でテストを実行するように設計されています。
- 課題: このアプローチは Scala ノートブックではサポートされていません。 この方法では、追跡および管理するファイルの数も増やします。
- 関数を 1 つのノートブックに格納し、その単体テストを別のノートブックに格納します。
- 利点: これらの関数は、ノートブック間で再利用する方が簡単です。
- 課題: 追跡および維持するノートブックの数が増加します。 これらの機能は、ノートブック以外では使用できません。 これらの関数は、ノートブックの外部でテストする方が難しい場合もあります。
- 関数とその単体テストを同じノートブック内に格納します。
- 利点: 関数とその単体テストは、追跡とメンテナンスを容易にするために、1 つのノートブック内に格納されます。
- 課題: これらの関数は、ノートブック間で再利用するのが難しい場合があります。 これらの機能は、ノートブック以外では使用できません。 これらの関数は、ノートブックの外部でテストする方が難しい場合もあります。
Python および R ノートブックの場合、Databricks では、関数とその単体テストをノートブックの外部に格納することをお勧めします。 Scala ノートブックの場合、Databricks では、関数を 1 つのノートブックに含め、その単体テストを別のノートブックに含めることをお勧めします。
SQL ノートブックの場合、Databricks では、関数を SQL ユーザー定義関数 (SQL UDF) としてスキーマ (データベースとも呼ばれます) に格納することをお勧めします。 その後、SQL ノートブックからこれらの SQL UDF とその単体テストを呼び出すことができます。
関数を記述する
このセクションでは、次を決定する関数の例の簡単なセットについて説明します。
- テーブルがデータベースに存在するかどうか。
- 列がデータベースに存在するかどうか。
- その列内の値の列に存在する行数。
これらの関数は、関数自体に焦点を当てるよりアーティクルの単体テストの詳細に焦点を当てることができるように、単純であることを目的としています。
最適な単体テスト結果を取得するには、関数は単一の予測可能な結果を返し、1 つのデータ型である必要があります。 たとえば、何かが存在するかどうかを確認するには、関数は true または false のブール値を返す必要があります。 存在する行数を返すには、負以外の整数を返す必要があります。 最初の例では、何かが存在しない場合は false、存在する場合はモノ自体を返すべきではありません。 同様に、2 番目の例では、存在する行数を返したり、行が存在しない場合は false を返したりしないでください。
これらの関数は、Python、R、Scala、または SQL で、次のように既存の Azure Databricks ワークスペースに追加できます。
Python
次のコードでは、Databricks Git フォルダーを設定し、リポジトリを追加し、Azure Databricks ワークスペースでリポジトリを開いているものとします。
リポジトリ内に名前付きのファイルを作成myfunctions.py
し、次の内容をファイルに追加します。 この記事の他の例では、このファイルに myfunctions.py
の名前が付けられていることが想定されています。 独自のファイルに異なる名前を使用できます。
import pyspark
from pyspark.sql import SparkSession
from pyspark.sql.functions import col
# Because this file is not a Databricks notebook, you
# must create a Spark session. Databricks notebooks
# create a Spark session for you by default.
spark = SparkSession.builder \
.appName('integrity-tests') \
.getOrCreate()
# Does the specified table exist in the specified database?
def tableExists(tableName, dbName):
return spark.catalog.tableExists(f"{dbName}.{tableName}")
# Does the specified column exist in the given DataFrame?
def columnExists(dataFrame, columnName):
if columnName in dataFrame.columns:
return True
else:
return False
# How many rows are there for the specified value in the specified column
# in the given DataFrame?
def numRowsInColumnForValue(dataFrame, columnName, columnValue):
df = dataFrame.filter(col(columnName) == columnValue)
return df.count()
R
次のコードでは、Databricks Git フォルダーを設定し、リポジトリを追加し、Azure Databricks ワークスペースでリポジトリを開いているものとします。
リポジトリ内に名前付きのファイルを作成myfunctions.r
し、次の内容をファイルに追加します。 この記事の他の例では、このファイルに myfunctions.r
の名前が付けられていることが想定されています。 独自のファイルに異なる名前を使用できます。
library(SparkR)
# Does the specified table exist in the specified database?
table_exists <- function(table_name, db_name) {
tableExists(paste(db_name, ".", table_name, sep = ""))
}
# Does the specified column exist in the given DataFrame?
column_exists <- function(dataframe, column_name) {
column_name %in% colnames(dataframe)
}
# How many rows are there for the specified value in the specified column
# in the given DataFrame?
num_rows_in_column_for_value <- function(dataframe, column_name, column_value) {
df = filter(dataframe, dataframe[[column_name]] == column_value)
count(df)
}
Scala
次の内容で という名前の Scala ノートブックmyfunctions
を作成します。 この記事の他の例では、このノートブックに myfunctions
の名前が付けられていることが想定されています。 独自のノートブックに異なる名前を使用できます。
import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions.col
// Does the specified table exist in the specified database?
def tableExists(tableName: String, dbName: String) : Boolean = {
return spark.catalog.tableExists(dbName + "." + tableName)
}
// Does the specified column exist in the given DataFrame?
def columnExists(dataFrame: DataFrame, columnName: String) : Boolean = {
val nameOfColumn = null
for(nameOfColumn <- dataFrame.columns) {
if (nameOfColumn == columnName) {
return true
}
}
return false
}
// How many rows are there for the specified value in the specified column
// in the given DataFrame?
def numRowsInColumnForValue(dataFrame: DataFrame, columnName: String, columnValue: String) : Long = {
val df = dataFrame.filter(col(columnName) === columnValue)
return df.count()
}
SQL
次のコードでは、Azure Databricks ワークスペースからアクセスできるカタログ内で指定されたスキーマ内にサードパーティ製のサンプル データセット default
ダイヤモンドmain
があることを前提としています。 使用するカタログまたはスキーマの名前が異なる場合は、次の USE
ステートメントの一方または両方を一致するように変更します。
SQL ノートブックを作成し、この新しいノートブックに次の内容を追加します。 次に、ノートブックをクラスターに アタッチ し、ノートブックを 実行 して、指定したカタログとスキーマに次の SQL UDF を追加します。
注意
SQL UDF table_exists
およびcolumn_exists
Unity Catalog でのみ動作します。 Unity Catalog に対する SQL UDF のサポートは パブリック プレビュー段階です。
USE CATALOG main;
USE SCHEMA default;
CREATE OR REPLACE FUNCTION table_exists(catalog_name STRING,
db_name STRING,
table_name STRING)
RETURNS BOOLEAN
RETURN if(
(SELECT count(*) FROM system.information_schema.tables
WHERE table_catalog = table_exists.catalog_name
AND table_schema = table_exists.db_name
AND table_name = table_exists.table_name) > 0,
true,
false
);
CREATE OR REPLACE FUNCTION column_exists(catalog_name STRING,
db_name STRING,
table_name STRING,
column_name STRING)
RETURNS BOOLEAN
RETURN if(
(SELECT count(*) FROM system.information_schema.columns
WHERE table_catalog = column_exists.catalog_name
AND table_schema = column_exists.db_name
AND table_name = column_exists.table_name
AND column_name = column_exists.column_name) > 0,
true,
false
);
CREATE OR REPLACE FUNCTION num_rows_for_clarity_in_diamonds(clarity_value STRING)
RETURNS BIGINT
RETURN SELECT count(*)
FROM main.default.diamonds
WHERE clarity = clarity_value
関数の呼び出し
このセクションでは、上記の関数を呼び出すコードについて説明します。 たとえば、これらの関数を使用して、指定された列内に指定された値が存在するテーブル内の行数をカウントできます。 ただし、先に進む前に、テーブルが実際に存在するかどうか、およびそのテーブルに列が実際に存在するかどうかを確認する必要があります。 次のコードは、これらの条件をチェックします。
前のセクションの関数を Azure Databricks ワークスペースに追加した場合は、次のようにワークスペースからこれらの関数を呼び出すことができます。
Python
担当者の前のmyfunctions.py
ファイルと同じフォルダーに Python ノートブックを作成 し、次の内容をノートブックに追加します。 必要に応じて、テーブル名、スキーマ (データベース) 名、列名、および列値の変数値を変更します。 次に、ノートブックをクラスターに アタッチ し、ノートブックを 実行 して結果を表示します。
from myfunctions import *
tableName = "diamonds"
dbName = "default"
columnName = "clarity"
columnValue = "VVS2"
# If the table exists in the specified database...
if tableExists(tableName, dbName):
df = spark.sql(f"SELECT * FROM {dbName}.{tableName}")
# And the specified column exists in that table...
if columnExists(df, columnName):
# Then report the number of rows for the specified value in that column.
numRows = numRowsInColumnForValue(df, columnName, columnValue)
print(f"There are {numRows} rows in '{tableName}' where '{columnName}' equals '{columnValue}'.")
else:
print(f"Column '{columnName}' does not exist in table '{tableName}' in schema (database) '{dbName}'.")
else:
print(f"Table '{tableName}' does not exist in schema (database) '{dbName}'.")
R
担当者の前の ファイルと同じフォルダーに R ノートブックを作成myfunctions.r
し、次の内容をノートブックに追加します。 必要に応じて、テーブル名、スキーマ (データベース) 名、列名、および列値の変数値を変更します。 次に、ノートブックをクラスターに アタッチ し、ノートブックを 実行 して結果を表示します。
library(SparkR)
source("myfunctions.r")
table_name <- "diamonds"
db_name <- "default"
column_name <- "clarity"
column_value <- "VVS2"
# If the table exists in the specified database...
if (table_exists(table_name, db_name)) {
df = sql(paste("SELECT * FROM ", db_name, ".", table_name, sep = ""))
# And the specified column exists in that table...
if (column_exists(df, column_name)) {
# Then report the number of rows for the specified value in that column.
num_rows = num_rows_in_column_for_value(df, column_name, column_value)
print(paste("There are ", num_rows, " rows in table '", table_name, "' where '", column_name, "' equals '", column_value, "'.", sep = ""))
} else {
print(paste("Column '", column_name, "' does not exist in table '", table_name, "' in schema (database) '", db_name, "'.", sep = ""))
}
} else {
print(paste("Table '", table_name, "' does not exist in schema (database) '", db_name, "'.", sep = ""))
}
Scala
前の myfunctions
Scala ノートブックと同じフォルダーに別の Scala ノートブックを作成し、この新しいノートブックに次の内容を追加します。
この新しいノートブックの最初のセルに、 %run マジックを呼び出す次のコードを追加します。 このマジックにより、myfunctions
ノートブックの内容が新しいノートブックで使用できるようになります。
%run ./myfunctions
この新しいノートブックの 2 番目のセルに、次のコードを追加します。 必要に応じて、テーブル名、スキーマ (データベース) 名、列名、および列値の変数値を変更します。 次に、ノートブックをクラスターに アタッチ し、ノートブックを 実行 して結果を表示します。
val tableName = "diamonds"
val dbName = "default"
val columnName = "clarity"
val columnValue = "VVS2"
// If the table exists in the specified database...
if (tableExists(tableName, dbName)) {
val df = spark.sql("SELECT * FROM " + dbName + "." + tableName)
// And the specified column exists in that table...
if (columnExists(df, columnName)) {
// Then report the number of rows for the specified value in that column.
val numRows = numRowsInColumnForValue(df, columnName, columnValue)
println("There are " + numRows + " rows in '" + tableName + "' where '" + columnName + "' equals '" + columnValue + "'.")
} else {
println("Column '" + columnName + "' does not exist in table '" + tableName + "' in database '" + dbName + "'.")
}
} else {
println("Table '" + tableName + "' does not exist in database '" + dbName + "'.")
}
SQL
前のノートブックの新しいセルまたは別のノートブックのセルに、次のコードを追加します。 必要に応じてスキーマまたはカタログ名を変更し、このセルを実行して結果を表示します。
SELECT CASE
-- If the table exists in the specified catalog and schema...
WHEN
table_exists("main", "default", "diamonds")
THEN
-- And the specified column exists in that table...
(SELECT CASE
WHEN
column_exists("main", "default", "diamonds", "clarity")
THEN
-- Then report the number of rows for the specified value in that column.
printf("There are %d rows in table 'main.default.diamonds' where 'clarity' equals 'VVS2'.",
num_rows_for_clarity_in_diamonds("VVS2"))
ELSE
printf("Column 'clarity' does not exist in table 'main.default.diamonds'.")
END)
ELSE
printf("Table 'main.default.diamonds' does not exist.")
END
単体テストを記述する
このセクションでは、この記事の冒頭で説明する各関数をテストするコードについて説明します。 将来関数に変更を加えた場合は、単体テストを使用して、それらの関数が期待どおりに動作するかどうかを判断できます。
この記事の冒頭に関数を Azure Databricks ワークスペースに追加した場合は、次のようにこれらの関数の単体テストをワークスペースに追加できます。
Python
リポジトリ内の前の myfunctions.py
ファイルと同じフォルダーに test_myfunctions.py
という名前の別のファイルを作成し、次の内容をファイルに追加します。 既定では、pytest
は名前が test_
で始まる (または _test
で終わる) .py
ファイルを検索してテストします。 同様に、既定では、pytest
はこれらのファイル内を調べて、名前が test_
で始まる関数をテストします。
一般に、運用環境のデータを操作する関数に対して単体テストを実行 しない ことをお勧めします。 これは、データを追加、削除、または変更する関数では特に重要です。 運用データが予期しない方法で単体テストによって侵害されないようにするには、非運用データに対して単体テストを実行する必要があります。 一般的なアプローチ方法の 1 つは、実稼働データにできるだけ近い偽のデータを作成することです。 次のコード例では、単体テストを実行するための偽のデータを作成します。
import pytest
import pyspark
from myfunctions import *
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, IntegerType, FloatType, StringType
tableName = "diamonds"
dbName = "default"
columnName = "clarity"
columnValue = "SI2"
# Because this file is not a Databricks notebook, you
# must create a Spark session. Databricks notebooks
# create a Spark session for you by default.
spark = SparkSession.builder \
.appName('integrity-tests') \
.getOrCreate()
# Create fake data for the unit tests to run against.
# In general, it is a best practice to not run unit tests
# against functions that work with data in production.
schema = StructType([ \
StructField("_c0", IntegerType(), True), \
StructField("carat", FloatType(), True), \
StructField("cut", StringType(), True), \
StructField("color", StringType(), True), \
StructField("clarity", StringType(), True), \
StructField("depth", FloatType(), True), \
StructField("table", IntegerType(), True), \
StructField("price", IntegerType(), True), \
StructField("x", FloatType(), True), \
StructField("y", FloatType(), True), \
StructField("z", FloatType(), True), \
])
data = [ (1, 0.23, "Ideal", "E", "SI2", 61.5, 55, 326, 3.95, 3.98, 2.43 ), \
(2, 0.21, "Premium", "E", "SI1", 59.8, 61, 326, 3.89, 3.84, 2.31 ) ]
df = spark.createDataFrame(data, schema)
# Does the table exist?
def test_tableExists():
assert tableExists(tableName, dbName) is True
# Does the column exist?
def test_columnExists():
assert columnExists(df, columnName) is True
# Is there at least one row for the value in the specified column?
def test_numRowsInColumnForValue():
assert numRowsInColumnForValue(df, columnName, columnValue) > 0
R
リポジトリ内の前の myfunctions.r
ファイルと同じフォルダーに test_myfunctions.r
という名前の別のファイルを作成し、次の内容をファイルに追加します。 デフォルトでは、testthat
は名前が test
で始まる .r
ファイルを検索してテストします。
一般に、運用環境のデータを操作する関数に対して単体テストを実行 しない ことをお勧めします。 これは、データを追加、削除、または変更する関数では特に重要です。 運用データが予期しない方法で単体テストによって侵害されないようにするには、非運用データに対して単体テストを実行する必要があります。 一般的なアプローチ方法の 1 つは、実稼働データにできるだけ近い偽のデータを作成することです。 次のコード例では、単体テストを実行するための偽のデータを作成します。
library(testthat)
source("myfunctions.r")
table_name <- "diamonds"
db_name <- "default"
column_name <- "clarity"
column_value <- "SI2"
# Create fake data for the unit tests to run against.
# In general, it is a best practice to not run unit tests
# against functions that work with data in production.
schema <- structType(
structField("_c0", "integer"),
structField("carat", "float"),
structField("cut", "string"),
structField("color", "string"),
structField("clarity", "string"),
structField("depth", "float"),
structField("table", "integer"),
structField("price", "integer"),
structField("x", "float"),
structField("y", "float"),
structField("z", "float"))
data <- list(list(as.integer(1), 0.23, "Ideal", "E", "SI2", 61.5, as.integer(55), as.integer(326), 3.95, 3.98, 2.43),
list(as.integer(2), 0.21, "Premium", "E", "SI1", 59.8, as.integer(61), as.integer(326), 3.89, 3.84, 2.31))
df <- createDataFrame(data, schema)
# Does the table exist?
test_that ("The table exists.", {
expect_true(table_exists(table_name, db_name))
})
# Does the column exist?
test_that ("The column exists in the table.", {
expect_true(column_exists(df, column_name))
})
# Is there at least one row for the value in the specified column?
test_that ("There is at least one row in the query result.", {
expect_true(num_rows_in_column_for_value(df, column_name, column_value) > 0)
})
Scala
前の myfunctions
Scala ノートブックと同じフォルダーに別の Scala ノートブックを作成し、この新しいノートブックに次の内容を追加します。
新しいノートブックの最初のセルに、 %run
マジックを呼び出す次のコードを追加します。 このマジックにより、myfunctions
ノートブックの内容が新しいノートブックで使用できるようになります。
%run ./myfunctions
2 番目のセルで、次のコードを追加します。 このコードでは、単体テストを定義し、それらを実行する方法を指定します。
一般に、運用環境のデータを操作する関数に対して単体テストを実行 しない ことをお勧めします。 これは、データを追加、削除、または変更する関数では特に重要です。 運用データが予期しない方法で単体テストによって侵害されないようにするには、非運用データに対して単体テストを実行する必要があります。 一般的なアプローチ方法の 1 つは、実稼働データにできるだけ近い偽のデータを作成することです。 次のコード例では、単体テストを実行するための偽のデータを作成します。
import org.scalatest._
import org.apache.spark.sql.types.{StructType, StructField, IntegerType, FloatType, StringType}
import scala.collection.JavaConverters._
class DataTests extends AsyncFunSuite {
val tableName = "diamonds"
val dbName = "default"
val columnName = "clarity"
val columnValue = "SI2"
// Create fake data for the unit tests to run against.
// In general, it is a best practice to not run unit tests
// against functions that work with data in production.
val schema = StructType(Array(
StructField("_c0", IntegerType),
StructField("carat", FloatType),
StructField("cut", StringType),
StructField("color", StringType),
StructField("clarity", StringType),
StructField("depth", FloatType),
StructField("table", IntegerType),
StructField("price", IntegerType),
StructField("x", FloatType),
StructField("y", FloatType),
StructField("z", FloatType)
))
val data = Seq(
Row(1, 0.23, "Ideal", "E", "SI2", 61.5, 55, 326, 3.95, 3.98, 2.43),
Row(2, 0.21, "Premium", "E", "SI1", 59.8, 61, 326, 3.89, 3.84, 2.31)
).asJava
val df = spark.createDataFrame(data, schema)
// Does the table exist?
test("The table exists") {
assert(tableExists(tableName, dbName) == true)
}
// Does the column exist?
test("The column exists") {
assert(columnExists(df, columnName) == true)
}
// Is there at least one row for the value in the specified column?
test("There is at least one matching row") {
assert(numRowsInColumnForValue(df, columnName, columnValue) > 0)
}
}
nocolor.nodurations.nostacks.stats.run(new DataTests)
注意
このコード例では、ScalaTest でテストの FunSuite
スタイルを使用します。 その他の使用可能なテスト スタイルについては、 「プロジェクトのテスト スタイルの選択」に関するページを参照してください。
SQL
単体テストを追加する前に、一般に、運用環境のデータを操作する関数に対して単体テストを実行 しない ことをお勧めします。 これは、データを追加、削除、または変更する関数では特に重要です。 運用データが予期しない方法で単体テストによって侵害されないようにするには、非運用データに対して単体テストを実行する必要があります。 一般的なアプローチ方法の 1 つは、テーブルではなく ビュー に対して単体テストを実行することです。
ビューを作成するには、前のノートブックまたは別のノートブックの新しいセルから CREATE VIEW コマンドを呼び出します。 次の例では、main
という名前のカタログ内にdefault
という名前のスキーマ (データベース) 内に diamonds
という名前の既存のテーブルがあることを前提としています。 これらの名前を必要に応じて独自の名前と一致するように変更し、そのセルのみを実行します。
USE CATALOG main;
USE SCHEMA default;
CREATE VIEW view_diamonds AS
SELECT * FROM diamonds;
ビューを作成したら、次の SELECT
ステートメントのそれぞれを、前のノートブックの独自の新しいセル、または別のノートブックの独自の新しいセルに追加します。 必要に応じて、自分の名前と一致するように名前を変更します。
SELECT if(table_exists("main", "default", "view_diamonds"),
printf("PASS: The table 'main.default.view_diamonds' exists."),
printf("FAIL: The table 'main.default.view_diamonds' does not exist."));
SELECT if(column_exists("main", "default", "view_diamonds", "clarity"),
printf("PASS: The column 'clarity' exists in the table 'main.default.view_diamonds'."),
printf("FAIL: The column 'clarity' does not exists in the table 'main.default.view_diamonds'."));
SELECT if(num_rows_for_clarity_in_diamonds("VVS2") > 0,
printf("PASS: The table 'main.default.view_diamonds' has at least one row where the column 'clarity' equals 'VVS2'."),
printf("FAIL: The table 'main.default.view_diamonds' does not have at least one row where the column 'clarity' equals 'VVS2'."));
単体テストを実行する
このセクションでは、前のセクションでコーディングした単体テストを実行する方法について説明します。 単体テストを実行すると、合格した単体テストと失敗した単体テストを示す結果が得られます。
前のセクションの単体テストを Azure Databricks ワークスペースに追加した場合は、ワークスペースからこれらの単体テストを実行できます。 これらの単体テストでは、手動で、またはスケジュールに基づいて実行できます。
Python
リポジトリ内の前の test_myfunctions.py
ファイルと同じフォルダーに Python ノートブックを作成し、次の内容を追加します。
新しいノートブックの最初のセルに次のコードを追加し、 %pip
マジックを呼び出す、セルを実行します。 このマジックは pytest
をインストールします。
%pip install pytest
2 番目のセルに次のコードを追加し、セルを実行します。 結果には、合格した単体テストと失敗した単体テストが表示されます。
import pytest
import sys
# Skip writing pyc files on a readonly filesystem.
sys.dont_write_bytecode = True
# Run pytest.
retcode = pytest.main([".", "-v", "-p", "no:cacheprovider"])
# Fail the cell execution if there are any test failures.
assert retcode == 0, "The pytest invocation failed. See the log for details."
R
リポジトリ内の前の test_myfunctions.r
ファイルと同じフォルダーに R ノートブックを作成し、次の内容を追加します。
最初のセルに次のコードを追加し、 install.packages
関数を呼び出す、セルを実行します。 この関数は testthat
をインストールします。
install.packages("testthat")
2 番目のセルに次のコードを追加し、セルを実行します。 結果には、合格した単体テストと失敗した単体テストが表示されます。
library(testthat)
source("myfunctions.r")
test_dir(".", reporter = "tap")
Scala
前のセクションのノートブックで最初のセルと 2 番目のセルを実行します。 結果には、合格した単体テストと失敗した単体テストが表示されます。
SQL
前のセクションのノートブックの 3 つのセルをそれぞれ実行します。 結果は、各単体テストが成功したか失敗したかを示します。
単体テストの実行後にビューが不要になった場合は、ビューを削除できます。 このビューを削除するには、前のいずれかのノートブック内の新しいセルに次のコードを追加し、そのセルのみを実行します。
DROP VIEW view_diamonds;
ヒント
クラスターの ドライバー ログで、ノートブックの実行結果 (単体テストの結果を含む) を表示できます。 クラスターの ログデリバリーの場所を指定することもできます。
継続的インテグレーションと継続的デリバリーまたはデプロイ(CI/CD) システム (GitHub Actions など) を設定して、コードが変更されるたびに単体テストを自動的に実行できます。 例については、ノートブックのソフトウェア エンジニアリングのベスト プラクティスのGitHub Actionsのカバレッジを参照してください。