Partilhar via


Testes unitários para computadores portáteis

Você pode usar o teste de unidade para ajudar a melhorar a qualidade e a consistência do código dos seus blocos de anotações. O teste de unidade é uma abordagem para testar unidades autônomas de código, como funções, cedo e com frequência. Isso ajuda você a encontrar problemas com seu código mais rapidamente, descobrir suposições erradas 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ásica com funções. Conceitos avançados, como classes e interfaces de teste de unidade, bem como o uso de stubs, simulações e chicotes de teste, embora também sejam suportados quando testes de unidade para notebooks, estão fora do escopo deste artigo. Este artigo também não abrange outros tipos de métodos de teste, como testes de integração, testes de sistema, testes de aceitação ou métodos de teste não funcionais, como testes de desempenho ou testes de usabilidade.

Este artigo demonstra o seguinte:

  • Como organizar funções e seus testes unitários.
  • Como escrever funções em Python, R, Scala, bem como funções definidas pelo usuário em SQL, que são bem projetadas para serem testadas por unidade.
  • Como chamar essas funções de blocos de anotações Python, R, Scala e SQL.
  • Como escrever testes de unidade em Python, R e Scala usando as estruturas de teste populares pytest para Python, testthat para R e ScalaTest para Scala. Também como escrever SQL que a unidade testa funções SQL definidas pelo usuário (SQL UDFs).
  • Como executar esses testes de unidade a partir de blocos de anotações Python, R, Scala e SQL.

Organizar funções e testes de unidade

Existem algumas abordagens comuns para organizar suas funções e seus testes de unidade com blocos de anotações. Cada abordagem tem seus benefícios e desafios.

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

  • Armazene funções e seus testes de unidade fora dos 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 é suportada para notebooks Scala. Essa abordagem também aumenta o número de arquivos para rastrear e manter.
  • Armazene funções em um bloco de anotações e seus testes de unidade em um bloco de anotações separado.
    • Benefícios: Estas funções são mais fáceis de reutilizar em blocos de notas.
    • Desafios: O número de notebooks para acompanhar e manter aumenta. Estas funções não podem ser utilizadas fora dos computadores portáteis. Estas funções também podem ser mais difíceis de testar fora dos computadores portáteis.
  • Armazene funções e seus testes de unidade dentro do mesmo notebook..
    • Benefícios: As funções e seus testes de unidade são armazenados em um único notebook para facilitar o rastreamento e a manutenção.
    • Desafios: Estas funções podem ser mais difíceis de reutilizar em blocos de notas. Estas funções não podem ser utilizadas fora dos computadores portáteis. Estas funções também podem ser mais difíceis de testar fora dos computadores portáteis.

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

Para blocos de anotações SQL, o Databricks recomenda que você armazene funções como SQL user-defined functions (SQL UDFs) em seus esquemas (também conhecidos como bancos de dados). Em seguida, você pode chamar esses UDFs SQL e seus testes de unidade a partir de blocos de anotações SQL.

Funções de escrita

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 numa 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 funções em si.

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 booleano de true ou false. Para retornar o número de linhas existentes, a função deve retornar um número inteiro não negativo. Não deve, no primeiro exemplo, retornar falso se algo não existir ou a própria coisa se existir. Da mesma forma, para o segundo exemplo, ele não deve retornar o número de linhas que existem ou false se não existirem linhas.

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

Python

O código a seguir pressupõe que você tenha configurado pastas Git do Databricks (Repos), adicionado um repositório e tenha o repositório aberto em seu espaço de trabalho do Azure Databricks.

Crie um arquivo nomeado myfunctions.py dentro do repositório e adicione o seguinte conteúdo ao arquivo. Outros exemplos neste artigo esperam que esse arquivo seja nomeado myfunctions.py. Pode utilizar nomes diferentes para os seus próprios ficheiros.

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ê tenha configurado pastas Git do Databricks (Repos), adicionado um repositório e tenha o repositório aberto em seu espaço de trabalho do Azure Databricks.

Crie um arquivo nomeado myfunctions.r dentro do repositório e adicione o seguinte conteúdo ao arquivo. Outros exemplos neste artigo esperam que esse arquivo seja nomeado myfunctions.r. Pode utilizar nomes diferentes para os seus próprios ficheiros.

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 bloco de anotações Scala nomeado myfunctions com o seguinte conteúdo. Outros exemplos neste artigo esperam que este bloco de anotações seja nomeado myfunctions. Pode utilizar nomes diferentes para os seus próprios blocos de notas.

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 você tenha os diamantes de conjunto de dados de exemplo de terceiros dentro de um esquema nomeado default dentro de um catálogo nomeado main que é acessível a partir do seu espaço de trabalho 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 seguinte conteúdo a este novo bloco de anotações. Em seguida, anexe o bloco de anotações a um cluster e execute o bloco de anotações para adicionar as seguintes UDFs SQL ao catálogo e esquema especificados.

Nota

As UDFs SQL table_exists e column_exists funcionam apenas com o Unity Catalog. O suporte do SQL UDF para o Unity Catalog 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 onde existe um valor especificado dentro de uma coluna especificada. No entanto, convém 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 seu espaço de trabalho do Azure Databricks, poderá chamar essas funções do seu espaço de trabalho da seguinte maneira.

Python

Crie um bloco de anotações Python na mesma pasta do arquivo anterior myfunctions.py no repositório e adicione o seguinte conteúdo ao bloco de anotações. Altere os valores das variáveis 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 bloco de anotações a um cluster e execute o bloco de anotações 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 do arquivo anterior myfunctions.r no repositório e adicione o seguinte conteúdo ao bloco de anotações. Altere os valores das variáveis 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 bloco de anotações a um cluster e execute o bloco de anotações 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 bloco de anotações Scala na mesma pasta que o bloco de anotações Scala anterior myfunctions e adicione o seguinte conteúdo a esse novo bloco de anotações.

Na primeira célula deste novo bloco de anotações, adicione o código a seguir, que chama % run magic. Esta magia torna o conteúdo do bloco de notas disponível para o myfunctions seu novo bloco de notas.

%run ./myfunctions

Na segunda célula deste novo bloco de notas, adicione o seguinte código. Altere os valores das variáveis 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 bloco de anotações a um cluster e execute o bloco de anotações 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 seguinte código a uma nova célula no bloco de notas anterior ou a uma célula num bloco de notas separado. Altere o esquema ou os nomes do catálogo, se necessário, para corresponder ao seu e, em seguida, 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

Escrever 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 esperado.

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

Python

Crie outro arquivo nomeado test_myfunctions.py na mesma pasta que o arquivo anterior myfunctions.py em seu repositório e adicione o seguinte conteúdo 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 procura dentro desses arquivos por 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 trabalham 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 dados que não são de produção. Uma abordagem comum é criar dados falsos que sejam o mais próximos possível dos dados de produção. O exemplo de código a seguir cria dados falsos para os testes de unidade serem 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 seguinte conteúdo ao arquivo. Por padrão, testthat procura .r arquivos cujos nomes começam com test para testar.

Em geral, é uma prática recomendada não executar testes de unidade em funções que trabalham 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 dados que não são de produção. Uma abordagem comum é criar dados falsos que sejam o mais próximos possível dos dados de produção. O exemplo de código a seguir cria dados falsos para os testes de unidade serem 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 bloco de anotações Scala na mesma pasta que o bloco de anotações Scala anterior myfunctions e adicione o seguinte conteúdo a esse novo bloco de anotações.

Na primeira célula do novo notebook, adicione o seguinte código, que chama a %run magia. Esta magia torna o conteúdo do bloco de notas disponível para o myfunctions seu novo bloco de notas.

%run ./myfunctions

Na segunda célula, adicione o seguinte código. Este código define seus testes de unidade e especifica como executá-los.

Em geral, é uma prática recomendada não executar testes de unidade em funções que trabalham 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 dados que não são de produção. Uma abordagem comum é criar dados falsos que sejam o mais próximos possível dos dados de produção. O exemplo de código a seguir cria dados falsos para os testes de unidade serem 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)

Nota

Este exemplo de código usa o FunSuite estilo de teste em ScalaTest. Para outros estilos de teste disponíveis, consulte Selecionando 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 dados que não são de produção. Uma abordagem comum é executar testes de unidade em modos de exibição em vez de tabelas.

Para criar uma vista, pode chamar o comando CREATE VIEW a partir de uma nova célula no bloco de notas anterior ou num bloco de notas separado. O exemplo a seguir pressupõe que você tenha uma tabela existente nomeada diamonds dentro de um esquema (banco de dados) nomeado default dentro de um catálogo chamado main. Altere esses nomes para corresponderem 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 o modo de exibição, adicione cada uma das instruções a seguir SELECT à sua própria nova célula no bloco de anotações anterior ou à sua própria nova célula em um bloco de anotações separado. Altere os nomes para corresponderem 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. Quando você executa os testes de unidade, você obtém resultados mostrando quais testes de unidade passaram e falharam.

Se você adicionou os testes de unidade da seção anterior ao seu espaço de trabalho do Azure Databricks, poderá executar esses testes de unidade do seu espaço de trabalho. Você pode executar esses testes de unidade manualmente ou de forma agendada.

Python

Crie um bloco de anotações Python na mesma pasta que o arquivo anterior test_myfunctions.py em seu repositório e adicione o seguinte conteúdo.

Na primeira célula do novo bloco de anotações, adicione o seguinte código e execute a célula, que chama a %pip magia. Esta magia instala pytesto .

%pip install pytest

Na segunda célula, adicione o seguinte código e, em seguida, execute a célula. Os resultados mostram quais testes de unidade passaram e falharam.

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 bloco de anotações R na mesma pasta do arquivo anterior test_myfunctions.r no repositório e adicione o seguinte conteúdo.

Na primeira célula, adicione o seguinte código e, em seguida, execute a célula, que chama a install.packages função. Esta função instala o testthat.

install.packages("testthat")

Na segunda célula, adicione o seguinte código e, em seguida, execute a célula. Os resultados mostram quais testes de unidade passaram e falharam.

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

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

Scala

Execute a primeira e, em seguida, a segunda células no bloco de anotações da seção anterior. Os resultados mostram quais testes de unidade passaram e falharam.

SQL

Execute cada uma das três células do bloco de anotações da seção anterior. Os resultados mostram se cada teste de unidade foi aprovado ou reprovado.

Se você não precisar mais do modo de exibição depois de executar os testes de unidade, poderá excluí-lo. Para eliminar esta vista, pode adicionar o seguinte código a uma nova célula num dos blocos de notas anteriores e, em seguida, executar apenas essa célula.

DROP VIEW view_diamonds;

Gorjeta

Você pode exibir os resultados das execuções do seu bloco de anotações (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 cluster.

Você pode configurar um sistema de integração contínua e entrega contínua ou implantação (CI/CD), como o GitHub Actions, para executar automaticamente seus testes de unidade sempre que o código for alterado. Para obter um exemplo, consulte a cobertura de Ações do GitHub em Práticas recomendadas de engenharia de software para notebooks.

Recursos adicionais

Pytest

testeatat

ScalaTest

SQL