Testes de unidade

Depois de alterar ou adicionar o código, execute testes de unidade existentes e considere programar mais. Todos os testes são executados nas versões não compactadas do código.

Há dois conjuntos de testes de unidade: testes JS e testes do gerador de blocos.

Testes JS

Os testes JS confirmam a operação das funções JavaScript internas no núcleo do Blockly. Usamos Mocha para executar testes de unidade, Sinon para criar dependências de stub e Chai para fazer declarações sobre o código.

Como executar testes

Em amostras em blocos e em blocos, o npm run test vai executar os testes de unidade. Em bloco, ele também vai executar outros testes, como inspeção e compilação. Também é possível abrir tests/mocha/index.html em um navegador para executar de forma interativa todos os testes mocha.

Como programar testes

Usamos a interface do Mocha TDD para executar testes. Os testes são organizados em pacotes, que podem conter subpacotes e/ou testes adicionais. Geralmente, cada componente do Blockly (como toolbox ou workspace) tem o próprio arquivo de teste que contém um ou mais pacotes. Cada pacote pode ter um método setup e teardown que será chamado antes e depois de cada teste nesse pacote, respectivamente.

Auxiliares de testes

Temos várias funções auxiliares específicas do Blockly que podem ser úteis ao programar testes. Eles podem ser encontrados nas amostras core e blockly-samples.

As funções auxiliares incluem sharedTestSetup e sharedTestTeardown, que são obrigatórias para serem chamadas antes e depois dos testes (consulte a seção "Requisitos").

sharedTestSetup:
  • Configura timers falsos sinon (em alguns testes, você precisará usar this.clock.runAll).
  • Stubs Blockly.Events.fire para disparar imediatamente (configurável).
  • Configura a limpeza automática de blockTypes definidos usando defineBlocksWithJsonArray.
  • Declara algumas propriedades no contexto this que precisam ser acessíveis:
    • this.clock (mas não pode ser restaurado, porque isso vai causar problemas em sharedTestTeardown)
    • this.eventsFireStub
    • this.sharedCleanup (para ser usado com addMessageToCleanup e addBlockTypeToCleanup). OBSERVAÇÃO: você não precisa usar addBlockTypeToCleanup se definiu o bloco usando defineBlocksWithJsonArray.

A função tem um parâmetro options opcional para configurar. Atualmente, ele só é usado para determinar se o stub do Blockly.Events.fire precisa ser disparado imediatamente (por padrão, ele é stub).

sharedTestTeardown:
  • Descarta do espaço de trabalho this.workspace (dependendo do local em que foi definido, consulte a seção "Requisitos de teste" para mais informações).
  • Restaura todos os stubs.
  • Limpa todos os tipos de bloco adicionados usando defineBlocksWithJsonArray e addBlockTypeToCleanup.
  • Limpa todas as mensagens adicionadas usando addMessageToCleanup.

Requisitos de teste

  • Cada teste precisa chamar sharedTestSetup.call(this); como a primeira linha de configuração do pacote externo e sharedTestTeardown.call(this); como a última linha da desmontagem do pacote externo de um arquivo.
  • Se você precisar de um espaço de trabalho com uma caixa de ferramentas genérica, use uma das caixas de ferramentas predefinidas no teste index.html. Veja abaixo um exemplo.
  • Descarte corretamente this.workspace. Na maioria dos testes, você definirá this.workspace no pacote mais externo e o usará para todos os testes subsequentes, mas, em alguns casos, é possível defini-lo ou redefini-lo em um pacote interno. Por exemplo, um dos seus testes requer um espaço de trabalho com opções diferentes daquelas configuradas originalmente. Descarte esses itens ao final do teste.
    • Se você definir this.workspace no conjunto externo e nunca o redefinir, nenhuma outra ação será necessária. Ele será descartado automaticamente por sharedTestTeardown.
    • Se você definir this.workspace pela primeira vez em um pacote interno, ou seja, não tiver definido no conjunto mais externo, será necessário descartar manualmente o conjunto chamando workspaceTeardown.call(this, this.workspace) na desmontagem desse conjunto.
    • Se você definir this.workpace no pacote mais externo, mas depois redefinir em um pacote de testes interno, chame workspaceTeardown.call(this, this.workspace) antes de redefini-lo para eliminar o espaço de trabalho original definido no pacote de nível superior. Você também precisa descartar manualmente o novo valor chamando workspaceTeardown.call(this, this.workspace) novamente na desmontagem desse pacote interno.

Estrutura do teste

Os testes de unidade geralmente seguem uma estrutura definida, que pode ser resumida como arrange, Act, assert.

  1. Organizar: define o estado do mundo e todas as condições necessárias para o comportamento em teste.
  2. Agir: chame o código em teste para acionar o comportamento que está sendo testado.
  3. Assert: faça declarações sobre o valor de retorno ou interações com objetos simulados para verificar a exatidão.

Em um teste simples, pode não haver nenhum comportamento a ser organizado. Além disso, os estágios de "Agir" e "assert" podem ser combinados com a inserção da chamada ao código que está sendo testado na declaração. Em casos mais complexos, os testes ficarão mais legíveis se você seguir esses três estágios.

Veja um exemplo de arquivo de teste (simplificado do modo real).

suite('Flyout', function() {
  setup(function() {
    sharedTestSetup.call(this);
    this.toolboxXml = document.getElementById('toolbox-simple');
    this.workspace = Blockly.inject('blocklyDiv',
        {
          toolbox: this.toolboxXml
        });
  });

  teardown(function() {
    sharedTestTeardown.call(this);
  });

  suite('simple flyout', function() {
    setup(function() {
      this.flyout = this.workspace.getFlyout();
    });
    test('y is always 0', function() {
      // Act and assert stages combined for simple test case
      chai.assert.equal(this.flyout.getY(), 0, 'y coordinate in vertical flyout is 0');
    });
    test('x is right of workspace if flyout at right', function() {
      // Arrange
      sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({
        viewWidth: 100,
      });
      this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT;
      this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_RIGHT;

      // Act
      var x = this.flyout.getX();

      // Assert
      chai.assert.equal(x, 100, 'x is right of workspace');
    });
  });
});

Observações sobre este exemplo:

  • Um pacote pode conter outros pacotes que têm outros métodos setup e teardown.
  • Cada pacote e teste possui um nome descritivo.
  • As declarações de Chai são usadas para fazer declarações sobre o código.
    • Você pode fornecer um argumento de string opcional que será exibido se o teste falhar. Isso facilita a depuração de testes corrompidos.
    • A ordem dos parâmetros é chai.assert.equal(actualValue, expectedValue, optionalMessage). Se você trocar actual e expected, as mensagens de erro não farão sentido.
  • Sinon é usado para criar stubs de métodos quando você não quer chamar o código real. Neste exemplo, não queremos chamar a função de métricas real porque ela não é relevante para o teste. Só nos preocupamos em como os resultados são usados pelo método em teste. O Sinon cria stubs da função getMetrics para retornar uma resposta automática que pode ser facilmente verificada nas declarações de teste.
  • Os métodos setup de cada pacote precisam conter apenas uma configuração genérica que se aplica a todos os testes. Se o teste de um comportamento específico depender de uma determinada condição, ela precisará ser declarada claramente no teste relevante.

Testes de depuração

  • Abra os testes em um navegador e use as ferramentas para desenvolvedores para definir pontos de interrupção e investigar se os testes estão falhando inesperadamente (ou passando de forma inesperada).
  • Defina .only() ou .skip() em um teste ou pacote para executar apenas esse conjunto ou pule um teste. Exemplo:

    suite.only('Workspace', function () {
      suite('updateToolbox', function () {
        test('test name', function () {
          // ...
        });
        test.skip('test I don’t care about', function () {
          // ...
        });
      });
    });
    

    Lembre-se de removê-los antes de confirmar seu código.

Testes de gerador de blocos

Cada bloco tem seus próprios testes de unidade. Esses testes verificam se os blocos geram código do que funções conforme o esperado.

  1. Carregue tests/generators/index.html no Firefox ou Safari. O Chrome e o Opera têm restrições de segurança que impedem o carregamento dos testes do sistema "file://" local (problemas 41024 e 47416).
  2. Escolha a parte relevante do sistema a ser testada no menu suspenso e clique em "Carregar". Os blocos devem aparecer no espaço de trabalho.
  3. Clique em "JavaScript".
    Copie e execute o código gerado em um Console JavaScript. Se a saída terminar com "OK", o teste terá sido aprovado.
  4. Clique em "Python".
    Copie e execute o código gerado em um interpretador do Python. Se a saída terminar com "OK", o teste foi aprovado.
  5. Clique em "PHP".
    Copie e execute o código gerado em um intérprete de PHP. Se a saída terminar com "OK", o teste foi aprovado.
  6. Clique em "Lua".
    Copie e execute o código gerado em um intérprete do Lua. Se a saída terminar com "OK", o teste foi aprovado.
  7. Clique em "Dart".
    Copie e execute o código gerado em um intérprete do Dart. Se a saída terminar com "OK", o teste foi aprovado.

Como editar testes do gerador de blocos

  1. Carregue tests/generators/index.html em um navegador.
  2. Escolha a parte relevante do sistema no menu suspenso e clique em "Load". Os blocos devem aparecer no espaço de trabalho.
  3. Faça alterações ou adições aos blocos.
  4. Clique em "XML".
  5. Copie o XML gerado no arquivo apropriado em tests/generators/.