Testes de unidade

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

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

Testes JS

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

Testes em execução

No Blockly e no blockly-samples, o npm run test vai executar os testes de unidade. No Blockly, isso também executa outros testes, como linting e compilação. Também é possível abrir tests/mocha/index.html em um navegador para executar interativamente todos os testes do Mocha.

Como programar testes

Usamos a interface Mocha TDD para executar testes. Os testes são organizados em conjuntos, que podem conter subconjuntos 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 conjunto pode ter um método setup e teardown que será chamado antes e depois, respectivamente, de cada teste nesse conjunto.

Auxiliares de teste

Temos várias funções auxiliares específicas do Blockly que podem ser úteis ao escrever testes. Eles podem ser encontrados no núcleo e em amostras em bloco.

As funções auxiliares incluem sharedTestSetup e sharedTestTeardown, que precisam ser chamadas antes e depois dos testes. Consulte a seção "Requisitos".

sharedTestSetup:
  • Configura timers falsos do sinon (em alguns testes, será necessário 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 devem ser acessíveis:
    • this.clock (mas não pode ser restaurado, caso contrário, causará problemas no sharedTestTeardown)
    • this.eventsFireStub
    • this.sharedCleanup (para ser usado com addMessageToCleanup e addBlockTypeToCleanup) (OBSERVAÇÃO: não é necessário usar addBlockTypeToCleanup se você definiu o bloco usando defineBlocksWithJsonArray).

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

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

Requisitos de teste

  • Cada teste precisa chamar sharedTestSetup.call(this); como a primeira linha na configuração do pacote mais externo e sharedTestTeardown.call(this); como a última linha na desmontagem do pacote mais 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 index.html de teste. Veja abaixo um exemplo.
  • Descarte corretamente o this.workspace. Na maioria dos testes, você define this.workspace no pacote mais externo e o usa em todos os testes posteriores, mas, em alguns casos, pode defini-lo ou redefini-lo em um pacote interno (por exemplo, um dos testes exige um espaço de trabalho com opções diferentes das que você configurou originalmente). Ele precisa ser descartado no final do teste.
    • Se você definir this.workspace no pacote mais externo e nunca o redefinir, não será necessário fazer mais nada. Ele será descartado automaticamente em sharedTestTeardown.
    • Se você definir this.workspace pela primeira vez em um pacote interno (ou seja, se você não o tiver definido no pacote mais externo), será necessário descartá-lo manualmente chamando workspaceTeardown.call(this, this.workspace) no descarte desse pacote.
    • Se você definir this.workpace no pacote mais externo, mas redefini-lo em um pacote de testes interno, primeiro 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 eliminação desse pacote interno.

Estrutura de teste

Os testes de unidade geralmente seguem uma estrutura definida, que pode ser resumida como organizar, agir, afirmar.

  1. Organizar: configurar o estado do mundo e todas as condições necessárias para o comportamento em teste.
  2. Ação: 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 as interações com objetos simulados para verificar a exatidão.

Em um teste simples, pode não haver nenhum comportamento a ser organizado, e os estágios "act" e "assert podem ser combinados ao incluir a chamada para o código em teste na declaração. Para casos mais complexos, seus testes serão mais legíveis se você seguir esses três estágios.

Confira um exemplo de arquivo de teste (simplificado).

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:

  • Uma suíte pode conter outras suítes com métodos setup e teardown adicionais.
  • Cada conjunto e teste tem 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 com falhas.
    • A ordem dos parâmetros é chai.assert.equal(actualValue, expectedValue, optionalMessage). Se você trocar actual e expected, as mensagens de erro não vão fazer sentido.
  • O 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 reais porque ela não é relevante para este teste. Só nos interessa como os resultados são usados pelo método em teste. O Sinon substitui a função getMetrics para retornar uma resposta padrão que pode ser verificada facilmente nas nossas afirmações de teste.
  • Os métodos setup de cada conjunto precisam conter apenas a configuração genérica que se aplica a todos os testes. Se um teste para um comportamento específico depender de uma determinada condição, essa condição precisa ser claramente indicada no teste relevante.

Testes de depuração

  • É possível abrir os testes em um navegador e usar as ferramentas para desenvolvedores para definir pontos de interrupção e investigar se os testes estão falhando ou passando de forma inesperada.
  • Defina .only() ou .skip() em um teste ou pacote para executar apenas esse conjunto ou pular um teste. Exemplo:

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

    Não se esqueça de removê-los antes de confirmar o código.

Testes do gerador de blocos

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

  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 local "file://" (problemas 41024 e 47416).
  2. Escolha a parte relevante do sistema para testar 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 foi aprovado.
  4. Clique em "Python".
    Copie e execute o código gerado em um intérprete de 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 interpretador do 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 interpretador 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 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 "Carregar". Os blocos devem aparecer no espaço de trabalho.
  3. Faça alterações ou adições nos blocos.
  4. Clique em "XML".
  5. Copie o XML gerado para o arquivo apropriado em tests/generators/.