Pesquisa de site

Testes Contínuos com Microsserviços (com Exemplos)


Quando a arquitetura de microsserviços é o caminho a seguir?

Escolher e projetar a arquitetura correta para um sistema é fundamental. É preciso garantir a qualidade dos requisitos de serviço e o tratamento de requisitos não funcionais, como manutenibilidade, extensibilidade e testabilidade.

A arquitetura de microsserviços é uma escolha bastante recorrente nos ecossistemas mais recentes depois que as empresas adotaram Agile e DevOps. Embora não seja uma escolha de facto, quando se trata de sistemas que estão em crescimento extensivo e onde uma arquitetura monolítica não é mais viável de manter, é uma das opções preferidas. Manter os componentes orientados a serviços e fracamente acoplados permite desenvolvimento contínuo e ciclos de lançamento contínuos. Isso leva as empresas a testar e atualizar constantemente seus softwares.

Os principais pré-requisitos que exigem tal arquitetura são:

  • Design Orientado a Domínio
  • Entrega Contínua e Cultura DevOps
  • Isolamento de falha
  • Descentralização

Tem os seguintes benefícios:

  • Propriedade da equipe
  • Lançamentos frequentes
  • Manutenção mais fácil
  • Atualizações mais fáceis para versões mais recentes
  • Agnóstico em tecnologia

Tem os seguintes contras:

  • mecanismos de comunicação de microsserviço para microsserviço
  • Aumentar o número de serviços aumenta a complexidade geral do sistema

Quanto mais distribuída e complexa for a arquitetura, mais desafiador será garantir que o sistema possa ser expandido e mantido, controlando custos e riscos. Uma transação comercial pode envolver múltiplas combinações de protocolos e tecnologias. Não se trata apenas dos casos de uso, mas também de suas operações. Ao adotar abordagens Agile e DevOps, deve-se encontrar um equilíbrio entre flexibilidade versus funcionalidade com o objetivo de obter revisão e testes contínuos.

A importância das estratégias de teste em relação aos microsserviços

A adoção do DevOps em uma organização visa eliminar os vários departamentos isolados e avançar para uma equipe geral. Essa mudança busca especificamente melhorar os relacionamentos e processos entre a equipe de software e a equipe de operações. Entregar em um ritmo mais rápido também significa garantir que haja testes contínuos como parte do pipeline de entrega de software. A implantação diária (e em alguns casos até a cada duas horas) é um dos principais objetivos para a entrega rápida de soluções de negócios de ponta a ponta. A confiabilidade e a segurança devem ser mantidas em mente aqui, e é aí que entram os testes.

A inclusão do desenvolvimento orientado a testes é a única maneira de obter confiança genuína de que o código está pronto para produção. Os casos de teste válidos agregam valor ao sistema, pois validam e documentam o próprio sistema. Além disso, uma boa cobertura de código incentiva melhorias e auxilia durante a refatoração.

A arquitetura de microsserviços descentraliza os canais de comunicação, o que torna os testes mais complicados. Não é um problema intransponível. Uma equipe que possui um microsserviço não deve ter medo de introduzir alterações, pois elas podem quebrar os aplicativos clientes existentes. O teste manual é muito ineficiente, considerando que a integração e a implantação contínuas são as melhores práticas atuais. Os engenheiros de DevOps devem garantir a inclusão de testes de automação em seu fluxo de trabalho de desenvolvimento: escrever testes, adicionar/refatorar código e executar testes.

Métodos comuns de teste de microsserviços

A pirâmide de testes é um conceito fácil que nos ajuda a identificar o esforço necessário ao escrever testes e onde o número de testes deve diminuir se a granularidade diminuir. Também se aplica ao considerar testes contínuos para microsserviços.

Para tornar o tópico mais concreto, abordaremos o teste de um microsserviço de amostra usando Spring Boot e Java. As arquiteturas de microsserviços, por construção, são mais complicadas do que a arquitetura monolítica. No entanto, manteremos o foco no tipo de testes e não na arquitetura. Nossos snippets são baseados em um projeto mínimo composto por um microsserviço baseado em API que possui um armazenamento de dados usando MongoDB.

Testes unitários

Os testes unitários devem ser a maioria dos testes, pois são rápidos, confiáveis e fáceis de manter. Esses testes também são chamados de testes de caixa branca. O engenheiro que os implementa está familiarizado com a lógica e escreve o teste para validar as especificações do módulo e verificar a qualidade do código.

O foco desses testes é uma pequena parte do sistema isoladamente, ou seja, a Classe em Teste (CUT). O Princípio da Responsabilidade Única é uma boa diretriz sobre como gerenciar código relacionado à funcionalidade.

A forma mais comum de teste unitário é um “teste unitário solitário”. “Não ultrapassa fronteiras e não precisa de colaboradores além da CUT.

Conforme descrito por Bill Caputo, bancos de dados, canais de mensagens ou outros sistemas são os limites; qualquer aula adicional utilizada ou exigida pela CUT é colaboradora. Um teste de unidade nunca deve ultrapassar um limite. Ao fazer uso de colaboradores, está-se escrevendo “testes de unidade sociáveis. ” Usar mocks para dependências usadas pelo CUT é uma forma de testar código sociável com um “teste de unidade solitário”. ”

Nos modelos tradicionais de desenvolvimento de software, os testes do desenvolvedor ainda não foram amplamente adotados, tendo que testar completamente fora de sincronia com o desenvolvimento. Alcançar uma alta classificação de cobertura de código foi considerado um indicador-chave da confiança do conjunto de testes.

Com a introdução do Agile e de ciclos iterativos curtos, fica evidente agora que os modelos de teste anteriores não funcionam mais. Mudanças frequentes são esperadas continuamente. É muito mais importante testar o comportamento observável do que cobrir todos os caminhos de código. Os testes de unidade devem ser mais sobre asserções do que sobre cobertura de código, porque o objetivo é verificar se a lógica está funcionando conforme o esperado.

De nada adianta ter um componente com muitos testes e uma alta porcentagem de cobertura de código quando os testes não possuem asserções adequadas. A aplicação de uma abordagem de Desenvolvimento Orientado ao Comportamento (BDD) garante que os testes verifiquem o estado final e que o comportamento corresponda aos requisitos definidos pelo negócio. A vantagem de ter testes focados e com escopo bem definido é que fica mais fácil identificar a causa da falha. Os testes BDD nos dão maior confiança de que a falha foi consequência de uma mudança no comportamento do recurso. Os testes que, de outra forma, se concentram mais na cobertura do código não podem oferecer muita confiança, pois haveria um risco maior de que a falha fosse uma repercussão nas alterações feitas nos próprios testes devido aos detalhes de implementação dos caminhos do código.

Os testes devem seguir a sugestão de Martin Fowler quando ele declarou o seguinte (em Refactoring: Improving the Design of Existing Code, Second Edition. Kent Beck, and Martin Fowler. Addison-Wesley. 2018):

Outro motivo para focar menos em pequenos detalhes de implementação é a refatoração. Durante a refatoração, os testes unitários devem estar presentes para nos dar confiança e não atrasar o trabalho. Uma mudança na implementação do colaborador pode resultar em uma falha no teste, o que pode dificultar a manutenção dos testes. É altamente recomendável manter um mínimo de testes de unidade sociáveis. Isto é especialmente verdadeiro quando tais testes podem retardar o ciclo de vida de desenvolvimento, com a possibilidade de que os testes acabem sendo ignorados. Uma excelente situação para incluir um teste unitário sociável é o teste negativo, especialmente quando se trata de verificação de comportamento.

Testes de integração

Um dos desafios mais significativos dos microsserviços é testar sua interação com o restante dos serviços de infraestrutura, ou seja, os limites dos quais um CUT específico depende, como bancos de dados ou outros serviços. A pirâmide de testes mostra claramente que os testes de integração devem ser menos que os testes unitários, mas mais que os testes de componentes e de ponta a ponta. Esses outros tipos de testes podem ser mais lentos, mais difíceis de escrever e manter e bastante frágeis quando comparados aos testes unitários. Cruzar limites pode ter impacto no desempenho e no tempo de execução devido ao acesso à rede e ao banco de dados; ainda assim, são indispensáveis, especialmente na cultura DevOps.

Em um escopo de implantação contínua, os testes de integração restritos são favorecidos em vez dos testes de integração amplos. Este último está muito próximo dos testes ponta a ponta, onde requer a execução real do serviço, em vez do uso de um teste duplo desses serviços para testar as interações do código. O principal objetivo a alcançar é construir testes operativos gerenciáveis de forma rápida, fácil e resiliente. Os testes de integração focam na interação do CUT com um serviço por vez. Nosso foco está em testes de integração restritos. A verificação da interação entre um par de serviços pode ser confirmada como esperada, onde os serviços podem ser um serviço de infraestrutura ou qualquer outro serviço.

Testes de persistência

>

Um tipo controverso de teste é o teste da camada de persistência, com o objetivo principal de testar as consultas e o efeito nos dados de teste. Uma opção é o uso de bancos de dados na memória. Alguns podem considerar o uso de bancos de dados na memória como um teste de unidade sociável, uma vez que é um teste independente, idempotente e rápido. O teste é executado no banco de dados criado com a configuração desejada. Depois que as execuções de teste e as asserções forem verificadas, o armazenamento de dados será automaticamente limpo assim que a JVM for encerrada devido à sua natureza efêmera. Tenha em mente que ainda há uma conexão acontecendo com um serviço diferente e isso é considerado um teste de integração restrito. Em uma abordagem de Desenvolvimento Orientado a Testes (TDD), tais testes são essenciais, pois os conjuntos de testes devem ser executados em segundos. Os bancos de dados na memória são uma compensação válida para garantir que os testes sejam mantidos o mais rápido possível e não sejam ignorados no longo prazo.

@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: Instalação e inicialização do MongoDB in-memory

O texto acima não é um teste de integração completo, pois um banco de dados na memória não se comporta exatamente como o servidor de banco de dados de produção. Portanto, não se trata de uma réplica do servidor mongo “real”, o que seria o caso se se optasse por testes de integração amplos.

Outra opção para testes de integração de persistência é executar testes amplos conectados a um servidor de banco de dados real ou com o uso de contêineres. Os contêineres aliviam a dor, pois, mediante solicitação, provisiona-se o banco de dados, em comparação com um servidor fixo. Tenha em mente que esses testes são demorados e categorizar os testes é uma solução possível. Como esses testes dependem de outro serviço executado além do CUT, é considerado um teste de sistema. Esses testes ainda são essenciais e, usando categorias, é possível determinar melhor quando testes específicos devem ser executados para obter o melhor equilíbrio entre custo e valor. Por exemplo, durante o ciclo de desenvolvimento, pode-se executar apenas testes de integração restritos usando o banco de dados na memória. As compilações noturnas também podem executar testes que se enquadram em uma categoria como testes de integração ampla.

@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: Usando categorias para diferenciar os tipos de testes de integração

Testes orientados ao consumidor

Os mecanismos de comunicação entre processos (IPC) são um aspecto central dos sistemas distribuídos baseados em uma arquitetura de microsserviços. Esta configuração levanta várias complicações durante a criação de conjuntos de testes. Além disso, em uma equipe Agile, as mudanças estão continuamente em andamento, incluindo alterações em APIs ou eventos. Não importa qual mecanismo IPC o sistema esteja usando, existe um contrato entre quaisquer dois serviços. Existem vários tipos de contratos, dependendo do mecanismo que se escolhe utilizar no sistema. Ao usar APIs, o contrato é a solicitação e resposta HTTP, enquanto no caso de um sistema baseado em eventos, o contrato é o próprio evento de domínio.

O objetivo principal ao testar microsserviços é garantir que esses contratos estejam bem definidos e estáveis a qualquer momento. Numa abordagem top-down do TDD, estes são os primeiros testes a serem cobertos. Um teste de integração fundamental garante que o consumidor tenha um feedback rápido assim que um cliente não condiz com o real estado do produtor com quem está conversando.

Esses testes devem fazer parte do pipeline de implantação regular. O seu fracasso permitiria que os consumidores tomassem consciência de que ocorreu uma mudança por parte do produtor e que são necessárias mudanças para alcançar novamente a consistência. Sem a necessidade de escrever testes intrincados de ponta a ponta, os “testes de contrato orientados ao consumidor” teriam como alvo este caso de uso.

A seguir está um exemplo de um verificador de contrato gerado pelo 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: Verificador de contrato gerado automaticamente pelo plugin spring-cloud-contract

Uma BaseClass escrita no produtor instrui que tipo de resposta esperar nos vários tipos de solicitações usando a configuração autônoma. A coleção embalada de stubs está disponível para todos os consumidores poderem retirá-los em sua implementação. A complexidade surge quando vários consumidores utilizam o mesmo contrato; portanto, o produtor precisa ter uma visão global dos contratos de serviços exigidos.

@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: A BaseClass do produtor definindo a resposta esperada para cada solicitação

Do lado do consumidor, com a inclusão da dependência spring-cloud-starter-contract-stub-runner, configuramos o teste para usar o binário stubs. Este teste seria executado usando os stubs gerados pelo produtor de acordo com a configuração com a versão especificada ou sempre a mais recente. O artefato stub vincula o cliente ao produtor para garantir que ambos estejam trabalhando no mesmo contrato. Qualquer mudança que ocorresse refletiria nesses testes e, assim, o consumidor identificaria se o produtor mudou ou não.

@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: Consumidor injetando a versão stub definida pelo produtor

Esses testes de integração verificam se a API de um provedor ainda está alinhada com as expectativas dos consumidores. Ao usar testes de unidade simulados para APIs, teríamos stub as APIs e simulado o comportamento. Do ponto de vista do consumidor, estes tipos de testes irão garantir que o cliente corresponde às nossas expectativas. É fundamental observar que se o produtor alterar a API, esses testes não falharão. E é imprescindível definir o que o teste cobre.

// 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: Um teste de consumidor fazendo afirmações sobre sua própria resposta definida

Testes de componentes

A arquitetura de microsserviços pode crescer rapidamente e, portanto, o componente em teste pode estar integrado a vários outros componentes e vários serviços de infraestrutura. Até agora, cobrimos testes de caixa branca com testes de unidade e testes de integração restritos para testar o CUT cruzando a fronteira para integração com outro serviço.

O tipo mais rápido de teste de componentes é a abordagem em processo, onde, juntamente com o uso de testes duplos e armazenamentos de dados na memória, o teste permanece dentro dos limites. A principal desvantagem desta abordagem é que o serviço de produção implantável não é totalmente testado; pelo contrário, o componente requer alterações para conectar o aplicativo de maneira diferente. O método preferido é o teste de componentes fora do processo. São como testes ponta a ponta, mas com todos os colaboradores externos trocados por testes duplos, ao fazer isso, ele exercita o artefato totalmente implantado fazendo uso de chamadas de rede reais. O teste seria responsável por configurar adequadamente quaisquer serviços externos como stubs.

@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: Implantação do manifesto no CloudFoundry e quaisquer chamadas exigidas pelos testes

Cloud Foundry é uma das opções usadas para arquiteturas de teste baseadas em contêineres. “É uma plataforma de aplicativos em nuvem de código aberto que torna mais rápido e fácil construir, testar, implantar e dimensionar aplicativos. ” A seguir está o manifest.yml, um arquivo que define a configuração de todos os aplicativos do sistema. Este arquivo é usado para implantar o serviço real no formato pronto para produção no espaço da organização Pivotal onde o serviço MongoDB já está configurado, correspondendo à versão de produção.

---
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: Implantação de uma instância do serviço dependendo do serviço mongo

Ao optar pela abordagem fora do processo, lembre-se de que os limites reais estão em teste e, portanto, os testes acabam sendo mais lentos, pois há interações de rede e banco de dados. Seria ideal ter esses conjuntos de testes escritos em um módulo separado. Ser capaz de executá-los separadamente em um estágio maven diferente, em vez da fase normal de ‘teste’.

Como a ênfase dos testes está no próprio componente, os testes cobrem as responsabilidades primárias do componente, negligenciando propositalmente qualquer outra parte do sistema.

Cucumber, uma ferramenta de software que suporta Desenvolvimento Orientado a Comportamento, é uma opção para definir tais testes comportamentais. Com seu analisador de linguagem simples, Gherkin, garante que os clientes possam compreender facilmente todos os testes descritos. O seguinte arquivo de recurso do Cucumber garante que a implementação de nosso componente atenda aos requisitos de negócios desse recurso específico.

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: um arquivo de recursos que define testes BDD

Testes de ponta a ponta

Semelhante aos testes de componentes, o objetivo desses testes ponta a ponta não é realizar cobertura de código, mas garantir que o sistema atenda aos cenários de negócios solicitados. A diferença é que no teste ponta a ponta, todos os componentes estão funcionando durante o teste.

De acordo com o diagrama da pirâmide de testes, o número de testes ponta a ponta diminui ainda mais, levando em consideração a lentidão que podem causar. O primeiro passo é ter a configuração em execução e, para este exemplo, aproveitaremos o 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: A definição docker.yml usada para implantar o serviço definido e a versão especificada do mongo como contêineres

De acordo com os testes de componentes, faz sentido manter os testes ponta a ponta em um módulo separado e em fases diferentes. O exec-maven-plugin foi usado para implantar todos os componentes necessários, executar nossos testes e, finalmente, limpar e desmontar nosso ambiente de teste.

Como este é um teste de pilha ampla, uma seleção menor de testes por recurso será executada. Os testes são selecionados com base no risco comercial percebido. Os tipos anteriores de testes cobriam detalhes de baixo nível. Isso significa se uma história de usuário corresponde aos critérios de aceitação. Esses testes também devem interromper imediatamente um lançamento, pois uma falha aqui pode causar graves repercussões nos negócios.

Conclusão

Os testes centrados na transferência geralmente acabam sendo um processo muito longo, levando até semanas até que todos os bugs sejam identificados, corrigidos e uma nova implantação preparada. O feedback só é recebido depois que um lançamento é feito, tornando a vida útil de uma versão o mais rápido possível.

A abordagem de teste contínuo garante feedback imediato. Isso significa que o engenheiro de DevOps sabe imediatamente se o recurso implementado está pronto para produção ou não, dependendo do resultado dos testes executados. Desde testes unitários até testes ponta a ponta, todos eles auxiliam na agilização do processo de avaliação.

A arquitetura de microsserviços ajuda a criar implementações mais rápidas para produção, pois é orientada por domínio. Ele garante o isolamento de falhas e aumenta a propriedade. Quando várias equipes estão trabalhando no mesmo projeto, esse é outro motivo para adotar tal arquitetura: para garantir que as equipes sejam independentes e não interfiram no trabalho umas das outras.

Melhore a testabilidade avançando para testes contínuos. Cada microsserviço possui um domínio bem definido e seu escopo deve ser limitado a um ator. Os casos de testes aplicados são específicos e mais concisos, e os testes são isolados, facilitando releases e implantações mais rápidas.

Seguindo a abordagem TDD, não há codificação, a menos que um teste com falha retorne. Este processo aumenta a confiança quando uma implementação iterativa resulta em um teste bem-sucedido. Este processo implica que os testes aconteçam em paralelo com a implementação real, e todos os testes mencionados acima sejam executados antes que as alterações cheguem a um ambiente de teste. Os testes contínuos continuam evoluindo até entrar no próximo estágio de lançamento, ou seja, um ambiente de teste, onde o foco muda para testes mais exaustivos, como testes de carga.

Ágil, DevOps e entrega contínua exigem testes contínuos. O principal benefício é o feedback imediato produzido a partir de testes automatizados. As possíveis repercussões podem influenciar a experiência do usuário, mas também ter consequências comerciais de alto risco. Para obter mais informações sobre testes contínuos, entre em contato com a PhoenixNAP hoje mesmo.

Artigos relacionados