Delen via


Eenheidstests voor notebooks

U kunt eenheidstests gebruiken om de kwaliteit en consistentie van de code van uw notebooks te verbeteren. Eenheidstests zijn een benadering voor het testen van zelfstandige code-eenheden, zoals functies, vroeg en vaak. Zo kunt u sneller problemen met uw code vinden, foute veronderstellingen over uw code sneller ontdekken en uw algehele code-inspanningen stroomlijnen.

Dit artikel is een inleiding tot basis unittesten met functies. Geavanceerde concepten, zoals klassen en interfaces voor eenheidstests, evenals het gebruik van stubs, mocks, en testharnassen, hoewel deze ook bij eenheidstests voor notebooks worden ondersteund, vallen buiten het bestek van dit artikel. Dit artikel omvat ook geen andere soorten testmethoden, zoals integratietests, systeemtests, acceptatietestsof niet-functionele tests methoden zoals prestatietests of bruikbaarheidstests.

In dit artikel ziet u het volgende:

  • Functies en hun eenheidstests organiseren.
  • Functies schrijven in Python, R, Scala en door de gebruiker gedefinieerde functies in SQL, die goed zijn ontworpen om te worden getest.
  • Deze functies aanroepen vanuit Python-, R-, Scala- en SQL-notebooks.
  • Eenheidstests schrijven in Python, R en Scala met behulp van de populaire testframeworks pytest- voor Python, testthat voor R en ScalaTest voor Scala. Ook hoe u SQL schrijft die eenheid SQL door de gebruiker gedefinieerde functies (SQL UDF's) test.
  • Deze eenheidstests uitvoeren vanuit Python-, R-, Scala- en SQL-notebooks.

Notitie

Azure Databricks raadt u aan uw eenheidstests in een notebook te schrijven en uit te voeren. Hoewel u bepaalde opdrachten in de webterminal kunt uitvoeren, heeft de webterminal meer beperkingen, zoals een gebrek aan ondersteuning voor Spark. Zie Shell-opdrachten uitvoeren in de Azure Databricks-webterminal.

Functies en eenheidstests organiseren

Er zijn enkele algemene benaderingen voor het ordenen van uw functies en hun unittests in notebooks. Elke aanpak heeft zijn voordelen en uitdagingen.

Voor Python-, R- en Scala-notebooks zijn veelvoorkomende benaderingen het volgende:

  • Store-functies en de bijbehorende eenheidstests buiten notebooks..
    • Voordelen: U kunt deze functies aanroepen met en buiten notebooks. Testframeworks zijn beter ontworpen om tests buiten notebooks uit te voeren.
    • Uitdagingen: deze benadering wordt niet ondersteund voor Scala-notebooks. Deze aanpak verhoogt ook het aantal bestanden dat moet worden bijgehouden en onderhouden.
  • Store-functies in één notebook en de bijbehorende unittests in een afzonderlijk notebook..
    • Voordelen: Deze functies zijn eenvoudiger te hergebruiken in notebooks.
    • Uitdagingen: het aantal notebooks dat moet worden bijgehouden en onderhouden, neemt toe. Deze functies kunnen niet buiten notebooks worden gebruikt. Deze functies kunnen ook lastiger te testen zijn buiten notebooks.
  • Store-functies en de bijbehorende unittests binnen hetzelfde notebook..
    • Voordelen: Functies en hun eenheidstests worden opgeslagen in één notebook voor eenvoudiger bijhouden en onderhouden.
    • Uitdagingen: deze functies kunnen moeilijker opnieuw worden gebruikt in notebooks. Deze functies kunnen niet buiten notebooks worden gebruikt. Deze functies kunnen ook lastiger te testen zijn buiten notebooks.

Voor Python- en R-notebooks raadt Databricks aan om functies en hun eenheidstests buiten notebooks op te slaan. Voor Scala-notebooks raadt Databricks aan om functies in één notebook en de bijbehorende eenheidstests in een afzonderlijk notebook op te geven.

Voor SQL-notebooks raadt Databricks u aan om functies op te slaan als door de gebruiker gedefinieerde SQL-functies (SQL UDF's) in uw schema's (ook wel databases genoemd). U kunt deze SQL UDF's en de bijbehorende eenheidstests vervolgens aanroepen vanuit SQL-notebooks.

Schrijffuncties

In deze sectie wordt een eenvoudige set voorbeeldfuncties beschreven die het volgende bepalen:

  • Of er een tabel in een database bestaat.
  • Of er een kolom in een tabel bestaat.
  • Het aantal rijen in een kolom voor een waarde in die kolom.

Deze functies zijn bedoeld om eenvoudig te zijn, zodat u zich kunt richten op de details van het testen van eenheden in dit artikel in plaats van zich te richten op de functies zelf.

Als u de beste resultaten van het testen van eenheden wilt ophalen, moet een functie één voorspelbaar resultaat retourneren en van één gegevenstype zijn. Als u bijvoorbeeld wilt controleren of er iets bestaat, moet de functie een Booleaanse waarde van waar of onwaar retourneren. Als u het aantal rijen wilt retourneren dat bestaat, moet de functie een niet-negatief geheel getal retourneren. In het eerste voorbeeld moet dit niet onwaar retourneren als er iets niet bestaat of het ding zelf als het wel bestaat. Voor het tweede voorbeeld mag het niet het aantal rijen retourneren dat bestaat of onwaar als er geen rijen bestaan.

U kunt deze functies als volgt toevoegen aan een bestaande Azure Databricks-werkruimte in Python, R, Scala of SQL.

Python

In de volgende code wordt ervan uitgegaan dat u Databricks Git-mappen (opslagplaatsen) hebt ingesteld, een opslagplaatshebt toegevoegd en dat de opslagplaats is geopend in uw Azure Databricks-werkruimte.

Maak een bestand met de naam myfunctions.py in de opslagplaats en voeg de volgende inhoud toe aan het bestand. In andere voorbeelden in dit artikel wordt verwacht dat dit bestand de naam myfunctions.pyheeft. U kunt verschillende namen gebruiken voor uw eigen bestanden.

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

In de volgende code wordt ervan uitgegaan dat u Databricks Git-mappen (opslagplaatsen) hebt ingesteld, een opslagplaatshebt toegevoegd en dat de opslagplaats is geopend in uw Azure Databricks-werkruimte.

Maak een bestand met de naam myfunctions.r in de opslagplaats en voeg de volgende inhoud toe aan het bestand. In andere voorbeelden in dit artikel wordt verwacht dat dit bestand de naam myfunctions.rheeft. U kunt verschillende namen gebruiken voor uw eigen bestanden.

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

Maak een Scala-notebook met de naam myfunctions met de volgende inhoud. In andere voorbeelden in dit artikel wordt verwacht dat dit notitieblok de naam myfunctionsheeft. U kunt verschillende namen gebruiken voor uw eigen notitieblokken.

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

In de volgende code wordt ervan uitgegaan dat u de voorbeeldgegevensset van derden hebt diamanten binnen een schema met de naam default in een catalogus met de naam main die toegankelijk is vanuit uw Azure Databricks-werkruimte. Als de catalogus of het schema dat u wilt gebruiken een andere naam heeft, wijzigt u een of beide van de volgende USE instructies zodat deze overeenkomen.

Maak een SQL-notebook en voeg de volgende inhoud toe aan dit nieuwe notebook. Sluit het notitieboekje aan op een cluster en voer het notitieboekje uit om de volgende SQL UDF's aan de opgegeven catalogus en het opgegeven schema toe te voegen.

Notitie

De SQL UDF's table_exists en column_exists werken alleen met Unity Catalog. SQL UDF-ondersteuning voor Unity Catalog bevindt zich in openbare versie.

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

Aanroepfuncties

In deze sectie wordt code beschreven die de voorgaande functies aanroept. U kunt deze functies bijvoorbeeld gebruiken om het aantal rijen in een tabel te tellen waarin een opgegeven waarde bestaat binnen een opgegeven kolom. U wilt echter controleren of de tabel daadwerkelijk bestaat en of de kolom daadwerkelijk in die tabel bestaat voordat u verdergaat. Met de volgende code wordt gecontroleerd op deze voorwaarden.

Als u de functies uit de vorige sectie hebt toegevoegd aan uw Azure Databricks-werkruimte, kunt u deze functies als volgt vanuit uw werkruimte aanroepen.

Python

Maak een Python-notebook in dezelfde map als het voorgaande myfunctions.py-bestand in uw opslagplaats en voeg de volgende inhoud toe aan het notitieblok. Wijzig de variabelewaarden voor de tabelnaam, de schemanaam (database), de kolomnaam en de kolomwaarde indien nodig. Voeg het notebook toe aan een cluster en het notebook uitvoeren om de resultaten te bekijken.

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

Maak een R-notebook in dezelfde map als het voorgaande myfunctions.r-bestand in uw opslagplaats en voeg de volgende inhoud toe aan het notitieblok. Wijzig de variabelewaarden voor de tabelnaam, de schemanaam (database), de kolomnaam en de kolomwaarde indien nodig. Bevestig een notebook aan een cluster en voer het notebook uit om de resultaten te bekijken.

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

Maak een ander Scala-notitieblok in dezelfde map als het voorgaande myfunctions Scala-notebook en voeg de volgende inhoud toe aan dit nieuwe notitieblok.

Voeg in de eerste cel van dit nieuwe notebook de volgende code toe, die de %run magie aanroept. Deze magic maakt de inhoud van het myfunctions notebook beschikbaar voor uw nieuwe notebook.

%run ./myfunctions

Voeg in de tweede cel van dit nieuwe notitieblok de volgende code toe. Wijzig de variabelewaarden voor de tabelnaam, de schemanaam (database), de kolomnaam en de kolomwaarde indien nodig. Voeg het notebook toe aan een cluster en voer het notebook uit om de resultaten te bekijken.

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

Voeg de volgende code toe aan een nieuwe cel in het voorgaande notitieblok of aan een cel in een afzonderlijk notitieblok. Wijzig zo nodig de schema- of catalogusnamen zodat deze overeenkomt met die van u en voer deze cel uit om de resultaten te bekijken.

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

Eenheidstests schrijven

In deze sectie wordt code beschreven waarmee alle functies worden getest die aan het begin van dit artikel worden beschreven. Als u in de toekomst wijzigingen aanbrengt in functies, kunt u eenheidstests gebruiken om te bepalen of deze functies nog steeds werken zoals verwacht.

Als u de functies aan het begin van dit artikel hebt toegevoegd aan uw Azure Databricks-werkruimte, kunt u als volgt eenheidstests voor deze functies aan uw werkruimte toevoegen.

Python

Maak een ander bestand met de naam test_myfunctions.py in dezelfde map als het voorgaande myfunctions.py-bestand in uw opslagplaats en voeg de volgende inhoud toe aan het bestand. Standaard zoekt pytest naar .py bestanden waarvan de namen beginnen met test_ (of eindigen met _test) om te testen. Op dezelfde manier kijkt pytest standaard in deze bestanden naar functies waarvan de namen beginnen met test_ om te testen.

Over het algemeen is het een goede praktijk om geen unittests uit te voeren op functies die met gegevens in productie werken. Dit is met name belangrijk voor functies die gegevens toevoegen, verwijderen of anderszins wijzigen. Als u uw productiegegevens op onverwachte manieren wilt beschermen tegen inbreuk op uw eenheidstests, moet u eenheidstests uitvoeren op niet-productiegegevens. Een veelvoorkomende aanpak is om valse gegevens te maken die zo dicht mogelijk bij de productiegegevens liggen. In het volgende codevoorbeeld worden valse gegevens gemaakt voor de eenheidstests waarop moet worden uitgevoerd.

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

Maak een ander bestand met de naam test_myfunctions.r in dezelfde map als het voorgaande myfunctions.r-bestand in uw opslagplaats en voeg de volgende inhoud toe aan het bestand. Standaard zoekt testthat naar .r bestanden waarvan de namen beginnen met test om te testen.

Over het algemeen is het een best practice om niet te moduletests uit te voeren op functies die met gegevens in productie werken. Dit is met name belangrijk voor functies die gegevens toevoegen, verwijderen of anderszins wijzigen. Als u uw productiegegevens op onverwachte manieren wilt beschermen tegen inbreuk op uw eenheidstests, moet u eenheidstests uitvoeren op niet-productiegegevens. Een veelvoorkomende aanpak is om valse gegevens te maken die zo dicht mogelijk bij de productiegegevens liggen. In het volgende codevoorbeeld worden nepgegevens gemaakt voor de unittests om tegen te testen.

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

Maak een ander Scala-notitieblok in dezelfde map als het voorgaande myfunctions Scala-notebook en voeg de volgende inhoud toe aan dit nieuwe notitieblok.

Voeg in de eerste cel van het nieuwe notitieblok de volgende code toe, waarmee je de %run-magic aanroept. Deze magische functie maakt de inhoud van het myfunctions-notitieboek beschikbaar voor uw nieuwe notitieboek.

%run ./myfunctions

Voeg in de tweede cel de volgende code toe. Deze code definieert uw eenheidstests en geeft aan hoe u deze kunt uitvoeren.

Over het algemeen is het een best practice om niet te moduletests uit te voeren op functies die met gegevens in productie werken. Dit is met name belangrijk voor functies die gegevens toevoegen, verwijderen of anderszins wijzigen. Als u uw productiegegevens op onverwachte manieren wilt beschermen tegen inbreuk op uw eenheidstests, moet u eenheidstests uitvoeren op niet-productiegegevens. Een veelvoorkomende aanpak is om valse gegevens te maken die zo dicht mogelijk bij de productiegegevens liggen. Het volgende codevoorbeeld maakt nepgegevens aan waarop de unittests vervolgens worden uitgevoerd.

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)

Notitie

In dit codevoorbeeld wordt de FunSuite stijl voor testen in ScalaTest gebruikt. Zie Teststijlen selecteren voor uw projectvoor andere beschikbare teststijlen.

SQL

Voordat u eenheidstests toevoegt, moet u er rekening mee houden dat het in het algemeen een best practice is om niet te eenheidstests uit te voeren op functies die werken met gegevens in productie. Dit is met name belangrijk voor functies die gegevens toevoegen, verwijderen of anderszins wijzigen. Als u uw productiegegevens op onverwachte manieren wilt beschermen tegen inbreuk op uw eenheidstests, moet u eenheidstests uitvoeren op niet-productiegegevens. Een veelvoorkomende benadering is het uitvoeren van eenheidstests op weergaven in plaats van tabellen.

Als u een weergave wilt maken, kunt u de opdracht CREATE VIEW aanroepen vanuit een nieuwe cel in het voorgaande notitieblok of een afzonderlijk notitieblok. In het volgende voorbeeld wordt ervan uitgegaan dat u een bestaande tabel hebt met de naam diamonds binnen een schema (database) met de naam default in een catalogus met de naam main. Wijzig deze namen zodat ze overeenkomen met uw eigen namen en voer dan alleen die cel uit.

USE CATALOG main;
USE SCHEMA default;

CREATE VIEW view_diamonds AS
SELECT * FROM diamonds;

Nadat u de weergave hebt gemaakt, voegt u elk van de volgende SELECT instructies toe aan een eigen nieuwe cel in het voorgaande notitieblok of aan een eigen nieuwe cel in een afzonderlijk notitieblok. Wijzig de namen zodat deze overeenkomen met uw eigen namen.

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'."));

Eenheidstests uitvoeren

In deze sectie wordt beschreven hoe u de eenheidstests uitvoert die u in de vorige sectie hebt gecodeerd. Wanneer u de eenheidstests uitvoert, krijgt u resultaten die laten zien welke eenheidstests zijn geslaagd en mislukt.

Als u de eenheidstests uit de vorige sectie hebt toegevoegd aan uw Azure Databricks-werkruimte, kunt u deze eenheidstests uitvoeren vanuit uw werkruimte. U kunt deze eenheidstests uitvoeren handmatig of volgens een planning.

Python

Maak een Python-notebook in dezelfde map als het voorgaande test_myfunctions.py-bestand in uw opslagplaats en voeg de volgende inhoud toe.

Voeg in de eerste cel van het nieuwe notitieblok de volgende code toe en voer de cel vervolgens uit, die de %pip-magic aanroept. Dit trucje installeert pytest.

%pip install pytest

Voeg in de tweede cel de volgende code toe en voer vervolgens de cel uit. Resultaten geven aan welke eenheidstests zijn geslaagd en mislukt.

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

Maak een R-notebook in dezelfde map als het voorgaande test_myfunctions.r-bestand in uw opslagplaats en voeg de volgende inhoud toe.

Voeg in de eerste cel de volgende code toe en voer vervolgens de cel uit, waarmee de functie install.packages wordt aangeroepen. Met deze functie wordt testthatgeïnstalleerd.

install.packages("testthat")

Voeg in de tweede cel de volgende code toe en voer vervolgens de cel uit. Resultaten geven aan welke eenheidstests zijn geslaagd en mislukt.

library(testthat)
source("myfunctions.r")

test_dir(".", reporter = "tap")

Scala

Voer eerst de eerste en dan de tweede cel in het notitieboek uit uit de vorige sectie. Resultaten geven aan welke eenheidstests zijn geslaagd en mislukt.

SQL

Voer elk van de drie cellen in het notebook uit vanuit de vorige sectie. De resultaten geven aan of elke eenheidstest is geslaagd of mislukt.

Als u de weergave niet meer nodig hebt nadat u de eenheidstests hebt uitgevoerd, kunt u de weergave verwijderen. Als u deze weergave wilt verwijderen, kunt u de volgende code toevoegen aan een nieuwe cel in een van de voorgaande notebooks en vervolgens alleen die cel uitvoeren.

DROP VIEW view_diamonds;

Tip

U kunt de resultaten van uw notebookuitvoeringen (inclusief eenheidstestresultaten) bekijken in de stuurprogrammalogboeken van uw cluster. U kunt ook een locatie opgeven voor de logboeklevering van uw cluster.

U kunt een systeem voor continue integratie en continue levering of implementatie (CI/CD), zoals GitHub Actions, instellen om uw eenheidstests automatisch uit te voeren wanneer uw code wordt gewijzigd. Zie de bespreking van GitHub Actions in best practices voor software-engineering voor notebooksvoor een voorbeeld.

Aanvullende informatiebronnen

pytest

testthat

ScalaTest

SQL