<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Piotr Przybyl - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Piotr Przybyl - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/pt/search-labs/author/piotr-przybyl</link>
    </image>
    <link>https://www.elastic.co/pt/search-labs/author/piotr-przybyl</link>
    <atom:link href="https://www.elastic.co/pt/search-labs/rss/author/piotr-przybyl.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[pt]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 08:47:23 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Fragmentos e réplicas do Elasticsearch: um guia prático]]></title>
    <description><![CDATA[Domine os conceitos de shards e réplicas do Elasticsearch e aprenda como otimizá-los.]]></description>
    <content:encoded><![CDATA[<p>O Elasticsearch potencializa o Lucene ao construir um sistema distribuído sobre ele, o que resolve os problemas de escalabilidade e tolerância a falhas. Também disponibiliza uma API REST baseada em JSON, tornando a interoperabilidade com outros sistemas muito simples.</p><p>Sistemas distribuídos como o Elasticsearch podem ser muito complexos, com muitos fatores que podem afetar seu desempenho e estabilidade. <strong>Os shards</strong> estão entre os conceitos mais fundamentais do Elasticsearch, e entender como eles funcionam permitirá que você gerencie um cluster Elasticsearch de forma eficaz.</p><p>Este artigo explica o que são shards primários e réplicas, seu impacto em um cluster Elasticsearch e quais ferramentas existem para ajustá-los a diferentes demandas.</p><h2>Entendendo os fragmentos</h2><p>Os dados em um índice Elasticsearch podem crescer a proporções gigantescas. Para manter a organização, cada dado é armazenado em um índice, e os índices são divididos em vários <strong>fragmentos</strong>. Cada fragmento do Elasticsearch é um índice Apache Lucene, sendo que cada índice Lucene individual contém um subconjunto dos documentos presentes no índice do Elasticsearch. Dividir os índices dessa forma mantém o uso de recursos sob controle. Um índice Apache Lucene tem um limite de 2.147.483.519 (2³¹ - 129) documentos.</p><p>Por vezes, os índices precisam ser movidos entre nós para fins de rebalanceamento. Como esse processo pode ser demorado e exigir muitos recursos, os índices não devem crescer demais, o que ajuda a manter o tempo de recuperação em níveis gerenciáveis. Além disso, como os índices são compostos por segmentos do Lucene que precisam ser constantemente mesclados, é importante que os segmentos não fiquem muito grandes. Por esses motivos, o Elasticsearch divide os dados do índice em partes menores e mais gerenciáveis, chamadas de <strong>shards primários</strong>, que podem ser distribuídas mais facilmente por várias máquinas. Os fragmentos <strong>de réplica</strong> são simplesmente uma cópia exata de um fragmento primário correspondente, e abordaremos sua função mais adiante neste artigo.</p><p>Ter o número correto de shards é importante para o desempenho. Portanto, é sensato planejar com antecedência. Quando as consultas são executadas em paralelo em diferentes shards, elas são executadas mais rapidamente do que um índice composto por um único shard, mas somente se cada shard estiver localizado em um nó diferente e houver nós suficientes no cluster. Ao mesmo tempo, porém, os shards consomem memória e espaço em disco, tanto em termos de dados indexados quanto de metadados do cluster. Ter muitos shards (também conhecido como sobresharding) pode tornar as consultas, as solicitações de indexação e as operações de gerenciamento mais lentas, sendo, portanto, fundamental manter o equilíbrio certo.</p><p>O número de shards primários é definido no momento da criação do índice <strong>para aquela instância de índice específica</strong>. Se precisar de um número diferente de shards primários posteriormente, você pode usar as<strong> APIs de redimensionamento</strong> : <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-split">split</a> (mais shards primários), <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-shrink">shrink</a> (menos shards primários) ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-clone">clone</a> (o mesmo número de shards primários com novas configurações para réplicas). Essas operações copiam segmentos do Lucene e <strong>evitam uma reindexação completa de todos os documentos</strong>. Ao criar um índice, você pode definir o número de shards primários e de réplicas nas configurações do índice:</p>PUT /sensor
{
   "settings" : {
       "index" : {
           "number_of_shards" : 6,
           "number_of_replicas" : 2
       }
   }
}<p>(Caso não especifique o número de shards ou réplicas, o valor padrão para ambos é 1, a partir do Elasticsearch 7.0). O número ideal de fragmentos deve ser determinado com base na quantidade de dados em um índice. Em geral, <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">um shard ideal deve conter de 10 a 50 GB de dados</a>, com menos de 200 milhões de documentos por shard. Por exemplo, se você espera acumular cerca de 300 GB de logs de aplicativos por dia, ter cerca de 10 shards nesse índice seria razoável, desde que você tenha nós suficientes para hospedá-los.</p><p>Durante sua existência, os fragmentos podem passar por diversos estados, incluindo:</p><ul><li><p><strong>Inicialização:</strong> Estado inicial antes que o fragmento possa ser usado.</p></li><li><p><strong>Iniciado:</strong> Estado em que o fragmento está ativo e pode receber solicitações.</p></li><li><p><strong>Relocação:</strong> Estado que ocorre quando os fragmentos estão em processo de serem movidos para um nó diferente. Isso pode ser necessário em certas condições, por exemplo, quando o nó em que estão instalados está ficando sem espaço em disco.</p></li><li><p><strong>Não atribuído:</strong> O estado de um fragmento que não pôde ser atribuído. Quando isso acontece, é apresentada uma justificativa, por exemplo, se o nó que hospeda o shard não estiver mais no cluster <em>(NODE_LEFT)</em> ou devido à restauração em um índice fechado <em>(EXISTING_INDEX_RESTORED).</em></p></li></ul><p>Para visualizar todos os fragmentos (shards), seus estados e outros metadados, você pode usar a seguinte solicitação:</p>GET _cat/shards<p>Para visualizar os fragmentos de um índice específico, você pode adicionar o nome do índice à URL, por exemplo, sensor:</p>GET _cat/shards/sensor<p>Este comando produz uma saída, como no exemplo a seguir. Por padrão, as colunas exibidas incluem o nome do índice, o nome (ou seja, número) do fragmento, se é um fragmento primário ou uma réplica, seu estado, o número de documentos, o tamanho em disco, bem como o endereço IP e o ID do nó onde o fragmento está localizado.</p>sensor 5 p STARTED    0  283b 127.0.0.1 ziap
sensor 5 r UNASSIGNED                  
sensor 2 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 2 r UNASSIGNED                  
sensor 3 p STARTED    3 7.2kb 127.0.0.1 ziap
sensor 3 r UNASSIGNED                  
sensor 1 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 1 r UNASSIGNED                  
sensor 4 p STARTED    2 3.8kb 127.0.0.1 ziap
sensor 4 r UNASSIGNED                  
sensor 0 p STARTED    0  283b 127.0.0.1 ziap
sensor 0 r UNASSIGNED<h2>Entendendo as réplicas</h2><p>Embora cada fragmento contenha uma única cópia dos dados, um índice pode conter várias cópias do fragmento. Existem, portanto, dois tipos de fragmentos: o <strong>fragmento primário</strong> e uma cópia, ou <strong>réplica</strong>. Cada réplica de um shard primário está sempre localizada em um nó diferente, o que garante alta disponibilidade dos seus dados em caso de falha de um nó. Além da redundância e de seu papel na prevenção de perda de dados e tempo de inatividade, as réplicas também podem ajudar a melhorar o desempenho da pesquisa, permitindo que as consultas sejam processadas em paralelo com o shard primário e, portanto, mais rapidamente.</p><p>Existem algumas diferenças importantes no comportamento dos fragmentos primários e das réplicas. Embora ambos sejam capazes de processar consultas, solicitações de indexação (ou seja, A adição de dados ao índice deve primeiro passar pelos shards primários antes de poder ser replicada para os shards de réplica. Conforme mencionado acima, se um shard primário ficar indisponível — por exemplo, devido à desconexão de um nó ou falha de hardware — uma réplica é promovida para assumir sua função.</p><p>Embora as réplicas possam ajudar em caso de falha de um nó, é importante não ter muitas delas, pois consomem memória, espaço em disco e poder computacional durante a indexação. Outra diferença entre os shards primários e as réplicas é que, enquanto o número de shards primários não pode ser alterado após a criação do índice, o número de réplicas pode ser alterado dinamicamente a qualquer momento, atualizando as configurações do índice.</p><p>Outro fator a ser considerado com réplicas é o número de nós disponíveis. As réplicas são sempre colocadas em nós diferentes do shard primário, uma vez que duas cópias dos mesmos dados no mesmo nó não ofereceriam proteção caso o nó falhasse. Consequentemente, para que um sistema suporte <em>n</em> réplicas, é necessário que haja pelo menos <em>n + 1</em> nós no cluster. Por exemplo, se houver dois nós em um cluster e um índice estiver configurado com seis réplicas, apenas uma réplica será alocada. Por outro lado, um sistema com sete nós é perfeitamente capaz de lidar com um shard primário e seis réplicas.</p><h2>Otimizando fragmentos e réplicas</h2><p>Mesmo após a criação de um índice com o equilíbrio correto entre shards primários e réplicas, é necessário monitorá-lo, pois a dinâmica em torno de um índice muda ao longo do tempo. Por exemplo, ao lidar com dados de séries temporais, os índices com dados recentes são geralmente mais ativos do que os mais antigos. Sem ajustar esses índices, todos eles consumiriam a mesma quantidade de recursos, apesar de suas necessidades serem muito diferentes.</p><p>A API de índice de rollover pode ser usada para separar índices mais recentes de índices mais antigos. É possível configurá-lo para criar automaticamente um novo índice quando um determinado limite — como o tamanho do índice no disco, o número de documentos ou sua idade — for atingido. Essa API também é útil para manter o tamanho dos fragmentos sob controle. Como o número de fragmentos não pode ser facilmente alterado após a criação do índice, os fragmentos continuarão acumulando dados se nenhuma condição de rollover for atendida. Para índices mais antigos que exigem acesso pouco frequente, reduzir o tamanho e forçar a fusão de um índice são duas maneiras diferentes de diminuir o espaço ocupado na memória e no disco. O primeiro reduz o número de fragmentos em um índice, enquanto o segundo reduz o número de segmentos do Lucene e libera espaço usado por documentos que foram excluídos.</p><h2>Fragmentos primários e réplicas como base do Elasticsearch</h2><p>O Elasticsearch construiu uma sólida reputação como plataforma distribuída de armazenamento, busca e análise para grandes volumes de dados. Ao operar em tal escala, porém, desafios inevitavelmente surgirão. Por isso, entender como funcionam os shards primários e de réplica é tão importante e fundamental para o Elasticsearch, pois isso pode ajudar a otimizar a confiabilidade e o desempenho da plataforma.</p><p>Saber como funcionam e como otimizá-los é fundamental para obter um cluster Elasticsearch mais robusto e com melhor desempenho. Se você está enfrentando lentidão nas respostas às consultas ou interrupções frequentes, esse conhecimento pode ser a chave para superar esses obstáculos.</p><p>Siga a documentação oficial do Elasticsearch para saber mais sobre <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/clusters-nodes-shards">clusters, nós e shards</a>, <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">como dimensionar seus shards</a>, <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/shard-allocation-relocation-recovery">alocação de shards e recuperação</a>.</p><p>Este tópico também está disponível como um curso introdutório no <a href="https://youtu.be/sAySPSyL2qE">canal da comunidade Elastic no YouTube.</a></p><p>Por último, mas não menos importante: se você não quiser se preocupar com nós, shards ou réplicas, pode experimentar o <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">Elastic Cloud Serverless</a>. Esta oferta da Elastic Cloud é totalmente gerenciada pela Elastic e automatizada para escalar de acordo com sua carga de trabalho. Um período de teste gratuito pode ajudá-lo a se familiarizar com outros benefícios da abordagem sem servidor.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-shards-and-replicas-guide</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-shards-and-replicas-guide</guid>
    <category><![CDATA[Noções básicas]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71dd92d939d383a2/6a17e9d53e03d769c44f2cc7/7775c44f01f2516c4ff4cce6d6bbe9e7b2c38908-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Testando seu código Java com mocks e Elasticsearch real.]]></title>
    <description><![CDATA[Aprenda a escrever seus testes automatizados para Elasticsearch, usando mocks e Testcontainers.]]></description>
    <content:encoded><![CDATA[<p>Neste artigo, apresentaremos e explicaremos duas maneiras de testar software usando o Elasticsearch como uma dependência externa do sistema. Vamos abordar testes usando mocks, bem como testes de integração, mostrar algumas diferenças práticas entre eles e dar algumas dicas sobre o que fazer com cada estilo.</p><h2>Bons testes para avaliar a confiabilidade do sistema.</h2><p>Um bom teste é aquele que aumenta a confiança de todas as pessoas envolvidas no processo de criação e manutenção de um sistema de TI. Os testes não devem ser pensados para serem modernos, rápidos ou para aumentar artificialmente a cobertura de código. Os testes desempenham um papel vital para garantir que:</p><ul><li><p>O que queremos entregar é algo que funcione na produção.</p></li><li><p>O sistema satisfaz os requisitos e os contratos.</p></li><li><p>Não haverá regressões no futuro.</p></li><li><p>Os desenvolvedores (e outros membros da equipe envolvidos) estão confiantes de que o que criaram funcionará.</p></li></ul><p>É claro que isso não significa que os testes não possam ser interessantes, rápidos ou aumentar a cobertura de código. Quanto mais rápido pudermos executar nosso conjunto de testes, melhor. A questão é que, na busca por reduzir a duração total do conjunto de testes, não devemos sacrificar a confiabilidade, a facilidade de manutenção e a segurança que os testes automatizados nos proporcionam.</p><p>Bons testes automatizados aumentam a confiança dos membros da equipe:</p><ul><li><p>Desenvolvedores: eles conseguem confirmar que o que estão fazendo funciona (mesmo antes que o código em que trabalham saia de suas máquinas).</p></li><li><p>Equipe de garantia da qualidade: eles têm menos coisas para testar manualmente.</p></li><li><p>Os operadores de sistemas e os SREs (Engenheiros de Confiabilidade de Site) estão mais tranquilos, pois os sistemas são mais fáceis de implantar e manter.</p></li></ul><p>Por último, mas não menos importante: a arquitetura de um sistema. Adoramos quando os sistemas são organizados, fáceis de manter e a arquitetura é limpa e cumpre sua função. No entanto, às vezes podemos nos deparar com uma arquitetura que sacrifica demais em nome da desculpa conhecida como "assim é mais fácil de testar". Não há nada de errado em ser altamente testável – o problema surge quando o sistema é escrito principalmente para ser testável, em vez de atender às necessidades que justificam sua existência. É nesse momento que vemos a cauda abanando o cachorro.</p><h2>Existem dois tipos de testes: simulações (mocks) e testes de dependência.</h2><p>Existem muitas maneiras de visualizar os testes e, portanto, classificá-los. Neste post, vou me concentrar em apenas um aspecto da divisão dos testes: usar mocks (ou stubs, ou fakes, ou ...) versus usar dependências reais. No nosso caso, a dependência é o Elasticsearch.</p><p>Os testes que utilizam mocks são muito rápidos porque não precisam iniciar nenhuma dependência externa e tudo acontece apenas na memória. Em testes automatizados, o "mocking" consiste na utilização de objetos falsos em vez de objetos reais para testar partes de um programa sem usar as dependências reais. É por isso que são necessários e por isso que se destacam em qualquer teste de rede de detecção rápida, por exemplo. Validação de entrada. Não é necessário iniciar um banco de dados e fazer uma chamada a ele apenas para verificar se números negativos em uma solicitação não são permitidos, por exemplo.</p><p>No entanto, a introdução de simulações tem várias implicações:</p><ul><li><p>Nem tudo e em todas as situações pode ser facilmente simulado, portanto, as simulações têm impacto na arquitetura do sistema (o que às vezes é ótimo, outras vezes nem tanto).</p></li><li><p>Os testes executados em mocks podem ser rápidos, mas o desenvolvimento desses testes pode levar bastante tempo, pois os mocks que refletem fielmente os sistemas que imitam geralmente não são fornecidos gratuitamente. Quem conhece o funcionamento do sistema precisa escrever os mocks da maneira correta, e esse conhecimento pode vir da experiência prática, do estudo da documentação e assim por diante.</p></li><li><p>É necessário manter os simulados. Quando seu sistema depende de uma dependência externa e você precisa atualizar essa dependência, alguém precisa garantir que os mocks que a reproduzem também sejam atualizados com todas as alterações: alterações que quebram a compatibilidade, alterações documentadas e alterações não documentadas (que também podem ter impacto em nosso sistema). Isso se torna especialmente problemático quando você deseja atualizar uma dependência, mas seu conjunto de testes (que usa apenas mocks) não consegue garantir que todos os casos testados funcionarão corretamente.</p></li><li><p>É preciso disciplina para garantir que o esforço seja direcionado para o desenvolvimento e teste do sistema, e não para simulações.</p></li></ul><p>Por essas razões, muitas pessoas defendem seguir exatamente na direção oposta: nunca usar mocks (ou stubs, etc.), mas confiar exclusivamente em dependências reais. Essa abordagem funciona muito bem em demonstrações ou quando o sistema é pequeno e possui apenas alguns casos de teste que geram uma cobertura enorme. Esses testes podem ser testes de integração (grosso modo: verificar uma parte de um sistema em relação a algumas dependências reais) ou testes de ponta a ponta (usando todas as dependências reais ao mesmo tempo e verificando o comportamento do sistema em todas as suas extremidades, enquanto se reproduzem fluxos de trabalho do usuário que definem o sistema como utilizável e bem-sucedido). Uma clara vantagem de usar essa abordagem é que também verificamos (muitas vezes sem intenção) nossas suposições sobre as dependências e como as integramos ao sistema em que estamos trabalhando.</p><p>No entanto, quando os testes utilizam apenas dependências reais, precisamos considerar os seguintes aspectos:</p><ul><li><p>Alguns cenários de teste não precisam da dependência real (por exemplo, para verificar as invariantes estáticas de uma solicitação).</p></li><li><p>Normalmente, esses testes não são executados em conjuntos completos nas máquinas dos desenvolvedores, porque esperar por feedback levaria muito tempo.</p></li><li><p>Elas exigem mais recursos nas máquinas de CI e pode levar mais tempo para ajustar tudo e evitar desperdício de tempo e recursos.</p></li><li><p>Pode não ser trivial inicializar dependências com dados de teste.</p></li><li><p>Testes com dependências reais são ótimos para isolar o código antes de grandes refatorações, migrações ou atualizações de dependências.</p></li><li><p>É mais provável que sejam testes opacos, ou seja, que não entrem em detalhes sobre o funcionamento interno do sistema em teste, mas que se preocupem com os resultados.</p></li></ul><h2>O ponto ideal: use ambos os testes.</h2><p>Em vez de testar seu sistema com apenas um tipo de teste, você pode usar ambos os tipos quando fizer sentido e tentar melhorar o uso de ambos.</p><ul><li><p>Execute primeiro os testes baseados em mocks, pois são muito mais rápidos, e somente depois que todos forem bem-sucedidos, execute os testes de dependência, que são mais lentos.</p></li><li><p>Escolha mocks para cenários onde dependências externas não são realmente necessárias: quando a criação de mocks levaria muito tempo, o código deve ser alterado drasticamente apenas para isso; dependa de dependências externas.</p></li><li><p>Não há nada de errado em testar um trecho de código usando ambas as abordagens, desde que faça sentido.</p></li></ul><h2>Exemplo de SistemaEmTeste</h2><p>Nas próximas seções, usaremos um exemplo que pode ser encontrado <a href="https://github.com/pioorg/testing-elasticsearch">aqui</a>. Trata-se de uma pequena aplicação de demonstração escrita em Java 21, utilizando o Maven como ferramenta de compilação, dependendo do cliente Elasticsearch e utilizando a mais recente adição ao Elasticsearch, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">o ES|QL</a> (a nova linguagem de consulta procedural da Elastic). Se Java não é a sua linguagem de programação, você ainda poderá entender os conceitos que discutiremos a seguir e adaptá-los à sua pilha de tecnologias. O simples fato de usar um exemplo de código real facilita a explicação de certas coisas.</p><p>O <code>BookSearcher</code> nos ajuda a lidar com a pesquisa e a analisar dados, que no nosso caso são livros (como demonstrado em <a href="https://www.elastic.co/search-labs/blog/esql-queries-to-java-objects">uma das postagens anteriores</a>).</p><ul><li><p>Ele requer o Elasticsearch exatamente na versão <code>8.15.x</code> como sua única dependência (veja <code>isCompatibleWithBackend()</code>), por exemplo, porque não temos certeza se nosso código é compatível com versões futuras e temos certeza de que não é compatível com versões anteriores. Antes de atualizar o Elasticsearch em produção para uma versão mais recente, primeiro o atualizaremos nos testes para garantir que o comportamento do Sistema em Teste permaneça o mesmo.</p></li><li><p>Podemos usá-lo para pesquisar o número de livros publicados em um determinado ano (ver <code>numberOfBooksPublishedInYear</code>).</p></li><li><p>Também podemos usá-lo quando precisamos analisar nosso conjunto de dados e descobrir os 20 autores mais publicados entre dois anos determinados (ver <code>mostPublishedAuthorsInYears</code>).</p></li></ul>public class BookSearcher {

    private final ElasticsearchClient esClient;

    public BookSearcher(ElasticsearchClient esClient) {
        this.esClient = esClient;
        if (!isCompatibleWithBackend()) {
            throw new UnsupportedOperationException("This is not compatible with backend");
        }
    }

    private boolean isCompatibleWithBackend() {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            show info
            | keep version
            | dissect version "%{major}.%{minor}.%{patch}"
            | keep major, minor
            | limit 1""")) {
            if (!rs.next()) {
                throw new RuntimeException("No version found");
            }
            return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public int numberOfBooksPublishedInYear(int year) {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            from books
            | where year == ?
            | stats published = count(*) by year
            | limit 1000""", year)) {

            if (rs.next()) {
                return rs.getInt("published");
            }
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
        return 0;
    }


    public List&lt;MostPublished&gt; mostPublishedAuthorsInYears(int minYear, int maxYear) {
        assert minYear &lt;= maxYear;
        String query = """
            from books
            | where year &gt;= ? and year &lt;= ?
            | stats first_published = min(year), last_published = max(year), times = count (*) by author
            | eval years_published = last_published - first_published
            | sort years_published desc
            | drop years_published
            | limit 20
            """;

        try {
            Iterable&lt;MostPublished&gt; published = esClient.esql().query(
                ObjectsEsqlAdapter.of(MostPublished.class),
                query,
                minYear,
                maxYear);

            List&lt;MostPublished&gt; mostPublishedAuthors = new ArrayList&lt;&gt;();
            for (MostPublished mostPublished : published) {
                mostPublishedAuthors.add(mostPublished);
            }
            return mostPublishedAuthors;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public record MostPublished(
        String author,
        @JsonProperty("first_published") int firstPublished,
        @JsonProperty("last_published") int lastPublished,
        int times
    ) {
        public MostPublished {
            assert author != null;
            assert firstPublished &lt;= lastPublished;
            assert times &gt; 0;
        }
    }
}
<h2>Faça testes com simulações para começar.</h2><p>Para criar os mocks usados em nossos testes, vamos usar <a href="https://site.mockito.org/">o Mockito</a>, uma biblioteca de mocks muito popular no ecossistema Java.</p><p>Podemos começar com o seguinte, para que os mocks sejam reinicializados antes de cada teste:</p>public class BookSearcherMockingTest {

    ResultSet mockResultSet;
    ElasticsearchClient esClient;
    ElasticsearchEsqlClient esql;

    @BeforeEach
    void setUpMocks() {
        mockResultSet = mock(ResultSet.class);
        esClient = mock(ElasticsearchClient.class);
        esql = mock(ElasticsearchEsqlClient.class);

    }
}
<p>Como dissemos anteriormente, nem tudo pode ser facilmente testado usando mocks. Mas há coisas que podemos (e provavelmente até devemos) fazer. Vamos verificar se a única versão do Elasticsearch suportada é <code>8.15.x</code> por enquanto (no futuro, poderemos ampliar o intervalo assim que confirmarmos que nosso sistema é compatível com versões futuras):</p>@Test
void canCreateSearcherWithES_8_15() throws SQLException, IOException{
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(15);

    // then
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(esClient));
}
<p>Podemos verificar de forma semelhante (simplesmente retornando uma versão secundária diferente) que nosso <code>BookSearcher</code> ainda não funcionará com <code>8.16.x</code> , porque não temos certeza se será compatível com ele:</p>@Test
void cannotCreateSearcherWithoutES_8_15() throws SQLException, IOException {
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(16);

    // then
    Assertions.assertThrows(UnsupportedOperationException.class, () -&gt; new BookSearcher(esClient));
}
<p>Agora vejamos como podemos alcançar algo semelhante ao testar com um Elasticsearch real. Para isso, vamos usar <a href="https://java.testcontainers.org/modules/elasticsearch/">o módulo Elasticsearch da Testcontainers</a>, que tem apenas um requisito: precisa de acesso ao Docker, pois executa contêineres Docker para você. De certo ponto de vista, o Testcontainers é simplesmente uma forma de operar contêineres Docker, mas em vez de fazer isso no seu Docker Desktop (ou similar), na sua CLI ou em scripts, você pode expressar suas necessidades na linguagem de programação que você conhece. Isso possibilita buscar imagens, iniciar contêineres, coletar o lixo após os testes, copiar arquivos de um lado para o outro, executar comandos, examinar logs, etc., diretamente do seu código de teste.</p><p>O esboço pode ter esta aparência:</p>@Testcontainers
public class BookSearcherIntTest {

    static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
    static final JacksonJsonpMapper JSONP_MAPPER = new JacksonJsonpMapper();

    RestClientTransport transport;
    ElasticsearchClient client;

    @Container
    ElasticsearchContainer elasticsearch = new ElasticsearchContainer(ELASTICSEARCH_IMAGE);

    @BeforeEach
    void setupClient() {
        transport = // setup transport here
        client = new ElasticsearchClient(transport);
    }

    @AfterEach
    void closeClient() throws IOException {
        if (transport != null) {
            transport.close();
        }
    }

}
<p>Neste exemplo, contamos com <a href="https://java.testcontainers.org/test_framework_integration/junit_5/">a integração do Testcontainers com o JUnit</a> , <code>@Testcontainers</code> e <code>@Container</code>, o que significa que não precisamos nos preocupar em iniciar o Elasticsearch antes de nossos testes e pará-lo depois. A única coisa que precisamos fazer é criar o cliente antes de cada teste e fechá-lo após cada teste (para evitar vazamentos de recursos, que poderiam afetar conjuntos de testes maiores).</p><p>Anotar um campo não estático com <code>@Container</code> significa que um novo contêiner será iniciado para cada teste, portanto não precisamos nos preocupar com dados desatualizados ou com a reinicialização do estado do contêiner. No entanto, em muitos testes, essa abordagem pode não apresentar um bom desempenho, por isso vamos compará-la com alternativas em uma das próximas publicações.</p><p><strong>Observação:</strong></p>Ao utilizar o <code>docker.elastic.co</code> (repositório oficial de imagens Docker da Elastic), você evita atingir seus limites no Docker Hub.

Recomenda-se também usar a mesma versão da sua dependência nos ambientes de teste e de produção, para garantir a máxima compatibilidade. Recomendamos também que você seja preciso ao selecionar a versão, por esse motivo, não há tag <code>latest</code> para imagens do Elasticsearch.<h2>Conectando-se ao Elasticsearch em testes</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html">O cliente Java do Elasticsearch</a> é capaz de se conectar ao Elasticsearch em execução em um contêiner de teste, mesmo com segurança e SSL/TLS habilitados (que são padrão nas versões 8.x, por isso não precisamos especificar nada relacionado à segurança na declaração do contêiner). Partindo do pressuposto que o Elasticsearch que você está usando em produção também tenha TLS e alguns recursos de segurança habilitados, recomenda-se configurar os testes de integração o mais próximo possível do cenário de produção e, portanto, não desabilitá-los nos testes.</p><p>Como obter os dados necessários para a conexão, assumindo que o contêiner está atribuído ao campo ou variável <code>elasticsearch</code>:</p><ul><li><p><code>elasticsearch.getHost()</code> irá fornecer o host no qual o contêiner está sendo executado (que na maioria das vezes provavelmente será <code>"localhost"</code>, mas, por favor, não codifique isso diretamente, pois às vezes, dependendo da sua configuração, pode ser outro nome; portanto, o host deve sempre ser obtido dinamicamente).</p></li><li><p><code>elasticsearch.getMappedPort(9200)</code> irá fornecer a porta do host que você precisa usar para se conectar ao Elasticsearch em execução dentro do contêiner (porque cada vez que você inicia o contêiner, a porta externa é diferente, então esta também precisa ser uma chamada dinâmica).</p></li><li><p>A menos que tenham sido sobrescritos, o nome de usuário e a senha padrão são <code>"elastic"</code> e <code>"changeme"</code> , respectivamente.</p></li><li><p>Caso nenhum certificado SSL/TLS tenha sido especificado durante a configuração do contêiner e a conectividade segura não esteja desativada (que é o comportamento padrão a partir das versões 8.x), um certificado autoassinado será gerado. Confiar nisso (por exemplo) como <a href="https://curl.se/docs/manpage.html#--cacert">o cURL pode fazer</a>) o certificado pode ser obtido usando <code>elasticsearch.caCertAsBytes()</code> (que retorna <code>Optional&lt;byte[]&gt;</code>), ou outra maneira conveniente é obter <code>SSLContext</code> usando <code>createSslContextFromCa()</code>.</p></li></ul><p>O resultado geral pode ser semelhante a este:</p>BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level rest client
RestClient restClient = RestClient.builder(new HttpHost(elasticsearch.getHost(), elasticsearch.getMappedPort(9200), "https"))
    .setHttpClientConfigCallback(httpClientBuilder -&gt;
        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
            .setSSLContext(elasticsearch.createSslContextFromCa())
    )
    .build();

// The RestClientTransport is mainly for serialization/deserialization
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

// The official Java API Client for Elasticsearch
ElasticsearchClient client = new ElasticsearchClient(transport);
<p>Outro exemplo de criação de uma instância de <code>ElasticsearchClient</code> pode ser encontrado no <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherIntTest.java#L61">projeto de demonstração</a>.</p><p><strong>Observação</strong>:</p>Para criar um cliente em ambientes de produção, consulte <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/connecting.html#_verifying_https_with_a_certificate_fingerprint">a documentação</a>.<h2>Primeiro teste de integração</h2><p>Nosso primeiro teste, para verificar se podemos criar <code>BookSearcher</code> usando o Elasticsearch versão 8.15.x, pode ser algo como:</p>@Test
void canCreateClientWithContainerRunning_8_15() {
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(client));
}
<p>Como podem ver, não precisamos configurar mais nada. Não precisamos simular a versão retornada pelo Elasticsearch, a única coisa que precisamos fazer é fornecer <code>BookSearcher</code> com um cliente conectado a uma instância real do Elasticsearch, que foi iniciada para nós pelo Testcontainers.</p><h2>Os testes de integração se preocupam menos com os detalhes internos.</h2><p>Vamos fazer uma pequena experiência: vamos supor que temos que parar de extrair dados do conjunto de resultados usando índices de coluna, e sim usar os nomes das colunas. Então, no método <code>isCompatibleWithBackend</code> em vez de</p>return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
<p>Teremos:</p>return rs.getInt("major") == 8 &amp;&amp; rs.getInt("minor") == 15;
<p>Ao executarmos novamente os dois testes, notaremos que o teste de integração com o Elasticsearch real ainda é aprovado sem problemas. No entanto, os testes usando mocks pararam de funcionar, porque simulamos chamadas como <code>rs.getInt(int)</code>, não <code>rs.getInt(String)</code>. Para que sejam aprovados, agora precisamos ou simulá-los, ou simulá-los ambos, dependendo de outros casos de uso que temos em nosso conjunto de testes.</p><h2>Testes de integração podem ser como um canhão para matar uma mosca.</h2><p>Os testes de integração são capazes de verificar o comportamento do sistema, mesmo que não haja necessidade de dependências externas. No entanto, usá-los dessa forma geralmente resulta em desperdício de tempo e recursos de execução. Vamos analisar o método <code>mostPublishedAuthorsInYears(int minYear, int maxYear)</code>. As duas primeiras linhas são as seguintes:</p>assert minYear &lt;= maxYear;
String query = // here goes the query
<p>A primeira declaração verifica uma condição que não depende do Elasticsearch (ou de qualquer outra dependência externa) de forma alguma. Portanto, não precisamos iniciar nenhum contêiner apenas para verificar se <code>minYear</code> é maior que <code>maxYear</code>, uma exceção é lançada.</p><p>Um teste de simulação simples, que também seja rápido e não consuma muitos recursos, é mais do que suficiente para garantir isso. Após configurar os mocks, podemos simplesmente prosseguir para:</p>BookSearcher systemUnderTest = new BookSearcher(esClient);

Assertions.assertThrows(
    AssertionError.class,
    () -&gt; systemUnderTest.mostPublishedAuthorsInYears(2012, 2000)
);
<p>Iniciar uma dependência, em vez de usar um mock, seria um desperdício <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherMockingTest.java#L89">neste caso de teste,</a> pois não há chance de fazer uma chamada significativa para essa dependência.</p><p>No entanto, para verificar o comportamento a partir de <code>String query = ...</code>, que a consulta está escrita corretamente, os resultados são os esperados: a biblioteca cliente é capaz de enviar solicitações e respostas adequadas, não há alterações de sintaxe e, portanto, é muito mais fácil usar um teste de integração, por exemplo:</p>@BeforeEach
void setupDataInContainer() {
    // here we initialise data in the Elasticsearch running in a container
}

@Test
void shouldGiveMostPublishedAuthorsInGivenYears() {
    var systemUnderTest = new BookSearcher(client);
    var list = systemUnderTest.mostPublishedAuthorsInYears(1800, 2010);
    Assertions.assertEquals("Beatrix Potter", list.get(12).author(), "Beatrix Potter was 13th most published author between 1800 and 2010");
}
<p>Dessa forma, podemos ter certeza de que, ao enviar nossos dados para o Elasticsearch (nesta ou em qualquer versão futura para a qual optarmos por migrar), nossa consulta nos dará exatamente o que esperamos: o formato dos dados não mudou, a consulta ainda é válida e todo o middleware (clientes, drivers, segurança etc.) continuará funcionando. Não precisamos nos preocupar em manter os mocks atualizados; a única alteração necessária é garantir a compatibilidade com, por exemplo, <code>8.15</code> alteraria isto:</p>static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
<p>O mesmo acontece se você decidir, por exemplo, Use a boa e velha QueryDSL em vez de ES|QL: os resultados que você receber da consulta (independentemente da linguagem) ainda serão os mesmos.</p><h2>Utilize ambas as abordagens quando necessário.</h2><p>O caso do método <code>mostPublishedAuthorsInYears</code> ilustra que um único método pode ser testado usando ambos os métodos. E talvez até devesse ser.</p><ul><li><p>Usar apenas mocks significa que temos que manter o mock e não temos nenhuma confiança ao atualizar nosso sistema.</p></li><li><p>Utilizar apenas testes de integração significaria um desperdício de muitos recursos, sem necessidade.</p></li></ul><h2>Vamos recapitular</h2><ul><li><p>É possível usar tanto testes de simulação (mocking) quanto testes de integração com o Elasticsearch.</p></li><li><p>Use testes de mocking como fast-detection-net e somente se eles passarem com sucesso, inicie os testes com dependências (por exemplo, usando <code>./mvnw test '-Dtest=!TestInt*' &amp;&amp; ./mvnw test '-Dtest=TestInt*'</code> ou os plugins <a href="https://maven.apache.org/surefire/maven-failsafe-plugin/">Failsafe</a> e <a href="https://maven.apache.org/surefire/maven-surefire-plugin/">Surefire</a> ).</p></li><li><p>Use mocks ao testar o comportamento do seu sistema ("linhas de código") onde a integração com dependências externas não é realmente importante (ou pode até ser ignorada).</p></li><li><p>Utilize testes de integração para verificar suas suposições sobre a integração com sistemas externos.</p></li><li><p>Não tenha receio de testar ambas as abordagens – se fizer sentido – de acordo com os pontos acima.</p></li></ul><p>Poderíamos observar que ser tão rigoroso quanto à versão (no nosso caso, <code>8.15.x</code>) é excessivo. Usar apenas a tag de versão poderia ser suficiente, mas lembre-se de que, neste post, ela representa todos os outros recursos que podem mudar entre as versões.</p><p>Na <a href="https://www.elastic.co/search-labs/blog/automated-integration-tests-faster-elasticsearch">próxima parte desta série</a>, veremos maneiras de inicializar o Elasticsearch em um contêiner de teste, com conjuntos de dados de teste. Informe-nos se você construiu algo com base neste blog ou se tiver dúvidas em nossos <a href="https://discuss.elastic.co/">fóruns de discussão</a> e <a href="https://communityinviter.com/apps/elasticstack/elastic-community">no canal da comunidade no Slack</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</guid>
    <category><![CDATA[Java]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e4d003f09dfbe50/6a1709aba929cf810aae0957/b6bb727815ebdb844aeb36d5c44cdf3657f0e4bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 03 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>