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 de seus 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 teste 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 escrever funções em Python, R, Scala, bem como funções definidas pelo usuário no SQL, que são bem projetadas para serem testadas por 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 em notebooks Python, R, Scala e SQL.

Observação

O Azure Databricks recomenda gravar e executar seus testes de unidade em um notebook. Embora você possa executar alguns comandos no terminal Web, o terminal Web tem mais limitações, como a falta de suporte para o Spark. Consulte Executar comandos do Shell no terminal Web do Azure Databricks.

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 tem 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 tanto dentro quanto 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 Scala. Essa abordagem também aumenta o número de arquivos a serem rastreados e mantidos.
  • Armazene funções em um notebook e seus 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 acompanhados 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 testar fora dos notebooks.
  • Armazene funções e seus testes de unidade no mesmo notebook..
    • Benefícios: as funções e seus testes de unidade são armazenados em um único bloco de anotações 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 testar 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 seus testes de unidade em um notebook separado.

Para notebooks SQL, o Databricks recomenda que você armazene funções como UDFs (funções definidas pelo usuário) 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 neste artigo, em vez de se concentrar nas próprias funções.

Para obter os melhores resultados de 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 de 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 falso 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 falso se nenhuma linha existir.

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

Pitão

O código a seguir pressupõe que você configurou pastas Git do Databricks, adicionou um repositório e tem o repositório aberto no workspace do Azure Databricks.

Crie um arquivo nomeado myfunctions.py no 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 pastas Git do Databricks, adicionou um repositório e tem o repositório aberto no workspace do Azure Databricks.

Crie um arquivo nomeado myfunctions.r no 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 (linguagem de programação)

Crie um bloco de anotações Scala nomeado myfunctions com o conteúdo a seguir. Outros exemplos neste artigo esperam que esse notebook seja nomeado myfunctions. Você pode usar nomes diferentes para seus próprios blocos de anotações.

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 supõe que você tenha o conjunto de dados de exemplo de terceiros diamonds em um esquema nomeado 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 a seguir USE para corresponder.

Crie um bloco de anotações SQL e adicione o conteúdo a seguir 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 table_exists do SQL e column_exists funcionam apenas com o Catálogo do Unity. O suporte do SQL UDF para o Catálogo do Unity está em Versão Prévia 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 invoca as funções anteriormente mencionadas. Você pode usar essas funções, por exemplo, para contar o número de linhas na tabela em que existe um valor especificado em uma coluna especificada. No entanto, você gostaria de verificar se a tabela realmente existe e se a coluna realmente existe nessa tabela, antes de continuar. 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.

Pitão

Crie um notebook Python na mesma pasta que o arquivo anterior myfunctions.py em seu repositório e adicione o conteúdo a seguir ao notebook. Altere os valores de variável para o 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 bloco de anotações R na mesma pasta que o arquivo anterior myfunctions.r em seu repositório e adicione o conteúdo a seguir ao notebook. Altere os valores de variável para o 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 (linguagem de programação)

Crie outro bloco de anotações Scala na mesma pasta que o bloco de anotações Scala anterior myfunctions e adicione o seguinte conteúdo a este novo notebook.

Na primeira célula deste novo bloco de anotações, adicione o código a seguir, que executa o comando %run mágico. 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ável para o 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 de esquema ou catálogo, se necessário, para corresponder ao seu e execute essa 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 em 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 seguinte maneira.

Pitão

Crie outro arquivo nomeado test_myfunctions.py na mesma pasta que o arquivo anterior myfunctions.py em seu repositório e adicione o conteúdo a seguir ao arquivo. Por padrão, pytest procura .py arquivos 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, é uma prática recomendada não executar testes de unidade em funções que funcionam com dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para proteger seus dados de produção de serem comprometidos por seus testes de unidade de maneiras inesperadas, você deve executar testes de unidade em relação a dados de não produção. Uma abordagem comum é criar dados falsos o mais próximo possível dos 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 nomeado test_myfunctions.r na mesma pasta que o arquivo anterior myfunctions.r em seu repositório e adicione o conteúdo a seguir ao arquivo. Por padrão, testthat procura por .r arquivos cujos nomes começam com test para teste.

Em geral, é uma prática recomendada não executar testes de unidade em funções que funcionam com dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para proteger seus dados de produção de serem comprometidos por seus testes de unidade de maneiras inesperadas, você deve executar testes de unidade em relação a dados de não produção. Uma abordagem comum é criar dados falsos o mais próximo possível dos 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 (linguagem de programação)

Crie outro bloco de anotações Scala na mesma pasta que o bloco de anotações Scala anterior myfunctions e adicione o seguinte conteúdo a este novo notebook.

Na primeira célula do novo notebook, adicione o código a seguir, que chama o comando mágico %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, é uma prática recomendada não executar testes de unidade em funções que funcionam com dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para proteger seus dados de produção de serem comprometidos por seus testes de unidade de maneiras inesperadas, você deve executar testes de unidade em relação a dados de não produção. Uma abordagem comum é criar dados falsos o mais próximo possível dos 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 FunSuite estilo 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, é uma prática recomendada não executar testes de unidade em funções que funcionam com dados em produção. Isso é especialmente importante para funções que adicionam, removem ou alteram dados. Para proteger seus dados de produção de serem comprometidos por seus testes de unidade de maneiras inesperadas, você deve executar testes de unidade em relação a dados de não produção. Uma abordagem comum é executar testes de unidade em exibições em vez de tabelas.

Para criar uma visão, você pode chamar o comando CREATE VIEW a partir de uma nova célula no bloco de anotações anterior ou em um bloco de anotações separado. O exemplo a seguir pressupõe que você tenha uma tabela existente nomeada diamonds dentro de um esquema (banco de dados) nomeado 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 falharam.

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

Pitão

Crie um notebook Python na mesma pasta que o arquivo anterior test_myfunctions.py 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 a funcionalidade %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 anterior test_myfunctions.r 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 (linguagem de programação)

Execute primeiro a primeira e depois 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 falhou.

Se você não precisar mais da exibição depois de executar os testes de unidade, poderá excluir a exibição. Para excluir essa exibição, você pode adicionar o código a seguir a uma nova célula em um dos blocos de anotações anteriores e, em seguida, executar somente essa célula.

DROP VIEW view_diamonds;

Dica

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

Você pode configurar um sistema de CI/CD (integração contínua e entrega contínua ou implantação), 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 Melhores práticas de engenharia de software para notebooks.

Recursos adicionais

pytest

testthat

ScalaTest

SQL