Interfețe R6 pentru backend: Definiți ce, nu cum

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(Acest articol a fost publicat pentru prima dată pe Jakub :: Sobolewskiși a contribuit cu drag la R-Bloggers). (Puteți raporta problema despre conținutul de pe această pagină aici)


Doriți să vă împărtășiți conținutul pe R-Bloggers? Faceți clic aici dacă aveți un blog sau aici dacă nu.

Aplicația dvs. strălucitoare are logica de afaceri împrăștiată peste tot.

Testarea se simte imposibilă. De fiecare dată când atingeți o bucată, alți trei se rup. Dependențele se încurcă ca cablurile pentru căști în buzunar.

Există o cale mai curată.

Interfețele definesc ce, nu cum

Interfețele R6 vă permit să împachetați concepte împreună fără a bloca în detalii despre implementare. Logica dvs. de afaceri depinde de contracte, nu de clase concrete.

Gândiți -vă la o aplicație strălucitoare care trebuie să analizeze datele clienților. Uneori doriți întrebări reale de baze de date. În timpul testării, doriți falsuri rapide. În testele de acceptare, aveți nevoie de scenarii controlate.

Interfața rămâne aceeași. Modificările de implementare.

Nivelați-vă jocul de testare! Prindeți -vă copia foii de parcurs a testării R.

Implementați interfața

Definiți comportamentul interfeței dvs. Ce metode ar trebui să aibă? Ce parametri? Ce valori de returnare?

Faceți ca toate metodele să arunce o eroare. În cazul în care o clasă care implementează această interfață uită să înlocuiască una, ar trebui să eșueze tare.

CustomerAnalyzerInterface <- R6::R6Class(
  classname = "CustomerAnalyzerInterface",
  public = list(
    initialize = function() {
      rlang::abort("This class cannot be instantiated.")
    },
    get_customer_metrics = function(customer_id) {
      rlang::abort("Not implemented.")
    },
    calculate_risk_score = function(metrics) {
      rlang::abort("Not implemented.")
    },
    generate_recommendations = function(customer_id, risk_score) {
      rlang::abort("Not implemented.")
    }
  )
)

Apoi, în funcție de fluxul dvs. de lucru, creați implementări concrete, fie reale, fie false.

Alege -ți fluxul de lucru

Pe dos

Dacă doriți să blocați logica de afaceri, scrieți teste pentru implementarea reală și implementați -o. S -ar putea să merite să verificați dacă ceea ce trebuie să calculați este chiar posibil.

  • Puteți obține datele de care aveți nevoie?
  • Puteți rula calculele într -un moment rezonabil?

Această abordare ar putea duce la o implementare reală mai complexă, dar veți ști că funcționează. Puteți scrie întotdeauna un adaptor pentru a servi datele din aplicație mai târziu.

Afară în

Dacă doriți să funcționați aplicația, scrieți implementarea falsă pentru a servi date false în aplicație. Concentrați -vă pe experiența utilizatorului și dezvoltatorului.

  • Utilizatorii pot obține ceea ce au nevoie în aplicație?
  • Cum ar trebui să arate UI?
  • Ce interfață de cod simplifică servirea interfeței?

Ceea ce înveți în această etapă poate informa mai târziu implementarea reală.

Implementarea reală

Clasa dvs. de producție face ridicarea grea. Se conectează la baze de date, apelează la API -uri, rulează calcule costisitoare.

CustomerAnalyzer <- R6::R6Class(
  classname = "CustomerAnalyzer",
  inherit = CustomerAnalyzerInterface,
  public = list(
    initialize = function(...) {
      # Set up whatever is needed to work in production
    },
    get_customer_metrics = function(customer_id) {
      # Query production database
      # Call analytics service
      # Return complex calculations
    },
    calculate_risk_score = function(metrics) {
      # Run ML model prediction
      # Factor in market conditions
      # Return weighted score
    },
    generate_recommendations = function(customer_id, risk_score) {
      # Query recommendation engine
      # Apply business rules
      # Return personalized actions
    }
  )
)

Implementarea falsă pentru testare

Testarea are nevoie de control. Fake returnează valorile cunoscute de fiecare dată.

CustomerAnalyzerFake <- R6::R6Class(
  classname = "CustomerAnalyzerFake",
  inherit = CustomerAnalyzerInterface,
  public = list(
    initialize = function() {},
    get_customer_metrics = function(customer_id) {
      list(
        revenue = 5000,
        retention = 0.85,
        satisfaction = 4.2
      )
    },
    calculate_risk_score = function(metrics) {
      0.3
    },
    generate_recommendations = function(customer_id, risk_score) {
      c("Schedule follow-up call", "Send satisfaction survey")
    }
  )
)

Creați o fabrică pentru instantaneu

Implementați a make_/create_,build_,get_/new_ funcție care alege și inițializează implementarea corectă. Variabilele de mediu sau fișierele de configurare controlează alegerea.

make_customer_analyzer <- function(
  type = c("real", "fake"),
  ... # additional parameters for initialization
) {
  type <- match.arg(type)
  switch(
    type,
    real = CustomerAnalyzerReal$new(),
    fake = CustomerAnalyzerFake$new()
  )
}

Aplicația strălucitoare rămâne curată și testabilă

Logica de afaceri depinde de interfață. Detaliile de implementare se ascund în spatele fabricii. Dependențele externe devin schimbate. Puteți dezvolta în continuare aplicația dacă DB -ul de producție a scăzut.

server <- function(input, output, session) {
  analyzer_type <- Sys.getenv("ANALYZER_TYPE", "fake")
  analyzer <- make_customer_analyzer(analyzer_type)

  output$metrics <- renderPlot({
    req(input$customer_id)
    metrics <- analyzer$get_customer_metrics(input$customer_id)
    chart(metrics)
  })

  # ... other server logic
}

Testarea devine simplă

Folosiți același lucru pentru a crea instanța în testele dvs. ca în producție. Aceasta este interfața publică a logicii de afaceri, nu a claselor în sine.

Testează că logica este corectă în implementarea reală:

test_that("high risk customers get urgent recommendations", {
  # Arrange
  analyzer <- make_customer_analyzer("real")

  # Act
  recommendations <- analyzer$generate_recommendations("customer-123", 0.9)

  # Assert
  expect_set_equal(recommendations, c("Immediate outreach"))
})

Testează că falsul îndeplinește contractul de interfață:

test_that("fake customer analyzer returns a recommendation", {
  # Arrange
  analyzer <- make_customer_analyzer("fake")

  # Act
  recommendations <- analyzer$generate_recommendations("123", 0.3)

  # Assert
  expect_s3_class(recommendations, "character")
})

Pentru testarea aplicației în sine, utilizați implementarea falsă pentru a servi date previzibile. Mai ales dacă depinde de dependențele externe fragile. Fie API -uri, baze de date sau sisteme de fișiere.

test_that("app shows customer metrics", {
  # Arrange
  withr::with_envvar(c(ANALYZER_TYPE = "fake"), {
    app <- AppDriver$new("path/to/app")
  })

  # Act
  app$set_inputs(customer_id = "123")

  # Assert
  output <- app$get_value("metrics")
  expect_true(is.character(output$src))
})

Nu testați aceeași logică de două ori. Folosiți falsuri pentru testarea acceptării. Veți obține o separare mai bună de preocupări, teste mai rapide și mai fiabile.

Testele de acceptare controlează scenariile complete

Același cod care rulează în producție rulează în testele tale. Doar sursa de date se modifică.

Acest model transformă aplicațiile fragile în sisteme testabile. Logica dvs. de afaceri rămâne izolată. Dependențele devin schimbabile. Testele funcționează rapid și fiabil.

Nu mai lupta cu codul încurcat. Începeți să proiectați cu interfețe.

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.