Ricerca nel sito web

Test continui con microservizi (con esempi)


Quando l'architettura dei microservizi è la strada da percorrere?

Scegliere e progettare l'architettura corretta per un sistema è fondamentale. È necessario garantire la qualità dei requisiti del servizio e la gestione dei requisiti non funzionali, come manutenibilità, estensibilità e testabilità.

L’architettura dei microservizi è una scelta piuttosto ricorrente negli ecosistemi più recenti dopo che le aziende hanno adottato Agile e DevOps. Pur non essendo una scelta di fatto, quando si ha a che fare con sistemi in forte crescita e dove non è più possibile mantenere un’architettura monolitica, è una delle opzioni preferite. Mantenere i componenti orientati ai servizi e liberamente accoppiati consente uno sviluppo continuo e cicli di rilascio continui. Ciò spinge le aziende a testare e aggiornare costantemente il proprio software.

I principali prerequisiti che richiedono tale architettura sono:

  • Progettazione basata sul dominio
  • Distribuzione continua e cultura DevOps
  • Isolamento dei guasti
  • Decentramento

Ha i seguenti vantaggi:

  • Proprietà della squadra
  • Rilasci frequenti
  • Manutenzione più semplice
  • Aggiornamenti più semplici alle versioni più recenti
  • Agnostico dalla tecnologia

Ha i seguenti svantaggi:

  • meccanismi di comunicazione da microservizio a microservizio
  • L’aumento del numero di servizi aumenta la complessità complessiva del sistema

Quanto più l'architettura è distribuita e complessa, tanto più difficile è garantire che il sistema possa essere ampliato e mantenuto controllando costi e rischi. Una transazione commerciale potrebbe comportare più combinazioni di protocolli e tecnologie. Non si tratta solo dei casi d’uso ma anche delle sue operazioni. Quando si adottano approcci Agile e DevOps, è necessario trovare un equilibrio tra flessibilità e funzionalità con l'obiettivo di ottenere revisioni e test continui.

L'importanza delle strategie di test in relazione ai microservizi

L'adozione di DevOps in un'organizzazione mira a eliminare i vari dipartimenti isolati e spostarsi verso un team globale. Questa mossa mira a migliorare in modo specifico le relazioni e i processi tra il team del software e il team delle operazioni. Fornire a un ritmo più rapido significa anche garantire che vengano effettuati test continui come parte della pipeline di distribuzione del software. La distribuzione giornaliera (e in alcuni casi anche ogni paio d'ore) è uno degli obiettivi principali per una distribuzione rapida di soluzioni aziendali end-to-end. Affidabilità e sicurezza devono essere tenute presenti in questo caso, ed è qui che entrano in gioco i test.

L'inclusione dello sviluppo basato sui test è l'unico modo per ottenere la reale certezza che il codice sia pronto per la produzione. Casi di test validi aggiungono valore al sistema poiché convalidano e documentano il sistema stesso. Oltre a ciò, una buona copertura del codice incoraggia miglioramenti e assiste durante il refactoring.

L’architettura dei microservizi decentralizza i canali di comunicazione, il che rende i test più complicati. Non è un problema insormontabile. Un team che possiede un microservizio non dovrebbe aver paura di introdurre modifiche perché potrebbero danneggiare le applicazioni client esistenti. I test manuali sono molto inefficienti, considerando che l'integrazione e la distribuzione continue rappresentano le migliori pratiche attuali. Gli ingegneri DevOps dovrebbero assicurarsi di includere test di automazione nel loro flusso di lavoro di sviluppo: scrivere test, aggiungere/refactoring codice ed eseguire test.

Metodi comuni di test dei microservizi

La piramide dei test è un concetto semplice che ci aiuta a identificare lo sforzo richiesto durante la scrittura dei test e dove il numero di test dovrebbe diminuire se diminuisce la granularità. Si applica anche quando si considerano i test continui per i microservizi.

Per rendere l'argomento più concreto, affronteremo il test di un microservizio di esempio utilizzando Spring Boot e Java. Le architetture di microservizi, per costruzione, sono più complicate dell'architettura monolitica. Tuttavia, manterremo l'attenzione sulla tipologia dei test e non sull'architettura. I nostri snippet si basano su un progetto minimo composto da un microservizio basato su API che possiede un archivio dati che utilizza MongoDB.

Test unitari

I test unitari dovrebbero costituire la maggior parte dei test poiché sono veloci, affidabili e facili da mantenere. Questi test sono anche chiamati test white-box. L'ingegnere che li implementa ha familiarità con la logica e sta scrivendo il test per convalidare le specifiche del modulo e verificare la qualità del codice.

Il focus di questi test è una piccola parte del sistema in isolamento, ovvero la Class Under Test (CUT). Il principio di responsabilità unica è una buona linea guida su come gestire il codice relativo alla funzionalità.

La forma più comune di test unitario è un “test unitario solitario”. “Non oltrepassa alcun confine e non ha bisogno di alcun collaboratore oltre al CUT.

Come sottolineato da Bill Caputo, i database, i canali di messaggistica o altri sistemi rappresentano i confini; qualsiasi classe aggiuntiva utilizzata o richiesta dal CUT è un collaboratore. Un test unitario non dovrebbe mai oltrepassare un limite. Quando ci si avvale di collaboratori si scrive uno “unit test socievole”. L'utilizzo di mock per le dipendenze utilizzati dal CUT è un modo per testare il codice socievole con un "test unitario solitario". "

Nei modelli di sviluppo software tradizionali, i test degli sviluppatori non erano ancora ampiamente adottati, dovendo eseguire i test completamente fuori sincronia dallo sviluppo. Il raggiungimento di un punteggio elevato di copertura del codice era considerato un indicatore chiave della fiducia della suite di test.

Con l’introduzione dell’Agile e dei cicli iterativi brevi, è evidente che i modelli di test precedenti non funzionano più. Si prevedono continui cambiamenti frequenti. È molto più importante testare il comportamento osservabile piuttosto che coprire tutti i percorsi del codice. I test unitari dovrebbero riguardare più le asserzioni che la copertura del codice, perché lo scopo è verificare che la logica funzioni come previsto.

È inutile avere un componente con molti test e un'alta percentuale di copertura del codice quando i test non hanno asserzioni corrette. L'applicazione di un approccio BDD (Beavior-Driven Development) garantisce che i test verifichino lo stato finale e che il comportamento corrisponda ai requisiti stabiliti dall'azienda. Il vantaggio di avere test mirati con un ambito ben definito è che diventa più semplice identificare la causa del fallimento. I test BDD ci danno una maggiore sicurezza che il fallimento sia una conseguenza di un cambiamento nel comportamento delle caratteristiche. I test che altrimenti si concentrano maggiormente sulla copertura del codice non possono offrire molta sicurezza poiché ci sarebbe un rischio maggiore che il fallimento sia una ripercussione per le modifiche apportate ai test stessi a causa dei dettagli di implementazione dei percorsi del codice.

I test dovrebbero seguire il suggerimento di Martin Fowler quando afferma quanto segue (in Refactoring: Improving the Design of Existing Code, Seconda edizione. Kent Beck e Martin Fowler. Addison-Wesley. 2018):

Un altro motivo per concentrarsi meno sui dettagli minori dell’implementazione è il refactoring. Durante il refactoring, gli unit test dovrebbero essere lì per darci fiducia e non rallentare il lavoro. Una modifica nell'implementazione del collaboratore potrebbe comportare un fallimento del test, che potrebbe rendere i test più difficili da mantenere. Si consiglia vivamente di mantenere un minimo di unit test socievoli. Ciò è particolarmente vero quando tali test potrebbero rallentare il ciclo di vita dello sviluppo con la possibilità che i test finiscano per essere ignorati. Una situazione eccellente per includere un test unitario socievole è il test negativo, soprattutto quando si ha a che fare con la verifica del comportamento.

Prove di integrazione

Una delle sfide più significative con i microservizi è testare la loro interazione con il resto dei servizi infrastrutturali, ovvero i confini da cui dipende il particolare CUT, come database o altri servizi. La piramide dei test mostra chiaramente che i test di integrazione dovrebbero essere meno di test unitari ma più di test di componenti ed end-to-end. Questi altri tipi di test potrebbero essere più lenti, più difficili da scrivere e gestire ed essere piuttosto fragili rispetto ai test unitari. Il superamento dei limiti potrebbe avere un impatto sulle prestazioni e sui tempi di esecuzione a causa dell'accesso alla rete e al database; tuttavia, sono indispensabili, soprattutto nella cultura DevOps.

In un ambito di distribuzione continua, sono preferiti test di integrazione ristretti anziché test di integrazione ampi. Quest'ultimo è molto vicino ai test end-to-end in cui richiede l'effettiva esecuzione del servizio anziché l'uso di un doppio test di tali servizi per testare le interazioni del codice. L'obiettivo principale da raggiungere è costruire test operativi gestibili in modo veloce, facile e resiliente. I test di integrazione si concentrano sull'interazione del CUT con un servizio alla volta. Il nostro focus è sui test di integrazione ristretta. La verifica dell'interazione tra una coppia di servizi può essere confermata come prevista, dove i servizi possono essere un servizio di infrastruttura o qualsiasi altro servizio.

Test di persistenza

>

Un tipo di test controverso avviene quando si testa il livello di persistenza, con l'obiettivo principale di testare le query e l'effetto sui dati di test. Un'opzione è l'uso di database in memoria. Alcuni potrebbero considerare l'uso dei database in memoria come uno unit test socievole poiché è un test autonomo, idempotente e veloce. Il test viene eseguito sul database creato con la configurazione desiderata. Dopo l'esecuzione dei test e la verifica delle asserzioni, l'archivio dati viene automaticamente cancellato una volta chiusa la JVM a causa della sua natura effimera. Tieni presente che è ancora in corso una connessione a un servizio diverso ed è considerato un test di integrazione ristretto. In un approccio TDD (Test-Driven Development), tali test sono essenziali poiché le suite di test dovrebbero essere eseguite in pochi secondi. I database in memoria rappresentano un valido compromesso per garantire che i test vengano mantenuti il più velocemente possibile e non ignorati nel lungo periodo.

@Before
public void setup() throws Exception {
   try {
	// this will download the version of mongo marked as production. One should
	// always mention the version that is currently being used by the SUT
	String ip = "localhost";
	int port = 27017;

	IMongodConfig mongodConfig = new MongodConfigBuilder().version(Version.Main. PRODUCTION)
		.net(new Net(ip, port, Network.localhostIsIPv6())).build();

	MongodStarter starter = MongodStarter.getDefaultInstance();
mongodExecutable = starter.prepare(mongodConfig);
	mongodExecutable.start();

   } catch (IOException e) {
	e.printStackTrace();
   }
}

Snippet 1: Installazione e avvio di In-memory MongoDB

Quanto sopra non è un test di integrazione completo poiché un database in memoria non si comporta esattamente come il server del database di produzione. Non si tratta quindi di una replica del “vero” server mongo, come sarebbe il caso se si optassero per test di integrazione ampi.

Un'altra opzione per i test di integrazione della persistenza è quella di eseguire test ampi connessi a un server di database reale o con l'uso di contenitori. I contenitori alleviano il problema poiché, su richiesta, si effettua il provisioning del database, rispetto ad avere un server fisso. Tieni presente che tali test richiedono molto tempo e la categorizzazione dei test è una possibile soluzione. Poiché questi test dipendono da un altro servizio diverso dal CUT, è considerato un test di sistema. Questi test sono ancora essenziali e, utilizzando le categorie, è possibile determinare meglio quando eseguire test specifici per ottenere il miglior equilibrio tra costo e valore. Ad esempio, durante il ciclo di sviluppo, è possibile eseguire solo i test di integrazione ristretti utilizzando il database in memoria. Le build notturne potrebbero anche eseguire test che rientrano in una categoria come i test di integrazione ampia.

@Category(FastIntegration.class)
@RunWith(SpringRunner.class)
@DataMongoTest
public class DailyTaskRepositoryInMemoryIntegrationTest {
	. . . 
}

@Category(SlowIntegration.class)
@RunWith(SpringRunner.class)
@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class DailyTaskRepositoryIntegrationTest {
   ...
}

Snippet 2: Utilizzo delle categorie per differenziare i tipi di test di integrazione

Test guidati dai consumatori

I meccanismi di comunicazione interprocesso (IPC) rappresentano un aspetto centrale dei sistemi distribuiti basati su un'architettura a microservizi. Questa configurazione solleva varie complicazioni durante la creazione delle suite di test. In aggiunta a ciò, in un team Agile, i cambiamenti sono continuamente in corso, inclusi cambiamenti nelle API o negli eventi. Indipendentemente dal meccanismo IPC utilizzato dal sistema, esiste la presenza di un contratto tra due servizi qualsiasi. Esistono vari tipi di contratti, a seconda del meccanismo che si sceglie di utilizzare nel sistema. Quando si utilizzano le API, il contratto è la richiesta e risposta HTTP, mentre nel caso di un sistema basato su eventi, il contratto è l'evento del dominio stesso.

Uno degli obiettivi principali quando si testano i microservizi è garantire che tali contratti siano ben definiti e stabili in qualsiasi momento. In un approccio TDD top-down, questi sono i primi test da coprire. Un test di integrazione fondamentale garantisce che il consumatore abbia un feedback rapido non appena un cliente non corrisponde allo stato reale del produttore con cui sta parlando.

Questi test dovrebbero far parte della normale pipeline di distribuzione. Il loro fallimento consentirebbe ai consumatori di prendere coscienza che è avvenuto un cambiamento da parte del produttore e che sono necessari cambiamenti per raggiungere nuovamente la coerenza. Senza la necessità di scrivere complessi test end-to-end, i “test dei contratti guidati dal consumatore” mirerebbero a questo caso d’uso.

Quello che segue è un esempio di un verificatore di contratto generato dal plugin spring-cloud-contract.

@Test
public void validate_add_New_Task() throws Exception {
  // given:
   MockMvcRequestSpecification request = given()
.header("Content-Type", "application/json;charset=UTF-8")
	.body("{"taskName":"newTask","taskDescription":"newDescription","isComplete":false,"isUrgent":true}");

  // when:
   ResponseOptions response = given().spec(request).post("/tasks");

  // then:
   assertThat(response.statusCode()).isEqualTo(200);
   assertThat(response.header("Content-Type")).isEqualTo("application/json;charset=UTF-8");
  // and:
   DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
   assertThatJson(parsedJson).field("['taskName']").isEqualTo("newTask");
   assertThatJson(parsedJson).field("['isUrgent']").isEqualTo(true);
   assertThatJson(parsedJson).field("['isComplete']").isEqualTo(false);
   assertThatJson(parsedJson).field("['id']").isEqualTo("3");
   assertThatJson(parsedJson).field("['taskDescription']").isEqualTo("newDescription");
}

Snippet 3: Contract Verifier generato automaticamente dal plug-in spring-cloud-contract

Una BaseClass scritta nel produttore indica quale tipo di risposta aspettarsi sui vari tipi di richieste utilizzando la configurazione autonoma. La raccolta confezionata di stub è disponibile a tutti i consumatori per poterli inserire nella loro implementazione. La complessità sorge quando più consumatori si avvalgono dello stesso contratto; pertanto, il produttore deve avere una visione globale dei contratti di servizio richiesti.

@RunWith(SpringRunner.class)
@SpringBootTest
public class ContractBaseClass {

	@Autowired
	private DailyTaskController taskController;

	@MockBean
	private DailyTaskRepository dailyTaskRepository;

	@Before
	public void before() {
		RestAssuredMockMvc.standaloneSetup(this.taskController);
		Mockito.when(this.dailyTaskRepository.findById("1")).thenReturn(
Optional.of(new DailyTask("1", "Test", "Description", false, null)));
		
		. . . 
				
		Mockito.when(this.dailyTaskRepository.save(
new DailyTask(null, "newTask", "newDescription", false, true))).thenReturn(
new DailyTask("3", "newTask", "newDescription", false, true));
		
	}

Snippet 4: BaseClass del produttore che definisce la risposta prevista per ogni richiesta

Dal lato del consumatore, con l'inclusione della dipendenza spring-cloud-starter-contratto-stub-runner, abbiamo configurato il test per utilizzare il binario stub. Questo test verrebbe eseguito utilizzando gli stub generati dal produttore secondo la configurazione con la versione specificata o sempre la più recente. L'artefatto stub collega il cliente al produttore per garantire che entrambi stiano lavorando allo stesso contratto. Qualsiasi cambiamento che si verifica si rifletterebbe in tali test e, quindi, il consumatore identificherebbe se il produttore è cambiato o meno.

@SpringBootTest(classes = TodayAskApplication.class)
@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(ids = "com.cwie.arch:today:+:stubs:8080", stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class TodayClientStubTest {
 	 . . .
	@Test
	public void addTask_expectNewTaskResponse () {
		Task newTask = todayClient.createTask(
new Task(null, "newTask", "newDescription", false, true));
		BDDAssertions.then(newTask).isNotNull();
		BDDAssertions.then(newTask.getId()).isEqualTo("3");
		. . . 
		
	}
}

Snippet 5: Il consumatore inserisce la versione stub definita dal produttore

Tali test di integrazione verificano che l’API di un fornitore sia ancora in linea con le aspettative dei consumatori. Quando si utilizzavano test unitari derisi per le API, avremmo stubbato le API e deriso il comportamento. Dal punto di vista del consumatore, questi tipi di test garantiranno che il cliente soddisfi le nostre aspettative. È essenziale notare che se il produttore modifica l'API, tali test non falliranno. Ed è imperativo definire cosa copre il test.

// the response we expect is represented in the task1.json file
private Resource taskOne = new ClassPathResource("task1.json");

@Autowired
private TodayClient todayClient;

@Test
public void createNewTask_expectTaskIsCreated() {
WireMock.stubFor(WireMock.post(WireMock.urlMatching("/tasks"))
		.willReturn(WireMock.aResponse()
		.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
		.withStatus(HttpStatus.OK.value())
		.withBody(transformResourceJsonToString(taskOne))));
		
	Task tasks = todayClient.createTask(new Task(null, "runUTest", "Run Test", false, true));
	BDDAssertions.then(tasks.getId()).isEqualTo("1");

Snippet 6: Un test del consumatore che fa asserzioni sulla propria risposta definita

Prove sui componenti

L'architettura dei microservizi può crescere rapidamente e quindi il componente sotto test potrebbe integrarsi con più altri componenti e più servizi infrastrutturali. Fino ad ora, abbiamo coperto i test white-box con test unitari e test di integrazione ristretti per testare il CUT che attraversa il confine per integrarsi con un altro servizio.

Il tipo più veloce di test dei componenti è l'approccio in-process, in cui, oltre all'uso di test doppi e archivi dati in memoria, il test rimane entro i limiti. Lo svantaggio principale di questo approccio è che il servizio di produzione distribuibile non è completamente testato; al contrario, il componente necessita di modifiche per cablare diversamente l'applicazione. Il metodo preferito è il test dei componenti fuori processo. Sono come test end-to-end, ma con tutti i collaboratori esterni sostituiti con test doppi, così facendo esercita l'artefatto completamente distribuito utilizzando chiamate di rete reali. Il test sarebbe responsabile della corretta configurazione di eventuali servizi esterni come stub.

@Ignore
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { TodayConfiguration.class, TodayIntegrationApplication.class,
   CloudFoundryClientConfiguration.class })
public class BaseFunctionalitySteps {

   @Autowired
   private CloudFoundryOperations cf;

   private static File manifest = new File(".manifest.yml");

   @Autowired
   private TodayClient client;

   // Any stubs required 
   . . . 

   public void setup() {

cf.applications().pushManifest(PushApplicationManifestRequest.builder() 
 
 .manifest(ApplicationManifestUtils.read(manifest.toPath()).get(0)).build()).block();
}
. . .
// Any calls required by tests
public void requestForAllTasks() {
this.client.getTodoTasks();
}
}

Snippet 7: Distribuzione del manifest su CloudFoundry ed eventuali chiamate richieste dai test

Cloud Foundry è una delle opzioni utilizzate per le architetture di test basate su contenitori. "È una piattaforma applicativa cloud open source che semplifica e velocizza la creazione, il test, la distribuzione e la scalabilità delle applicazioni. ” Quello che segue è il manifest.yml, un file che definisce la configurazione di tutte le applicazioni nel sistema. Questo file viene utilizzato per distribuire il servizio effettivo nel formato pronto per la produzione nello spazio dell'organizzazione Pivotal in cui il servizio MongoDB è già configurato, corrispondente alla versione di produzione.

---
applications:
- name: today
  instances: 1
  path: ../today/target/today-0.0.1-SNAPSHOT.jar 
  memory: 1024M
  routes:
  - route: today.cfapps.io
  services:
  - mongo-it

Snippet 8: Distribuzione di un'istanza del servizio in base al servizio mongo

Quando si opta per l'approccio out-of-process, tenere presente che i confini effettivi sono sotto test e, pertanto, i test finiscono per essere più lenti poiché esistono interazioni di rete e database. L'ideale sarebbe avere queste suite di test scritte in un modulo separato. Per poterli eseguire separatamente in una fase diversa invece della solita fase di "test".

Poiché l'enfasi dei test è sul componente stesso, i test coprono le responsabilità primarie del componente trascurando di proposito qualsiasi altra parte del sistema.

Cucumber, uno strumento software che supporta lo sviluppo basato sul comportamento, è un'opzione per definire tali test comportamentali. Con il suo parser in linguaggio semplice, Gherkin, garantisce che i clienti possano comprendere facilmente tutti i test descritti. Il seguente file di funzionalità di Cucumber garantisce che l'implementazione del nostro componente soddisfi i requisiti aziendali per quella particolare funzionalità.

Feature: Tasks

Scenario: Retrieving one task from list
 Given the component is running
 And the data consists of one or more tasks
 When user requests for task x
 Then the correct task x is returned

Scenario: Retrieving all lists
 Given the data consists of one or more tasks
 When user requests for all tasks
 Then all tasks in database are returned

Scenario: Negative Test
 Given the component is not running
 When user requests for task x it fails with response 404

Snippet 9: un file di funzionalità che definisce i test BDD

Test end-to-end

Similmente ai test dei componenti, lo scopo di questi test end-to-end non è eseguire la copertura del codice ma garantire che il sistema soddisfi gli scenari aziendali richiesti. La differenza è che nei test end-to-end tutti i componenti sono attivi e funzionanti durante il test.

Secondo il diagramma piramidale dei test, il numero di test end-to-end diminuisce ulteriormente, tenendo conto della lentezza che potrebbero causare. Il primo passaggio è eseguire la configurazione e, per questo esempio, sfrutteremo docker.

version: '3.7'
services:
    today-app:
        image: today-app:1
        container_name: "today-app"
        build:
          context: ./
          dockerfile: DockerFile
        environment:
           - SPRING_DATA_MONGODB_HOST=mongodb
        volumes:
          - /data/today-app
        ports:
          - "8082:8080"
        links:
          - mongodb
        depends_on:
          - mongodb

    mongodb:
        image: mongo:3.2
        container_name: "mongodb"
        restart: always
        environment:
           - AUTH=no
           - MONGO_DATA_DIR=/data/db
           - MONGO_LOG_DIR=/dev/log
        volumes:
           - ./data:/data
        ports:
           - 27017:27017
        command: mongod --smallfiles --logpath=/dev/null # --quiet

Snippet 10: La definizione docker.yml utilizzata per distribuire il servizio definito e la versione specificata di mongo come contenitori

Per quanto riguarda i test dei componenti, è opportuno mantenere i test end-to-end in un modulo separato e in fasi diverse. Il exec-maven-plugin è stato utilizzato per distribuire tutti i componenti richiesti, eseguire i nostri test e infine pulire e smontare il nostro ambiente di test.

Poiché si tratta di un test di tipo ampio, verrà eseguita una selezione più piccola di test per funzionalità. I test vengono selezionati in base al rischio aziendale percepito. I tipi precedenti di test coprivano dettagli di basso livello. Ciò significa se una storia utente corrisponde ai criteri di accettazione. Questi test dovrebbero anche interrompere immediatamente un rilascio, poiché un fallimento in questo caso potrebbe causare gravi ripercussioni aziendali.

Conclusione

I test incentrati sull'handoff spesso finiscono per essere un processo molto lungo, che richiede fino a settimane prima che tutti i bug vengano identificati, risolti e sia pronta una nuova distribuzione. Il feedback viene ricevuto solo dopo che è stato effettuato un rilascio, rendendo la durata di vita di una versione il più breve possibile.

L'approccio di test continuo garantisce un feedback immediato. Ciò significa che l'ingegnere DevOps è immediatamente consapevole se la funzionalità implementata è pronta per la produzione o meno, a seconda dell'esito dei test eseguiti. Dai test unitari fino ai test end-to-end, tutti aiutano ad accelerare il processo di valutazione.

L'architettura dei microservizi aiuta a creare implementazioni più rapide nella produzione poiché è basata sul dominio. Garantisce l'isolamento degli errori e aumenta la proprietà. Quando più team lavorano sullo stesso progetto, c’è un altro motivo per adottare tale architettura: garantire che i team siano indipendenti e non interferiscano reciprocamente con il lavoro.

Migliora la testabilità passando a test continui. Ogni microservizio ha un dominio ben definito e il suo ambito dovrebbe essere limitato a un attore. I casi di test applicati sono specifici e più concisi e i test sono isolati, facilitando rilasci e implementazioni più rapide.

Seguendo l'approccio TDD, non vi è alcuna codifica a meno che non venga restituito un test fallito. Questo processo aumenta la fiducia una volta che un'implementazione iterativa si traduce in una prova di successo. Questo processo implica che i test avvengano parallelamente all'effettiva implementazione e che tutti i test sopra menzionati vengano eseguiti prima che le modifiche raggiungano un ambiente di staging. I test continui continuano ad evolversi fino alla fase di rilascio successiva, ovvero un ambiente di staging, in cui l'attenzione si sposta su test più esaustivi come i test di carico.

Agile, DevOps e distribuzione continua richiedono test continui. Il vantaggio principale è il feedback immediato prodotto dai test automatizzati. Le possibili ripercussioni potrebbero influenzare l’esperienza dell’utente ma avere anche conseguenze aziendali ad alto rischio. Per ulteriori informazioni sui test continui, contatta phoenixNAP oggi stesso.

Articoli correlati