Mais um post da série de qualidade de código em teste de software

Se você está chegando neste post agora e ainda não leu os conteúdos anteriores, recomendo começar por eles. Seguem os links:

Agora se você já leu os primeiros posts da série, vamos falar sobre a quarta guideline do Better Code Hub: Mantenha as interfaces da unidade pequenas.

Segundo o BCH:

  • Manter o número de parâmetros baixo torna as unidades fáceis de entender e reutilizar.
  • Limite o número de parâmetros por unidade para no máximo 2.
  • O número de parâmetros pode ser reduzido agrupando parâmetros relacionados em objetos.
  • De forma alternativa, tente extrair partes da unidade que precisam de menos parâmetros.

Manter o número de parâmetros baixo torna as unidades fáceis de entender e reutilizar

Creio que um exemplo será melhor para explicar. Vejamos o seguinte código:

class LoginPage {
  constructor() {
    this._usernameField = element(by.css('[data-test="username-field"]'));
    this._passwordField = element(by.css('[data-test="password-field"]'));
    this._submitButton = element(by.css('[data-test="login-submit-btn"]'));
  }

  get usernameField() {
    return this._usernameField;
  }

  get passwordField() {
    return this._passwordField;
  }

  get submitButton() {
    return this._submitButton;
  }

  login(username, password) {
    this.usernameField.sendKeys(username);
   
 this.passwordField.sendKeys(password);
   
 this.submitButton.click();

  }
}

module.exports = LoginPage;

O código acima é bastante legível e auto explicativo, e veja que já que o método login recebe somente dois argumentos (usernamepassword) o mesmo fica fácil de ser compreendido, onde o primeiro argumento (username) é digitado no campo usernameField e o segundo argumento (password) é digitado no campo passwordField. Por fim o submitButton é clicado para submeter o suposto formulário de login.

Agora vejamos um exemplo que não respeita esta guideline:

class ContactPage {
  constructor() {
    this._firstNameField = element(by.css('[data-test="first-name-field"]'));
    this._lastNameField = element(by.css('[data-test="last-name-field"]'));
    this._emailField = element(by.css('[data-test="email-field"]'));
    this._messageField = element(by.css('[data-test="message-field"]'));
  }

  get firstNameField() {
    return this._firstNameField;
  }

  get lastNameField() {
    return this._lastNameField;
  }

  get emailField() {
    return this._emailField;
  }

  get messageField() {
    return this._messageField;
  }

  fillFormWithPossibleAttachment(firstName, lastName, email, message, attachSampleFile = false) {
    this.firstNameField.sendKeys(firstName);
   
 this.lastNameField.sendKeys(lastName);
   
 this.emailField.sendKeys(email);
   
 this.messageField.sendKeys(message);

   

 if (attachSampleFile) {

      /* some code that attaches a sample file in a file input field */
   
 }

  }
}

module.exports = LoginPage;

Perceba que no código acima, o método fillFormWithPossibleAttachment não é tão fácil de ser entendendido, e um dos motivos disso é o fato dele receber muitos argumentos, sendo alguns deles relacionados (firstName, lastName, email e message), e  outro uma flag (attacthSampleFile), a qual recebe um valor default false.

Além disso, você provavelmente teve que rolar o código horizontamente para ler todos os argumentos necessários.

Em seguida iremos revisitar este método, refatorado para simplificar sua chamada, implementação e entendimento.

Limite o número de parâmetros por unidade para no máximo 2

O método login do primeiro exemplo mostra tal prática, onde somente o usernamepassword são necessários quando chamando tal método, e como pode ser visto, o código é simples de ser entendido e o método de ser usado.

Veja alguns exemplos de sua chamada:

// login with valid user and password
page.
login('valid-user', 'valid-password');



// login tentative with invalid password
page.
login('valid-user', 'invalid-password');



// login tentative with an empty password

page.login('some-user', '');



// login tentative without user and password
page.
login();

Como pode ser visto, tal método é tão simples que pode ser usado com diferentes combinações de parâmetros para testar diferentes condições/comportamentos de uma aplicação, sem adição de complexidade.

O número de parâmetros pode ser reduzido agrupando parâmetros relacionados em objetos

Conforme falei que iria revisitar o método fillFormWithPossibleAttachment, vamos à ele, porém refatorado.

class ContactPage {
  ...

  fillFormWithPossibleAttachment(
    data,
    attachSampleFile = false)
  {
    this.firstNameField.sendKeys(data.firstName);
 
   this.lastNameField.sendKeys(data.lastName);
 
   this.emailField.sendKeys(data.email);
 
   this.messageField.sendKeys(data.message);

  

  if (attachSampleFile) {

      /* some code that attaches a sample file in a file input field */
  
  }

  }
}

module.exports = LoginPage;

Veja agora que apesar de a implementação do corpo do método ser praticamente a mesma, a assinatura do método é mais simples, onde o primeiro argumento esperado é um objeto (data), o qual deve possuir os atributos necessários (firstNamelastNameemail message) com seus determinados valores, e o segundo e último argumento é uma flag que indica se tal formulário deve ser preenchido ou não com um arquivo anexado. Perceba também que a única mudança no corpo do método, é que os valores digitados nos campos são acessados a partir dos atributos do objeto data.

Desta forma, não estamos ferindo a guideline, visto que o número de parâmetros para a unidade não ultrapassa o máximo de 2.

De forma alternativa, tente extrair partes da unidade que precisam de menos parâmtros

Outra alternativa para o método fillFormWithPossibleAttachment, seguindo a sugestão acima, poderia ser a seguinte:

class ContactPage {
  ...

  fillForm(data) {

    this.firstNameField.sendKeys(data.firstName);
  
  this.lastNameField.sendKeys(data.lastName);

    this.emailField.sendKeys(data.email);
  
  this.messageField.sendKeys(data.message);
  
}



  attachFile() {

    /* some code that attaches a sample file in a file input field */
  
}
}

module.exports = LoginPage;

Perceba que em vez de ter um método que faz duas coisas distintas (preencher campos de texto em um formulário e anexar um arquivo), agora temos dois métodos, um responsável por cada parte, tornando o código mais simples, e de certa forma, mais modularizado.

Veja um exemplo de uma suite de testes que utiliza ambos métodos, em casos de teste diferentes, para testar cenários distintos:

const ContactPage = require('../page-objects/Contact');

describe('Formulário de contato', () => {
  const page = new ContactPage();


  beforeEach(() => browser.get('/contact');



  const validFormData = {

    firstName: "João",

    lastName: "Silva",

    email: "joao@silva.com",

    message: "Uma mensagem qualquer",

  };



  it('submete formulário sem anexo', () => {

    page.fillForm(validFormData);



    // submissão do formulário



    // verificação

  });



  it('submete formulário com anexo', () => {

    page.fillForm(validFormData);

    page.attachFile();



    // submissão do formulário



    // verificação

  });

});

Note que a separação do método fillFormWithPossibleAttachment em dois (fillForm e attachFiletorna a escrita dos testes mais simples e direta ao ponto, onde agora não há mais um método com mais de uma responsabilidade, ou seja, onde o princípio de responsabilidade única não é violado.


Por hoje é só.

O próximo post da série será: Separe responsabilidades em módulos.

Até a próxima e bons testes! 👋

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s