Compartilhar via


Teste de unidade para notebooks

Você pode usar o teste de unidade para ajudar a melhorar a qualidade e a consistência do código dos notebooks. O teste de unidade é uma abordagem para testar unidades de código independentes, como funções, antecipadamente e com frequência. Isso ajuda você a encontrar problemas com o código mais rapidamente, descobrir suposições equivocadas sobre seu código mais cedo e simplificar seus esforços gerais de codificação.

Este artigo é uma introdução ao teste de unidade básico com funções. Conceitos avançados, como classes e interfaces de teste de unidade, bem como o uso de stubs, elementos fictícios e agentes de teste, ao mesmo tempo em que são compatíveis com testes de unidade para notebooks, estão fora do escopo deste artigo. Este artigo também não aborda outros tipos de métodos de teste, como teste de integração, teste de sistema, teste de aceitação ou métodos de testes não funcionais, como teste de desempenho ou teste de usabilidade.

Este artigo demonstra o seguinte:

  • Como organizar funções e seus testes de unidade.
  • Como gravar funções no Python, R, Scala, bem como funções definidas pelo usuário no SQL, que são bem projetadas para passarem pelo teste de unidade.
  • Como chamar essas funções de notebooks do Python, R, Scala e SQL.
  • Como gravar testes de unidade no Python, R e Scala usando as estruturas de teste populares pytest para Python, testthat para R e ScalaTest para Scala. Além disso, como gravar o SQL que a unidade testa as funções definidas pelo usuário do SQL (UDFs do SQL).
  • Como executar esses testes de unidade nos notebooks do Python, R, Scala e SQL.

Organizar funções e testes de unidade

Há algumas abordagens comuns para organizar suas funções e seus testes de unidade com notebooks. Cada abordagem possui seus benefícios e desafios.

Para notebooks Python, R e Scala, as abordagens comuns incluem o seguinte:

  • Armazenar funções e respectivos testes de unidade fora de notebooks.
    • Benefícios: você pode chamar essas funções com e fora dos notebooks. As estruturas de teste são melhor projetadas para executar testes fora dos notebooks.
    • Desafios: essa abordagem não tem suporte para notebooks do Scala. Essa abordagem também aumenta o número de arquivos a serem rastreados e mantidos.
  • Armazenar funções em um notebook e os testes de unidade em um notebook separado.
    • Benefícios: essas funções são mais fáceis de serem reutilizadas em notebooks.
    • Desafios: o número de notebooks a serem rastreados e mantidos aumenta. Essas funções não podem ser usadas fora dos notebooks. Essas funções também podem ser mais difíceis de serem testadas fora dos notebooks.
  • Armazenar as funções e os testes de unidade no mesmo notebook.
    • Benefício: as funções e os testes de unidade são armazenados em um único notebook para facilitar o acompanhamento e a manutenção.
    • Desafios: essas funções podem ser mais difíceis de serem reutilizadas em notebooks. Essas funções não podem ser usadas fora dos notebooks. Essas funções também podem ser mais difíceis de serem testadas fora dos notebooks.

Para notebooks Python e R, o Databricks recomenda armazenar funções e seus testes de unidade fora dos notebooks. Para notebooks Scala, o Databricks recomenda incluir funções em um notebook e os testes de unidade em um notebook separado.

Para notebooks SQL, o Databricks recomenda que você armazene as funções como funções definidas pelo usuário do SQL (UDFs do SQL) em seus esquemas (também conhecidos como bancos de dados). Em seguida, você pode chamar esses UDFs do SQL e seus testes de unidade de notebooks SQL.

Funções de gravação

Esta seção descreve um conjunto simples de funções de exemplo que determinam o seguinte:

  • Se existe uma tabela em um banco de dados.
  • Se existe uma coluna em uma tabela.
  • Quantas linhas existem em uma coluna para um valor dentro dessa coluna.

Essas funções devem ser simples para que você possa se concentrar nos detalhes do teste de unidade que estão neste artigo e não nas funções em si.

Para obter os melhores resultados do teste de unidade, uma função deve retornar um único resultado previsível e ser de um único tipo de dados. Por exemplo, para verificar se algo existe, a função deve retornar um valor booliano true ou false. Para retornar o número de linhas existentes, a função deve retornar um número inteiro não negativo. Ele não deve, no primeiro exemplo, retornar false se algo não existir ou a coisa em si se ele existir. Da mesma forma, no segundo exemplo, ele não deve retornar o número de linhas existentes ou false se nenhuma linha existir.

Você pode adicionar essas funções a um workspace existente do Azure Databricks, da seguinte maneira no Python, R, Scala ou SQL.

Python

O código a seguir pressupõe que você Configurou as pastas Git do Databricks (Repos), adicionou um repositório e está com o repositório aberto no workspace do Azure Databricks.

Crie um arquivo chamado myfunctions.py dentro do repositório e adicione o conteúdo a seguir ao arquivo. Outros exemplos neste artigo esperam que esse arquivo seja nomeado myfunctions.py. Você pode usar nomes diferentes para seus próprios arquivos.

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

O código a seguir pressupõe que você Configurou as pastas Git do Databricks (Repos), adicionou um repositório e está com o repositório aberto no workspace do Azure Databricks.

Crie um arquivo chamado myfunctions.r dentro do repositório e adicione o conteúdo a seguir ao arquivo. Outros exemplos neste artigo esperam que esse arquivo seja nomeado myfunctions.r. Você pode usar nomes diferentes para seus próprios arquivos.

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

Crie um notebook do Scala chamado myfunctions com o seguinte conteúdo. Outros exemplos neste artigo esperam que esse notebook seja nomeado myfunctions. Você pode usar nomes diferentes para seus próprios 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()
}

SQL

O código a seguir pressupõe que os diamantes do conjunto de dados de exemplo de terceiros estejam em um esquema chamado default em um catálogo chamado main acessível do workspace do Azure Databricks. Se o catálogo ou esquema que você deseja usar tiver um nome diferente, altere uma ou ambas as instruções USE a seguir para corresponder.

Crie um notebook SQL e adicione o seguinte conteúdo a este novo notebook. Em seguida, anexe o notebook a um cluster e execute o notebook para adicionar as UDFs do SQL a seguir ao catálogo e ao esquema especificados.

Observação

Os UDFs do SQL table_exists e column_exists funcionam apenas com o Catálogo do Unity. O suporte da UDF do SQL para o Catálogo do Unity está em Visualização pública.

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

Funções de chamada

Esta seção descreve o código que chama as funções anteriores. Você pode usar essas funções, por exemplo, para contar o número de linhas na tabela em que um valor especificado existe dentro de uma coluna especificada. No entanto, você gostaria de verificar se a tabela realmente existe e se a coluna realmente existe nessa tabela, antes de prosseguir. O código a seguir verifica essas condições.

Se você adicionou as funções da seção anterior ao workspace do Azure Databricks, poderá chamar essas funções do workspace da seguinte maneira.

Python

Crie um notebook Python na mesma pasta que o arquivo myfunctions.py anterior em seu repositório e adicione o conteúdo a seguir ao notebook. Altere os valores de variáveis pelo nome da tabela, o nome do esquema (banco de dados), o nome da coluna e o valor da coluna conforme necessário. Em seguida, anexe o notebook a um cluster e execute o notebook para ver os resultados.

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

Crie um notebook R na mesma pasta que o arquivo myfunctions.r anterior em seu repositório e adicione o conteúdo a seguir ao notebook. Altere os valores de variáveis pelo nome da tabela, o nome do esquema (banco de dados), o nome da coluna e o valor da coluna conforme necessário. Em seguida, anexe o notebook a um cluster e execute o notebook para ver os resultados.

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

Crie outro notebook do Scala na mesma pasta que o notebook do Scala anterior myfunctions e adicione o conteúdo a seguir a este novo notebook.

Na primeira célula deste novo notebook, adicione o código a seguir, que chama o magic %run. O magic disponibiliza o conteúdo do notebook myfunctions para o novo notebook.

%run ./myfunctions

Na segunda célula deste novo notebook, adicione o código a seguir. Altere os valores de variáveis pelo nome da tabela, o nome do esquema (banco de dados), o nome da coluna e o valor da coluna conforme necessário. Em seguida, anexe o notebook a um cluster e execute o notebook para ver os resultados.

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

Adicione o código a seguir a uma nova célula no notebook anterior ou a uma célula em um notebook separado. Altere os nomes do esquema ou catálogo, se necessário, para corresponder ao seu e execute esta célula para ver os resultados.

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

Gravar testes de unidade

Esta seção descreve o código que testa cada uma das funções descritas no início deste artigo. Se você fizer alterações nas funções no futuro, poderá usar testes de unidade para determinar se essas funções ainda funcionam como você espera.

Se você adicionou as funções no início deste artigo ao workspace do Azure Databricks, poderá adicionar testes de unidade para essas funções ao seu workspace da maneira descrita a seguir.

Python

Crie outro arquivo chamado test_myfunctions.py na mesma pasta que o arquivo myfunctions.py anterior em seu repositório e adicione o conteúdo a seguir ao arquivo. Por padrão, pytest procura arquivos .py cujos nomes começam com test_ (ou terminam com _test) para testar. Da mesma forma, por padrão, pytest examina dentro desses arquivos funções cujos nomes começam com test_ para testar.

Em geral, a prática recomendada é não executar testes de unidade em funções que trabalham com os dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para evitar que seus dados de produção sejam comprometidos por testes de unidade de maneiras inesperadas, você deve executar testes de unidade em dados que não sejam de produção. Uma abordagem comum é criar dados falsos com a maior semelhança possível com os dados de produção. O exemplo de código a seguir cria dados falsos para que os testes de unidade sejam executados.

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

Crie outro arquivo chamado test_myfunctions.r na mesma pasta que o arquivo myfunctions.r anterior em seu repositório e adicione o conteúdo a seguir ao arquivo. Por padrão, testthat procura arquivos .r cujos nomes começam com test para testar.

Em geral, a prática recomendada é não executar testes de unidade em funções que trabalham com os dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para evitar que seus dados de produção sejam comprometidos por testes de unidade de maneiras inesperadas, você deve executar testes de unidade em dados que não sejam de produção. Uma abordagem comum é criar dados falsos com a maior semelhança possível com os dados de produção. O exemplo de código a seguir cria dados falsos para que os testes de unidade sejam executados.

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

Crie outro notebook do Scala na mesma pasta que o notebook do Scala anterior myfunctions e adicione o conteúdo a seguir a este novo notebook.

Na primeira célula do novo notebook, adicione o código a seguir, que chama o magic %run. O magic disponibiliza o conteúdo do notebook myfunctions para o novo notebook.

%run ./myfunctions

Na segunda célula, adicione o código a seguir. Esse código define os testes de unidade e especifica como executá-los.

Em geral, a prática recomendada é não executar testes de unidade em funções que trabalham com os dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para evitar que seus dados de produção sejam comprometidos por testes de unidade de maneiras inesperadas, você deve executar testes de unidade em dados que não sejam de produção. Uma abordagem comum é criar dados falsos com a maior semelhança possível com os dados de produção. O exemplo de código a seguir cria dados falsos para que os testes de unidade sejam executados.

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)

Observação

Este exemplo de código usa o estilo FunSuite de teste no ScalaTest. Para outros estilos de teste disponíveis, consulte Selecionar estilos de teste para seu projeto.

SQL

Antes de adicionar testes de unidade, você deve estar ciente de que, em geral, a prática recomendada é não executar testes de unidade em funções que trabalhem com os dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para evitar que seus dados de produção sejam comprometidos por testes de unidade de maneiras inesperadas, você deve executar testes de unidade em dados que não sejam de produção. Uma abordagem comum é executar testes de unidade em exibições em vez de tabelas.

Para criar uma exibição, você pode chamar o comando CREATE VIEW em uma nova célula no notebook anterior ou em outro notebook. O exemplo a seguir pressupõe que você já tenha uma tabela chamada diamonds dentro de um esquema (banco de dados) chamado default em um catálogo chamado main. Altere esses nomes para corresponder aos seus conforme necessário e execute apenas essa célula.

USE CATALOG main;
USE SCHEMA default;

CREATE VIEW view_diamonds AS
SELECT * FROM diamonds;

Depois de criar a exibição, adicione cada uma das instruções SELECT a seguir à sua própria nova célula no notebook anterior ou às próprias novas células em um notebook separado. Altere os nomes para corresponder aos seus, conforme necessário.

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

Executar testes de unidade

Esta seção descreve como executar os testes de unidade codificados na seção anterior. Ao executar os testes de unidade, você obtém resultados mostrando quais testes de unidade foram aprovados e reprovados.

Se você adicionou os testes de unidade da seção anterior ao workspace do Azure Databricks, execute esses testes de unidade em seu workspace. Você pode executar esses testes de unidade manualmente ou de acordo com um agendamento.

Python

Crie um notebook Python na mesma pasta que o arquivo test_myfunctions.py anterior em seu repositório e adicione o conteúdo a seguir.

Na primeira célula do novo notebook, adicione o código a seguir e execute a célula, que chama o magic %pip. Esse magic instala o pytest.

%pip install pytest

Na segunda célula, adicione o código a seguir e execute a célula. Os resultados mostram quais testes de unidade foram aprovados e reprovados.

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

Crie um notebook R na mesma pasta que o arquivo test_myfunctions.r anterior em seu repositório e adicione o conteúdo a seguir.

Na primeira célula, adicione o código a seguir e execute a célula, que chama a função install.packages. Essa função instala o testthat.

install.packages("testthat")

Na segunda célula, adicione o código a seguir e execute a célula. Os resultados mostram quais testes de unidade foram aprovados e reprovados.

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

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

Scala

Execute a primeira e, em seguida, a segunda célula no notebook da seção anterior. Os resultados mostram quais testes de unidade foram aprovados e reprovados.

SQL

Execute cada uma das três células no notebook da seção anterior. Os resultados mostram se cada teste de unidade foi aprovado ou reprovado.

Se você não precisar mais da exibição depois de executar os testes de unidade, exclua a exibição. Para excluir a exibição, adicione o código a seguir a uma nova célula em um dos notebooks anteriores e, em seguida, execute somente essa célula.

DROP VIEW view_diamonds;

Dica

Você pode ver os resultados das execuções do notebook (incluindo os resultados do teste de unidade) nos logs de driver do cluster. Você também pode especificar um local para a entrega de log do seu cluster.

Você pode configurar um sistema de CI/CD (integração contínua e entrega ou implantação contínua), como o GitHub Actions, para executar automaticamente os testes de unidade sempre que o código for alterado. Para ver um exemplo, confira a abordagem sobre o GitHub Actions em Práticas recomendadas de engenharia de software para notebooks.

Recursos adicionais

pytest

testthat

ScalaTest

SQL