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 (username e password) 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 username e password 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 (firstName, lastName, email e 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 attachFile) torna 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! 👋
7 comentários em “Mantenha as interfaces da unidade pequenas”