Teste funcional das habilidades do Yandex Alice no Node.js

Alice, lance a habilidade


Quase um ano se passou desde que surgiu a oportunidade de criar suas habilidades para Alice, a assistente de voz da Yandex. Novas habilidades chegam diariamente no catálogo e seu número total é de várias centenas. Infelizmente, a comunicação com algumas habilidades para dizer o mínimo "não se soma". A habilidade faz um loop sobre a mesma frase ou geralmente é interrompida e não responde.


Neste artigo, considerarei escrever testes funcionais automatizados para uma habilidade no Node.js. A presença de tais testes permite que você crie melhores habilidades e confie em seu desempenho.


Ferramentas de teste existentes


O Skill for Alice é um servidor web que pode responder a solicitações POST em um formato específico. No momento, existem várias ferramentas nas quais você pode transferir o URL da habilidade e verificar seu funcionamento:


  • O console oficial do desenvolvedor , onde você pode testar suas habilidades com texto e assistir a solicitações / respostas
  • Simulador de Estação Aimylogic, suporta voz
  • Projeto de código aberto dialogs.popstas.ru para teste local de habilidades

A peculiaridade dessas ferramentas é que elas oferecem alguma interface do usuário para testes de habilidades manuais. Eu quero as melhores tradições de integração contínua
execute o comando no console, verifique automaticamente todos os scripts e faça o upload da nova versão.


Ao mesmo tempo, não quero me aprofundar nos testes de unidade de módulos de habilidades individuais. O protocolo de solicitação / resposta é registrado na documentação e é nesse nível que é melhor testar. Então, mesmo tendo reescrito completamente a arquitetura interna, não há necessidade de alterar os testes. Ou seja, em essência, esses são testes funcionais .


Não encontrei uma biblioteca concluída para o Node.js para essa tarefa, portanto, escreveremos nossa própria :)



Veja o exemplo de habilidade oficial do repositório Yandex no GitHub. Esta é a habilidade "Parrot", que simplesmente repete tudo o que o usuário disse. Construído com base na micro estrutura e contém apenas algumas linhas de código:


// server.js const micro = require('micro'); const {json} = micro; module.exports = micro(async req => { const {request, session, version} = await json(req); return { version, session, response: { text: request.original_utterance || 'Hello!', end_session: false, }, }; }); 

Na primeira chamada, a habilidade receberá uma mensagem em branco do usuário (original_utterance) e responderá "Hello!" . Em outros casos, ele simplesmente copia a mensagem do usuário no campo response.text .


Embrulhei o código de exemplo original do GitHub na função micro() para que a exportação retorne um servidor http, que usaremos nos testes.


Plano de teste


Portanto, para cobrir essa habilidade com testes, você precisa do seguinte:


  1. Crie um servidor com habilidade na porta local
  2. Verifique dois casos:
    • O usuário digita a habilidade, a habilidade deve responder "Olá!"
    • O usuário envia uma mensagem para a habilidade, a habilidade deve responder com a mesma mensagem
  3. Pare o servidor com habilidade e mostre o relatório

Ao automatizar essas verificações, você pode executá-las antes de cada confirmação e garantir que nada esteja quebrado.


Escreveremos o código de teste de acordo com o plano, usando a sintaxe do mocha . Suponha que já tenhamos alguma classe de User que possa fazer tudo o que precisamos:


 // test.js const assert = require('assert'); before(done => { //    server.listen(PORT, done); }); it('should get hello on enter', async () => { //     const user = new User(`http://localhost:${PORT}`); //       const response = await user.enter(); //    assert.equal(response.text, 'Hello!'); }); after(done => { //   server.close(done); }); 

Resta escrever a classe User e será possível executar o teste.


Usuário virtual


A principal coisa que um usuário de teste deve poder fazer é enviar solicitações POST para o URL da habilidade com dados no formato desejado. O formato da solicitação é descrito na documentação . Agora, como não precisamos de todos os campos, deixei apenas o necessário para não aumentar o código de exemplo. Classe de User com comentários:


 // user.js const fetch = require('node-fetch'); module.exports = class User { /** *  * @param {String} webhookUrl */ constructor(webhookUrl) { this._webhookUrl = webhookUrl; } /** *     */ async enter() { const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; //    ,  -   const body = this._buildRequest(''); const response = await fetch(this._webhookUrl, { method: 'post', headers, body: JSON.stringify(body), }); const json = await response.json(); return json.response; } /** *        * @param {String} message */ _buildRequest(message) { return { request: { command: message, original_utterance: message, type: 'SimpleUtterance', }, session: { new: true, user_id: 'user-1', session_id: 'session-1' }, version: '1.0' } } }; 

Lançamento


Para começar, resta importar as classes de usuário e servidor para o arquivo de teste e também definir o valor da porta na qual o servidor aumentará:


 // test.js ... const server = require('./server'); const User = require('./user'); const PORT = 3456; ... 

Instale todas as dependências necessárias:


 npm install micro node-fetch mocha 

E execute o teste:


 $ mocha test.js ✓ should get hello on enter 1 passing (34ms) 

Está tudo bem, o teste passou!


Mas antes de prosseguir, você precisa garantir que o teste realmente funcione. Para fazer isso, substitua a habilidade de resposta "Olá!" para "olá!" e execute-o novamente:


 $ mocha test.js 0 passing (487ms) 1 failing 1) should get hello on enter: AssertionError [ERR_ASSERTION]: '!' == 'Hello!' + expected - actual -! +Hello! 

O teste mostrou um erro - como deveria ser.
Agora, com certeza, consideramos o primeiro caso a ser coberto.


Ensinamos o usuário a se comunicar


O segundo caso permanece quando o usuário envia uma mensagem para a habilidade e deve receber a mesma mensagem de volta. Para que o usuário possa "se comunicar", adicionei o método say(message) à classe User . Também refatorei um pouco: fiz o envio de solicitações http para um método separado e o usei dentro de enter() e say(message) :


 // user.js const fetch = require('node-fetch'); module.exports = class User { /** *  * @param {String} webhookUrl */ constructor(webhookUrl) { this._webhookUrl = webhookUrl; } /** *     */ async enter() { //    ,  -   const body = this._buildRequest(''); return this._sendRequest(body); } /** *     * @param {String} message */ async say(message) { const body = this._buildRequest(message); return this._sendRequest(body); } /** *  http- * @param {Object} body   */ async _sendRequest(body) { const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; const response = await fetch(this._webhookUrl, { method: 'post', headers, body: JSON.stringify(body), }); const json = await response.json(); return json.response; } // ... }; 

O código de teste para o segundo caso é assim:


 it('should reply the same message', async () => { //   const user = new User(`http://localhost:${PORT}`); //    await user.enter(); //   const response = await user.say('  ?'); //    assert.equal(response.text, '  ?'); }); 

Começamos novamente e vemos que os dois testes passaram:


 $ mocha test.js ✓ should get hello on enter ✓ should reply the same message 2 passing (37ms) 

Passos adicionais


Da mesma forma, você pode adicionar scripts mais complexos à habilidade, cobrindo-os com testes. Isso garantirá que novas alterações não sejam antigas.


A infraestrutura de teste criada também pode ser aprimorada:


  • modifique a classe User para que os campos restantes na solicitação possam ser alterados (por exemplo, marque a caixa que o usuário não possui uma tela)
  • conectar cobertura de código (por exemplo, nyc )
  • pendure todas as verificações nos ganchos pré-confirmação / pré-envio (por exemplo, usando husky )

Eu tenho várias habilidades, então coloquei a classe de usuário de teste em um pacote alice-tester separado, talvez alguém seja útil.


Também publiquei o código de trabalho completo do exemplo no artigo no GitHub . Você pode clonar o repositório e experimentar.


Obrigado pela atenção!

Source: https://habr.com/ru/post/pt441978/


All Articles