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”