Tests unitaires pour les notebooks Databricks
Vous pouvez utiliser les *tests unitaires* pour améliorer la qualité et la cohérence du code de vos notebooks. Les tests unitaires sont une approche pour tester des unités de code autonomes, telles que des fonctions, dès le début et fréquemment. Cela vous aide à trouver plus rapidement les problèmes dans votre code, à découvrir plus tôt les hypothèses erronées concernant votre code et à rationaliser vos efforts de codage globaux.
Cet article est une introduction aux tests unitaires de base avec des fonctions. Les concepts avancés tels que les classes et interfaces de test unitaire, ainsi que l'utilisation de stubs, de mocks et de harnais de test, bien que également pris en charge lors des tests unitaires pour les notebooks, dépassent le cadre de cette page. Cet article ne couvre pas non plus d'autres types de méthodes de test, comme les tests d'intégration, les tests système, les tests d'acceptation ou les tests non fonctionnels, tels que les tests de performance ou les tests d'ergonomie.
Cet article démontre ce qui suit :
- Comment organiser les fonctions et leurs tests unitaires.
- Comment écrire des fonctions en Python, R, Scala, ainsi que des fonctions définies par l’utilisateur en SQL, qui sont bien conçues pour être testées unitairement.
- Comment appeler ces fonctions à partir de Notebooks Python, R, Scala et SQL.
- Comment écrire des tests unitaires en Python, R et Scala en utilisant les frameworks de test populaires pytest pour Python, testthat pour R et ScalaTest pour Scala. Également comment écrire du SQL qui teste unitairement les fonctions SQL définies par l'utilisateur (SQL UDFs).
- Comment exécuter ces tests unitaires à partir de Notebooks Python, R, Scala et SQL.
Databricks recommande d'écrire et d'exécuter vos tests unitaires dans un Notebook. Alors que vous pouvez exécuter certaines commandes dans le terminal web, le terminal web présente plus de limitations, telles qu'un manque de prise en charge pour Spark. Consultez Exécuter des commandes Shell dans le terminal web Databricks.
Organiser les fonctions et les tests unitaires
Il existe plusieurs approches courantes pour organiser vos fonctions et leurs tests unitaires avec des Notebooks. Chaque approche présente ses avantages et ses défis.
Pour les notebooks Python, R et Scala, les approches courantes incluent les suivantes :
-
Stockez les fonctions et leurs tests unitaires en dehors des Notebooks..
- Avantages : Vous pouvez appeler ces fonctions avec des Notebooks et en dehors. Les frameworks de test sont mieux conçus pour exécuter des tests en dehors des Notebooks. Databricks fournit une suite d'outils pour découvrir, exécuter et suivre les tests unitaires Python directement dans le workspace. Consultez les tests unitaires Python dans le Workspace.
- Défis : Cette approche n'est pas prise en charge pour les notebooks Scala. Cette approche augmente également le nombre de fichiers à suivre et à maintenir.
-
Stockez les fonctions dans un Notebook et leurs tests unitaires dans un Notebook séparé..
- Avantages : ces fonctions sont plus faciles à réutiliser dans tous les Notebooks.
- Défis : Le nombre de notebooks à suivre et à maintenir augmente. Ces fonctions ne peuvent pas être utilisées en dehors des notebooks. Ces fonctions peuvent également être plus difficiles à tester en dehors des Notebook.
-
Stockez les fonctions et leurs tests unitaires dans le même notebook..
- Avantages : les fonctions et leurs tests unitaires sont stockés dans un seul Notebook pour faciliter le suivi et la maintenance.
- Défis : ces fonctions peuvent être plus difficiles à réutiliser entre les Notebooks. Ces fonctions ne peuvent pas être utilisées en dehors des notebooks. Ces fonctions peuvent également être plus difficiles à tester en dehors des Notebook.
Pour les notebooks Python et R, Databricks recommande de stocker les fonctions et leurs tests unitaires en dehors des notebooks. Pour les notebooks Scala, Databricks recommande d'inclure les fonctions dans un notebook et leurs tests unitaires dans un notebook distinct.
Pour les Notebooks SQL, Databricks vous recommande de stocker les fonctions en tant que fonctions définies par l'utilisateur SQL (UDF SQL) dans vos schémas (également appelés bases de données). Vous pouvez ensuite appeler ces UDF SQL et leurs tests unitaires à partir de Notebooks SQL.
Fonctions d'écriture
Cette section décrit un ensemble simple d'exemples de fonctions qui déterminent les éléments suivants :
- Indique si une table existe dans une base de données.
- Indique si une colonne existe dans une table.
- Combien de lignes existent dans une colonne pour une valeur à l'intérieur de cette colonne.
Ces fonctions sont conçues pour être simples, afin que vous puissiez vous concentrer sur les détails des tests unitaires de cette page plutôt que sur les fonctions elles-mêmes.
Pour obtenir les meilleurs résultats de tests unitaires, une fonction doit renvoyer un résultat unique et prévisible et être d'un seul type de données. Par exemple, pour vérifier si quelque chose existe, la fonction doit renvoyer une valeur booléenne de vrai ou faux. Pour renvoyer le nombre de lignes existantes, la fonction doit renvoyer un nombre entier non négatif. Il ne doit pas, dans le premier exemple, renvoyer faux si quelque chose n'existe pas, ni la chose elle-même si elle existe. De même, pour le second exemple, il ne doit pas renvoyer le nombre de lignes existantes, ni faux si aucune ligne n'existe.
Vous pouvez ajouter ces fonctions à un Workspace Databricks existant comme suit, en Python, R, Scala ou SQL.
- Python
- R
- Scala
- SQL
Le code suivant suppose que vous avez configuré l'intégration Git pour les dossiers Git, ajouté un référentiel, et que le référentiel est ouvert dans votre workspace Databricks.
Créez un fichier nommé myfunctions.py dans le dépôt, et ajoutez le contenu suivant au fichier. D'autres exemples sur cette page s'attendent à ce que ce fichier soit nommé myfunctions.py. Vous pouvez utiliser différents noms pour vos propres fichiers.
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()
Le code suivant suppose que vous avez configuré l'intégration Git pour les dossiers Git, ajouté un référentiel, et que le référentiel est ouvert dans votre workspace Databricks.
Créez un fichier nommé myfunctions.r dans le dépôt, et ajoutez le contenu suivant au fichier. D'autres exemples sur cette page s'attendent à ce que ce fichier soit nommé myfunctions.r. Vous pouvez utiliser différents noms pour vos propres fichiers.
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)
}
Créez un notebook Scala nommé myfunctions avec les contenus suivants. D'autres exemples de cette page attendent que ce notebook soit nommé myfunctions. Vous pouvez utiliser des noms différents pour vos propres Notebooks.
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()
}
Le code suivant suppose que vous disposez du dataset d'exemple tiers diamonds dans un schéma nommé default au sein d'un catalogue nommé main qui est accessible depuis votre workspace Databricks. Si le catalogue ou le schéma que vous souhaitez utiliser a un nom différent, alors modifiez une ou les deux instructions USE suivantes pour qu'elles correspondent.
Créez un notebook SQL et ajoutez le contenu suivant à ce nouveau notebook. Ensuite, attachez le notebook à un cluster et exécutez le notebook pour ajouter les UDF SQL suivantes au catalogue et au schéma spécifiés.
Les UDF SQL table_exists et column_exists ne fonctionnent qu'avec Unity Catalog. La prise en charge des SQL UDF pour Unity Catalog est en aperçu public.
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
Appeler des fonctions
Cette section décrit le code qui appelle les fonctions précédentes. Vous pourriez utiliser ces fonctions, par exemple, pour compter le nombre de lignes dans une table où une valeur spécifiée existe dans une colonne spécifiée. Cependant, vous voudriez vérifier si la table existe réellement, et si la colonne existe réellement dans cette table, avant de continuer. Le code suivant vérifie ces conditions.
Si vous avez ajouté les fonctions de la section précédente à votre workspace Databricks, vous pouvez appeler ces fonctions depuis votre workspace comme suit.
- Python
- R
- Scala
- SQL
Créez un Notebook Python dans le même dossier que le fichier myfunctions.py précédent de votre référentiel, et ajoutez le contenu suivant au Notebook. Modifiez les valeurs des variables pour le nom de la table, le nom du schéma (base de données), le nom de la colonne et la valeur de la colonne selon les besoins. Ensuite, associez le Notebook à un cluster et exécutez le Notebook pour voir les résultats.
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}'.")
Créez un notebook R dans le même dossier que le fichier myfunctions.r précédent de votre dépôt, et ajoutez le contenu suivant au notebook. Modifiez les valeurs des variables pour le nom de la table, le nom du schéma (base de données), le nom de la colonne et la valeur de la colonne selon les besoins. Ensuite, associez le Notebook à un cluster et exécutez le Notebook pour voir les résultats.
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 = ""))
}
Créez un autre Notebook Scala dans le même dossier que le Notebook Scala précédent myfunctions, et ajoutez le contenu suivant à ce nouveau Notebook.
Dans la première cellule de ce nouveau Notebook, ajoutez le code suivant, qui appelle le magic %run. Ce magic rend le contenu du Notebook myfunctions disponible pour votre nouveau Notebook.
%run ./myfunctions
Dans la deuxième cellule de ce nouveau Notebook, ajoutez le code suivant. Modifiez les valeurs des variables pour le nom de la table, le nom du schéma (base de données), le nom de la colonne et la valeur de la colonne, selon les besoins. Ensuite, associez le Notebook à un cluster et exécutez le Notebook pour voir les résultats.
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 + "'.")
}
Ajoutez le code suivant à une nouvelle cellule dans le notebook précédent ou à une cellule dans un notebook distinct. Modifiez les noms de schéma ou de catalogue si nécessaire pour qu'ils correspondent aux vôtres, puis exécutez cette cellule pour afficher les résultats.
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
Écrire des tests unitaires
Cette section décrit le code qui teste chacune des fonctions décrites au début de cette page. Si vous apportez des modifications aux fonctions à l’avenir, vous pouvez utiliser les tests unitaires pour déterminer si ces fonctions fonctionnent toujours comme vous vous y attendez.
Si vous avez ajouté les fonctions vers le début de cette page à votre workspace Databricks, vous pouvez y ajouter des tests unitaires comme suit.
- Python
- R
- Scala
- SQL
Créez un autre fichier nommé test_myfunctions.py dans le même dossier que le fichier myfunctions.py précédent de votre repo, et ajoutez le contenu suivant au fichier. Par défaut, pytest recherche .py fichiers dont les noms commencent par test_ (ou se terminent par _test) pour les tester. De même, par défaut, pytest examine ces fichiers à la recherche de fonctions dont les noms start par test_ pour les tester.
En général, il est préférable de *ne pas* exécuter de tests unitaires sur des fonctions qui traitent des données en production. Ceci est particulièrement important pour les fonctions qui ajoutent, suppriment ou modifient de toute autre manière les données. Pour protéger vos données de production contre toute altération inattendue par vos tests unitaires, vous devez exécuter des tests unitaires sur des données non-production. Une approche courante consiste à créer des données fictives aussi proches que possible des données de production. L'exemple de code suivant crée des données fictives pour l'exécution des tests unitaires.
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
Créez un autre fichier nommé test_myfunctions.r dans le même dossier que le fichier myfunctions.r précédent de votre repo, et ajoutez le contenu suivant au fichier. Par default, testthat recherche les fichiers .r dont les noms start par test à tester.
En général, il est préférable de *ne pas* exécuter de tests unitaires sur des fonctions qui traitent des données en production. Ceci est particulièrement important pour les fonctions qui ajoutent, suppriment ou modifient de toute autre manière les données. Pour protéger vos données de production contre toute altération inattendue par vos tests unitaires, vous devez exécuter des tests unitaires sur des données non-production. Une approche courante consiste à créer des données fictives aussi proches que possible des données de production. L'exemple de code suivant crée des données fictives pour l'exécution des tests unitaires.
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)
})
Créez un autre Notebook Scala dans le même dossier que le Notebook Scala précédent myfunctions, et ajoutez le contenu suivant à ce nouveau Notebook.
Dans la première cellule du nouveau Notebook, ajoutez le code suivant, qui appelle le magic %run. Cette commande magique rend le contenu du notebook myfunctions disponible dans votre nouveau notebook.
%run ./myfunctions
Dans la deuxième cellule, ajoutez le code suivant. Ce code définit vos tests unitaires et spécifie comment les exécuter.
En général, il est préférable de *ne pas* exécuter de tests unitaires sur des fonctions qui traitent des données en production. Ceci est particulièrement important pour les fonctions qui ajoutent, suppriment ou modifient de toute autre manière les données. Pour protéger vos données de production contre toute altération inattendue par vos tests unitaires, vous devez exécuter des tests unitaires sur des données non-production. Une approche courante consiste à créer des données fictives aussi proches que possible des données de production. L'exemple de code suivant crée des données fictives pour l'exécution des tests unitaires.
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)
Cet exemple de code utilise le style de test FunSuite dans ScalaTest. Pour les autres styles de test disponibles, consultez Sélection de styles de test pour votre projet.
Avant d'ajouter des tests unitaires, vous devez savoir qu'en général, il est préférable de ne pas exécuter de tests unitaires sur des fonctions qui traitent des données en production. Ceci est particulièrement important pour les fonctions qui ajoutent, suppriment ou modifient de toute autre manière les données. Pour protéger vos données de production contre toute altération inattendue par vos tests unitaires, vous devez exécuter des tests unitaires sur des données non-production. Une approche courante consiste à exécuter des tests unitaires sur des vues plutôt que sur des tables.
Pour créer une vue, vous pouvez appeler la commande CREATE VIEW à partir d'une nouvelle cellule dans le Notebook précédent ou un Notebook distinct. L'exemple suivant suppose que vous disposez d'une table existante nommée diamonds dans un schéma (base de données) nommé default dans un catalogue nommé main. Modifiez ces noms pour qu'ils correspondent aux vôtres si nécessaire, puis exécutez uniquement cette cellule.
USE CATALOG main;
USE SCHEMA default;
CREATE VIEW view_diamonds AS
SELECT * FROM diamonds;
Après avoir créé la vue, ajoutez chacune des SELECT instructions suivantes dans sa propre nouvelle cellule dans le notebook précédent ou dans sa propre nouvelle cellule dans un notebook séparé. Modifiez les noms pour qu'ils correspondent aux vôtres, si nécessaire.
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'."));
Exécuter les tests unitaires
Cette section décrit comment exécuter les tests unitaires que vous avez codés dans la section précédente. Lorsque vous exécutez les tests unitaires, vous obtenez des résultats indiquant quels tests unitaires ont réussi et lesquels ont échoué.
Si vous avez ajouté les tests unitaires de la section précédente à votre workspace Databricks, vous pouvez exécuter ces tests unitaires à partir de votre workspace. Vous pouvez exécuter ces tests unitaires soit manuellement, soit selon un calendrier.
- Python
- R
- Scala
- SQL
Créez un notebook Python dans le même dossier que le fichier test_myfunctions.py précédent dans votre repo, et ajoutez le contenu suivant.
Dans la première cellule du nouveau notebook, ajoutez le code suivant, puis exécutez la cellule, qui appelle la commande magique %pip. Cette commande magique installe pytest.
%pip install pytest
Dans la deuxième cellule, ajoutez le code suivant, puis exécutez la cellule. Les résultats indiquent quels tests unitaires ont réussi et ont échoué.
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."
Créez un notebook R dans le même dossier que le fichier test_myfunctions.r précédent dans votre repo, et ajoutez le contenu suivant.
Dans la première cellule, ajoutez le code suivant, puis exécutez la cellule, qui appelle la fonction install.packages. Cette fonction installe testthat.
install.packages("testthat")
Dans la deuxième cellule, ajoutez le code suivant, puis exécutez la cellule. Les résultats indiquent quels tests unitaires ont réussi et ont échoué.
library(testthat)
source("myfunctions.r")
test_dir(".", reporter = "tap")
Exécutez la première puis la deuxième cellule du notebook de la section précédente. Les résultats indiquent quels tests unitaires ont réussi et ont échoué.
Exécutez chacune des trois cellules du Notebook de la section précédente. Les résultats indiquent si chaque test unitaire a réussi ou échoué.
Si vous n'avez plus besoin de la vue après avoir exécuté vos tests unitaires, vous pouvez supprimer la vue. Pour supprimer cette vue, vous pouvez ajouter le code suivant à une nouvelle cellule dans l'un des Notebooks précédents, puis exécuter uniquement cette cellule.
DROP VIEW view_diamonds;
Vous pouvez afficher les résultats de vos exécutions de notebook (y compris les résultats des tests unitaires) dans les logs du Driver de votre cluster. Vous pouvez également spécifier un emplacement pour la livraison des Logs de votre cluster.
Vous pouvez configurer un système de fonctionnalités d’intégration et de livraison continues (CI/CD), tel que GitHub Actions, pour exécuter automatiquement vos tests unitaires chaque fois que votre code change. Pour un exemple, consultez la couverture de GitHub Actions dans Bonnes pratiques d'ingénierie logicielle pour les notebooks Databricks.
Ressources supplémentaires
pytest
- Page d'accueil de pytest.
- Guides pratiques pytest
- Guides de référence pytest
- Meilleures pratiques d'ingénierie logicielle pour les Notebooks Databricks