Egységtesztelés Databricks-jegyzetfüzetekhez

Az egységtesztelés segítségével javíthatja a jegyzetfüzetek kódjának minőségét és konzisztenciáját. Az egységtesztelés a kód önálló egységeinek, például a függvények korai és gyakran történő tesztelésének egyik módszere. Ez segít gyorsabban megtalálni a kóddal kapcsolatos problémákat, hamarabb feltárni a kód téves feltételezéseit, és egyszerűsíteni az általános kódolási erőfeszítéseket.

Ez a cikk bemutatja a függvényekkel alapszintű egységtesztelést. Az olyan fejlett fogalmak, mint az egységtesztelési osztályok és interfészek, valamint a csonkok, mockok és tesztkeretek használata, bár egységtesztelésnél jegyzetfüzetek esetében is támogatottak, nem tartoznak a lap hatókörébe. Ez a cikk nem foglalkozik egyéb tesztelési módszerekkel, például integrációs tesztelési, rendszertesztelési, elfogadási tesztelésivagy nem funkcionális tesztelési módszerekkel, például teljesítménytesztelési vagy használhatósági tesztelési.

Ez a cikk a következőket mutatja be:

  • Függvények és azok egységtesztjeinek rendszerezése.
  • A Python, az R, a Scala és a felhasználó által definiált függvények írása az SQL-ben, amelyek kifejezetten egységtesztelhetőek.
  • A függvények meghívása Python, R, Scala és SQL-jegyzetfüzetekből.
  • Egységtesztek írása Python nyelven a népszerű pytest keretrendszer használatával, R nyelven a testthat keretrendszerrel, és Scala nyelven a ScalaTest alkalmazásával. Azt is megtudhatja, hogyan írhat olyan SQL-t, amely a felhasználó által definiált SQL-függvényeket (SQL UDF-eket) teszteli.
  • Az egységtesztek futtatása Python, R, Scala és SQL-jegyzetfüzetekből.

Jegyzet

Azure Databricks az egységtesztek jegyzetfüzetben való írását és futtatását javasolja. Bár futtathat néhány parancsot a webes terminálban, a webes terminál további korlátozásokkal rendelkezik, például a Spark támogatásának hiányával. Lásd: Shell-parancsok végrehajtása Azure Databricks webes terminálban.

Függvények és egységtesztek rendszerezése

A függvények és egységtesztjeik rendszerezésére szolgáló néhány gyakori módszer létezik jegyzetfüzetek használatával. Minden megközelítésnek megvannak a maga előnyei és kihívásai.

Az Python, az R és a Scala notebookok esetében a gyakori megközelítések a következők:

  • A függvényeket és azok egységtesztjeit jegyzetfüzeteken kívül tároljuk..
    • Előnyök: Ezeket a függvényeket notebookokon belül és kívül is meghívhatja. A tesztelési keretrendszerek jobban alkalmasak a jegyzetfüzeteken kívüli tesztek futtatására. Azure Databricks a munkaterületen közvetlenül az egységtesztek felderítésére, futtatására és nyomon Python követésére alkalmas eszközkészletet biztosít. Lásd: Python egységtesztelés a munkaterületen.
    • Kihívások: Ez a megközelítés a Scala-jegyzetfüzetek esetében nem támogatott. Ez a módszer a nyomon követendő és karbantartandó fájlok számát is növeli.
  • Tárold a függvényeket egy jegyzetfüzetben, és az egységtesztek egy külön jegyzetfüzetben..
    • Előnyök: Ezek a függvények könnyebben újra felhasználhatók a jegyzetfüzetekben.
    • Kihívások: A nyomon követni és karbantartani kívánt jegyzetfüzetek száma nő. Ezek a függvények nem használhatók jegyzetfüzetek esetén kívül. Ezek a függvények a jegyzetfüzeteken kívül is nehezebben tesztelhetők.
  • Függvények és azok egységtesztjeinek tárolása ugyanabban a jegyzetfüzetben..
    • Előnyök: A függvények és egységtesztjeik egyetlen jegyzetfüzetben vannak tárolva a könnyebb nyomon követés és karbantartás érdekében.
    • Kihívások: Ezeknek a függvényeknek az újbóli használata nehezebb lehet a jegyzetfüzetekben. Ezek a függvények nem használhatók jegyzetfüzetek esetén kívül. Ezek a függvények a jegyzetfüzeteken kívül is nehezebben tesztelhetők.

Python és R notebookok esetében a Databricks azt javasolja, hogy a függvényeket és azok egységtesztjeit a jegyzetfüzeteken kívül tárolja. Scala-jegyzetfüzetek esetén a Databricks azt javasolja, hogy a függvényeket egy jegyzetfüzetbe, és az egységteszteket külön jegyzetfüzetbe foglalja bele.

SQL-jegyzetfüzetek esetén a Databricks azt javasolja, hogy a sémákban (más néven adatbázisokban) sql-felhasználó által definiált függvényekként (SQL UDF-ként) tárolja a függvényeket. Ezután meghívhatja ezeket az SQL UDF-eket és azok egységtesztjeit AZ SQL-jegyzetfüzetekből.

Írj függvényeket

Ez a szakasz egy egyszerű példafüggvény-készletet ír le, amely a következőket határozza meg:

  • Azt jelzi, hogy létezik-e tábla egy adatbázisban.
  • Azt jelzi, hogy létezik-e oszlop egy táblában.
  • Hány sor létezik egy oszlopban az adott oszlopban lévő értékhez.

Ezeket a függvényeket úgy tervezték, hogy egyszerűek legyenek, így az ezen az oldalon található egységtesztelési részletekre összpontosíthatsz ahelyett, hogy magukra a függvényekre figyelnél.

A legjobb egységtesztelési eredmények eléréséhez egy függvénynek egyetlen kiszámítható eredményt kell visszaadnia, és egyetlen adattípusnak kell lennie. Ha például ellenőrizni szeretné, hogy létezik-e valami, a függvénynek igaz vagy hamis logikai értéket kell visszaadnia. A létező sorok számának visszaadásához a függvénynek nem negatív egész számot kell visszaadnia. Az első példában nem szabad hamis értéket visszaadnia, ha valami nem létezik, vagy magát a dolgot, ha létezik. Hasonlóképpen, a második példában sem a létező sorok számát, sem pedig a hamis sorok számát nem szabad visszaadnia, ha nincsenek sorok.

Ezeket a függvényeket az alábbiak szerint veheti fel egy meglévő Azure Databricks-munkaterületre, Python, R, Scala vagy SQL nyelven.

Python

Az alábbi kód feltételezi, hogy Konfigurációs Git-integrációval rendelkezik a Git-mappákhoz, egy adattárat adott hozzá, és megnyitja az adattárat a Azure Databricks-munkaterületen.

az adattárban, és adja hozzá a következő tartalmat a fájlhoz. A lap további példái azt várják, hogy a fájl neve myfunctions.py legyen. A saját fájljaihoz különböző neveket használhat.

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

Az alábbi kód feltételezi, hogy Konfigurációs Git-integrációval rendelkezik a Git-mappákhoz, egy adattárat adott hozzá, és megnyitja az adattárat a Azure Databricks-munkaterületen.

az adattárban, és adja hozzá a következő tartalmat a fájlhoz. A lap további példái azt várják, hogy a fájl neve myfunctions.r legyen. A saját fájljaihoz különböző neveket használhat.

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

Hozzon létre egy az alábbi tartalommal. A jelen oldalon lévő további példák arra számítanak, hogy a jegyzetfüzet neve ez legyen myfunctions. A saját jegyzetfüzeteihez különböző neveket használhat.

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

Az alábbi kód feltételezi, hogy a nevű katalógusban egy default nevű sémában található külső main mintaadatkészlet érhető el az Azure Databricks munkaterületről. Ha a használni kívánt katalógusnak vagy sémának más a neve, módosítsa az alábbi USE utasítások egyikét vagy mindkettőt.

Hozzon létre egy SQL-jegyzetfüzetet, és adja hozzá az alábbi tartalmat az új jegyzetfüzethez. Ezután csatold a jegyzetfüzetet egy fürthöz, és futtasd a jegyzetfüzetet a következő SQL UDF-ek hozzáadásához a megadott katalógushoz és sémához.

Jegyzet

Az SQL UDF-ek table_exists és column_exists csak a Unity Catalogtal működnek. A Unity Catalog SQL UDF-támogatása nyilvános előzetes verzióban van.

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

Függvények hívása

Ez a szakasz az előző függvényeket meghívó kódot ismerteti. Ezekkel a függvényekkel például megszámolhatja a tábla azon sorainak számát, amelyekben egy adott érték egy adott oszlopban található. A folytatás előtt azonban ellenőrizni szeretné, hogy a tábla valóban létezik-e, és hogy az oszlop valóban létezik-e a táblában. A következő kód ellenőrzi ezeket a feltételeket.

Ha az előző szakaszban szereplő függvényeket hozzáadta a Azure Databricks munkaterülethez, az alábbiak szerint hívhatja meg ezeket a függvényeket a munkaterületről.

Python

A Python jegyzetfüzet létrehozása ugyanabban a mappában, mint az előző myfunctions.py fájl az adattárban, és adja hozzá a következő tartalmat a jegyzetfüzethez. Szükség szerint módosítsa a táblanév, a séma (adatbázis) nevét, az oszlopnevet és az oszlop értékét. Ezután csatolja a jegyzetfüzetet egy fürthöz, és futtassa a jegyzetfüzetet, hogy lássa az eredményeket.

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

Hozzon létre egy R-jegyzetfüzetet ugyanabban a mappában, mint az előző myfunctions.r fájl az adattárban, és adja hozzá a következő tartalmat a jegyzetfüzethez. Szükség szerint módosítsa a táblanév, a séma (adatbázis) nevét, az oszlopnevet és az oszlop értékét. Ezután csatolja a jegyzetfüzetet egy fürthöz, és futtassa a jegyzetfüzetet, hogy lássa az eredményeket.

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

Hozzon létre egy másik Scala-jegyzetfüzetet az előző myfunctions Scala-jegyzetfüzetével megegyező mappában, és adja hozzá a következő tartalmat az új jegyzetfüzethez.

Az új jegyzetfüzet első cellájában adja hozzá a következő kódot, amely a%run varázslatot hívja meg. Ez a varázslat teszi elérhetővé a myfunctions jegyzetfüzet tartalmát az új jegyzetfüzet számára.

%run ./myfunctions

Az új jegyzetfüzet második cellájában adja hozzá a következő kódot. Szükség szerint módosítsa a táblanév, a séma (adatbázis) nevét, az oszlopnevet és az oszlop értékét. Ezután csatolja a jegyzetfüzetet egy fürthöz, és futtassa a jegyzetfüzetet, hogy lássa az eredményeket.

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

Adja hozzá a következő kódot az előző jegyzetfüzet új cellájába vagy egy másik jegyzetfüzet egy cellába. Szükség esetén módosítsa a séma vagy katalógus nevét a saját igényeinek megfelelően, majd futtassa ezt a cellát az eredmények megtekintéséhez.

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

Egységtesztek írása

Ez a szakasz a lap elején leírt összes függvényt tesztelő kódot ismerteti. Ha a jövőben módosítja a függvényeket, egységtesztekkel megállapíthatja, hogy ezek a függvények továbbra is a várt módon működnek-e.

Ha a függvényeket a lap elején adta hozzá a Azure Databricks munkaterülethez, az alábbiak szerint adhat hozzá egységteszteket ezekhez a függvényekhez.

Python

Hozzon létre egy másik test_myfunctions.py nevű fájlt ugyanabban a mappában, mint az előző myfunctions.py fájl az adattárban, és adja hozzá a következő tartalmat a fájlhoz. Alapértelmezés szerint pytest olyan .py fájlokat keres, amelyek neve test_ (vagy _test) végződéssel kezdődik. Hasonlóképpen, alapértelmezés szerint pytest ezen fájlokban olyan függvényeket keres, amelyek neve test_ a teszteléshez kezdődik.

Általánosságban elmondható, hogy nem ajánlott egységteszteket futtatni olyan függvényeken, amelyek éles környezetben dolgoznak. Ez különösen olyan függvények esetében fontos, amelyek adatokat adnak hozzá, távolítanak el vagy más módon módosítanak. Annak érdekében, hogy a termelési adatok ne sérüljenek meg váratlan módon az egységtesztek által, futtassa az egységteszteket a nem termelési adatokkal szemben. Az egyik gyakori módszer a hamis adatok létrehozása, amelyek a lehető legközelebb állnak a produkciós adatokhoz. Az alábbi példakód hamis adatokat hoz létre a futtatandó egységtesztekhez.

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

Hozzon létre egy másik test_myfunctions.r nevű fájlt ugyanabban a mappában, mint az előző myfunctions.r fájl az adattárban, és adja hozzá a következő tartalmat a fájlhoz. Alapértelmezés szerint testthat olyan .r fájlokat keres, amelyek neve test teszteléssel kezdődik.

Általánosságban elmondható, hogy nem ajánlott egységteszteket futtatni olyan függvényeken, amelyek éles környezetben dolgoznak. Ez különösen olyan függvények esetében fontos, amelyek adatokat adnak hozzá, távolítanak el vagy más módon módosítanak. Annak érdekében, hogy a termelési adatok ne sérüljenek meg váratlan módon az egységtesztek által, futtassa az egységteszteket a nem termelési adatokkal szemben. Az egyik gyakori módszer a hamis adatok létrehozása, amelyek a lehető legközelebb állnak a produkciós adatokhoz. Az alábbi példakód hamis adatokat hoz létre a futtatandó egységtesztekhez.

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

Hozzon létre egy másik Scala-jegyzetfüzetet az előző myfunctions Scala-jegyzetfüzetével megegyező mappában, és adja hozzá a következő tartalmat az új jegyzetfüzethez.

Az új jegyzetfüzet első cellájába illessze be a következő kódot, amely meghívja a %run mágikus parancsot. Ez a varázslat teszi elérhetővé a myfunctions jegyzetfüzet tartalmát az új jegyzetfüzet számára.

%run ./myfunctions

A második cellában adja hozzá a következő kódot. Ez a kód határozza meg az egységteszteket, és meghatározza azok futtatásának módját.

Általánosságban elmondható, hogy nem ajánlott egységteszteket futtatni olyan függvényeken, amelyek éles környezetben dolgoznak. Ez különösen olyan függvények esetében fontos, amelyek adatokat adnak hozzá, távolítanak el vagy más módon módosítanak. Annak érdekében, hogy a termelési adatok ne sérüljenek meg váratlan módon az egységtesztek által, futtassa az egységteszteket a nem termelési adatokkal szemben. Az egyik gyakori módszer a hamis adatok létrehozása, amelyek a lehető legközelebb állnak a produkciós adatokhoz. Az alábbi példakód hamis adatokat hoz létre a futtatandó egységtesztekhez.

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)

Jegyzet

Ez a példakód a ScalaTest FunSuite tesztelési stílusát használja. Az elérhető tesztelési stílusokkal kapcsolatban lásd a Tesztelési stílusok kiválasztása a projekt számárarészt.

SQL

Az egységtesztek hozzáadása előtt tudnia kell, hogy általánosságban a legjobb gyakorlat az, ha nem futtatunk egységteszteket olyan függvényeken, amelyek éles adatokkal dolgoznak. Ez különösen olyan függvények esetében fontos, amelyek adatokat adnak hozzá, távolítanak el vagy más módon módosítanak. Annak érdekében, hogy a termelési adatok ne sérüljenek meg váratlan módon az egységtesztek által, futtassa az egységteszteket a nem termelési adatokkal szemben. Az egyik gyakori módszer az egységtesztek futtatása nézeteken táblák helyett.

Nézet létrehozásához meghívhatja a CREATE VIEW parancsot egy új cellából az előző jegyzetfüzetben vagy egy külön jegyzetfüzetben. Az alábbi példa feltételezi, hogy van egy diamonds nevű tábla egy default nevű sémában egy mainnevű katalóguson belül. Módosítsa ezeket a neveket a saját igényeinek megfelelően, majd futtassa csak azt a cellát.

USE CATALOG main;
USE SCHEMA default;

CREATE VIEW view_diamonds AS
SELECT * FROM diamonds;

A nézet létrehozása után adja hozzá az alábbi SELECT utasításokat az előző jegyzetfüzet saját új cellájába vagy egy külön jegyzetfüzet saját új cellájába. Módosítsa a neveket a saját igényeinek megfelelően.

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

Egységtesztek futtatása

Ez a szakasz az előző szakaszban kódolt egységtesztek futtatását ismerteti. Az egységtesztek futtatásakor az eredmények azt mutatják, hogy mely egységtesztek mentek át és sikertelenek.

Ha az előző szakasz egységtesztjeit hozzáadta a Azure Databricks munkaterülethez, ezeket az egységteszteket a munkaterületről futtathatja. Ezeket az egységteszteket futtathatja manuálisan, vagy ütemezés szerint.

Python

Hozzon létre egy Python jegyzetfüzetet az előző test_myfunctions.py fájllal megegyező mappában az adattárban, és adja hozzá a következő tartalmat.

Az új jegyzetfüzet első cellájában adja hozzá a következő kódot, majd futtassa a cellát, amely meghívja a %pip varázslatot. Ez a varázslat telepíti pytest.

%pip install pytest

A második cellában adja hozzá a következő kódot, majd futtassa a cellát. Az eredmények azt mutatják, hogy mely egységtesztek voltak sikeresek és melyek vallottak kudarcot.

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

Hozzon létre egy R-jegyzetfüzetet ugyanabban a mappában, mint az előző test_myfunctions.r fájl az adattárban, és adja hozzá a következő tartalmat.

Az első cellában adja hozzá a következő kódot, majd futtassa a cellát, amely meghívja a install.packages függvényt. Ez a függvény a testthat-t telepíti.

install.packages("testthat")

A második cellában adja hozzá a következő kódot, majd futtassa a cellát. Az eredmények azt mutatják, hogy mely egységtesztek voltak sikeresek és melyek vallottak kudarcot.

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

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

Scala

Az előző szakaszból futtassa a jegyzetfüzet első, majd második celláját. Az eredmények azt mutatják, hogy mely egységtesztek voltak sikeresek és melyek vallottak kudarcot.

SQL

A jegyzetfüzet három celláját mindegyikét futtassa külön-külön az előző szakaszból. Az eredmények azt mutatják, hogy az egyes egységtesztek sikeresek vagy sikertelenek voltak-e.

Ha már nincs szüksége a nézetre az egységtesztek futtatása után, törölheti a nézetet. A nézet törléséhez hozzáadhatja a következő kódot egy új cellához az előző jegyzetfüzetek egyikében, majd csak az adott cellát futtathatja.

DROP VIEW view_diamonds;

Borravaló

A notebook-futtatások eredményeit (beleértve az egységteszt eredményeit) megtekintheti a fürt illesztőprogram-naplóiban. Megadhatja a csoport naplók szállításának helyét is.

Beállíthat egy folyamatos integrációs és folyamatos kézbesítési vagy üzembe helyezési (CI/CD) rendszert, például GitHub Actions, hogy automatikusan futtathassa az egységteszteket, amikor a kód megváltozik. Példaként lásd a GitHub Actionsről szóló részt a Databricks-jegyzetfüzetek bevált szoftvermérnöki gyakorlatai című dokumentumban.

További erőforrások

pytest

testthat

ScalaTest

SQL