Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Biblioteca de testes #174

Open
Andre0n opened this issue Oct 5, 2023 · 10 comments
Open

Biblioteca de testes #174

Andre0n opened this issue Oct 5, 2023 · 10 comments

Comments

@Andre0n
Copy link
Contributor

Andre0n commented Oct 5, 2023

Olá pessoal! Estava dando uma olhada nos testes da linguagem e pensei em como poderia tornar a experiência de escrever os testes mais agradável para novos e antigos contribuidores. Tive a ideia de implementar uma biblioteca de testes que oferece funções que ajude a testar os elementos da linguagem. Sei que é um tópico complicado, uma vez que isso afeta todo o escopo e funcionamento linguagem, mas acho que é interessante avaliar a proposta.

Motivações:

  • Facilidade: Permite a construção de testes de maneira simplificada.
  • Legibilidade: Torna o código dos testes mais legível e elegante.
  • Clareza: A biblioteca apresenta de forma clara e concisa o resultados.

Dificuldades:

  • Manutenção: Assegurar que a biblioteca esteja funcionando corretamente.
  • Reformulação: Reescrever os testes no novo formato.
  • Validação: Garantir que a versão da linguagem que roda os testes não contenha bugs.
  • Cobertura: Garantir que a biblioteca cubra os testes necessários.

Atuais problemas da biblioteca:

  • Cada conjunto é um objeto global no escopo da biblioteca.
  • As comparações entre objetos não é precisa.
  • Ainda não há implementação para testes do tipo (Tente/Pegue, escreva, ...)
  • Ainda não é possível passar funções para testar, apenas os valores retornados.

Funcionamento básico da biblioteca:

// testes.egua
var testes = importar("testes"); //Importando a biblioteca;

var testes_basicos = função(){
  //Inicia novo conjuto de testes.
  testes.novo_conjunto("Testes básicos");

  testes.novo_teste("Verifica se 1 == 1").esperado_que(1 == 1).seja_verdadeiro();
  testes.novo_teste("Verifica se 1 != 2").esperado_que(1 == 2).seja_falso();

  // Finaliza o conjunto de testes e mostra o resultado!.
  escreva(testes.resultados());
};

var testes_objetos = função(){
    testes.novo_conjunto("Testes de objetos");
     // Exemplos testes com objetos
    testes.novo_teste("Verifica se [1, 2] == [1, 2]").esperado_que([1,2]).seja_objeto([1, 2]);
    testes.novo_teste("Verifica se {one: 1, two: 2} == {one: 1, two: 2}")
                          .esperado_que({"one": 1, "two": 2})
                          .seja_objeto({"one": 1, "two": 2});
    escreva(testes.resultados());
};

var testes_numeros = função(){
  //Inicia novo conjuto de testes.
  testes.novo_conjunto("Testes de números");
 
  // Exemplos testes com números
  testes.novo_teste("Verifica se 0.1 + 0.2 == 0.3").esperado_que(0.1 + 0.2).seja(0.3);
  testes.novo_teste("Vefica se 0.1 + 0.3 é próximo a 0.3").esperado_que(0.1 + 0.2).seja_proximo(0.3);

  // Finaliza o conjunto de testes e mostra o resultado!.
  escreva(testes.resultados());
};
// Chamando os testes.
testes_basicos();
testes_objetos();
testes_numeros();

A saída do programa:

Testes básicos -- PASSOU
  ✔  Verifica se 1 == 1
  ✔  Verifica se 1 != 2
Testes: 2 passou, 0 falhou, 2 total

Testes de objetos -- PASSOU
  ✔  Verifica se [1, 2] == [1, 2]
  ✔  Verifica se {one: 1, two: 2} == {one: 1, two: 2}
Testes: 2 passou, 0 falhou, 2 total

Testes de números -- FALHOU
  ✖  Verifica se 0.1 + 0.2 == 0.3
	 ● Testes de números > Verifica se 0.1 + 0.2 == 0.3  
	        esperado_que(obtido).seja(esperado) //Object.is igualdade
	        Esperado: 0.3
	        Obtido: 0.30000000000000004
  ✔  Vefica se 0.1 + 0.3 é próximo a 0.3
Testes: 1 passou, 1 falhou, 2 total
@lucaspompeun
Copy link
Member

Olá @Andre0n.
A sua ideia seria criar uma biblioteca de testes em égua para reescrever os testes do arquivo /tests/tests.egua?

@Andre0n
Copy link
Contributor Author

Andre0n commented Oct 5, 2023

Sim! Hoje o arquivo tests.egua contém muitas linhas, muitas delas são chamadas da função escreva(), fora que saída não é exatamente clara.
A minha proposta é construir uma biblioteca para gerenciar esses testes, como no exemplo acima, permitindo uma melhora na escrita dos testes e clareza nos resultados. Já estou trabalhando nisso, o código acima é baseado na implementação que fiz, mas acho que é bom discutir e avaliar outras soluções para esse problema.

@lucaspompeun
Copy link
Member

Então, essa parte dos testes tem como propósito testar a linguagem após alguma alteração, pra ter certeza que nada além do que foi alterado teve impacto. Em resumo ele substitui, de uma forma burra, os testes unitários. A ideia é realmente ele apenas rodar, de maneira crua, tudo que a linguagem faz, pra garantir que o interpretador não quebrou em algum lugar.
A construção de uma biblioteca de testes em égua não seria o suficiente para testar o interpretador da linguagem em si, pelo menos não consigo enxergar isso, no entanto seria interessante para o ensino de testes unitários. Adorei a ideia, mas o propósito precisa ser alinhado.
Pra que a linguagem consiga atender aos requisitos que você mostrou acima, como testes.novo_teste("Verifica se 1 != 2").esperado_que(1 == 2).seja_falso();, creio (posso estar errado) que seja necessário a inferência de tipos, coisa que a linguagem não suporta hoje em dia. Ademais, como está a sua implementação? Você está fazendo algo no sentido da inferência de tipos? Ou adotou outra estratégia?

@Andre0n
Copy link
Contributor Author

Andre0n commented Oct 5, 2023

Entendi, implementação inicial que fiz foi em javascript mesmo e está disponível aqui: https://github.com/Andre0n/egua/tree/bib_testes/src/lib/testes

A implementação é um pouco extensa, mas acho que dá para pegar a ideia com os códigos abaixo:

Basicamante ele composta de alguns módulos:

  • index.js
  • conjunto.js
  • correspondencias.js
  • saida_testes.js

No módulo index (o que fica disponível na linguagem):

const StandardFn = require("../../structures/standardFn");
const { novo_conjunto } = require("./conjunto");
const { correspondencias } = require("./correspondencias");
const { novo_erro } = require("./erro");

let conjunto_atual = null;

const esperado_que = (obtido) => {
    return correspondencias(obtido, conjunto_atual);
};

module.exports.novo_conjunto = function (descricao = "") {
    if (typeof descricao !== "string") {
        novo_erro(this.token, "A descrição do conjunto deve ser um texto");
    }
    if (descricao === "") {
        novo_erro(this.token, "A descrição do conjunto não pode ser vazia");
    }
    conjunto_atual = novo_conjunto(descricao, this.token);
};

module.exports.novo_teste = function (descricao = "") {
    if (typeof descricao !== "string") {
        novo_erro(this.token, "A descrição  do teste deve ser um texto");
    }
    if (descricao === "") {
        novo_erro(this.token, "A descrição  do teste não pode ser vazia");
    }
    conjunto_atual.teste_atual = descricao;
    conjunto_atual.token = this.token;
    return {
        esperado_que: new StandardFn(0, esperado_que),
    };
};

module.exports.resultados = function () {
    if (conjunto_atual == null) {
        novo_erro(this.token, "O conjunto de testes não foi descrito");
    }
    const resumo = conjunto_atual.resumo();
    conjunto_atual = null;
    return resumo;
};

No módulo do conjunto temos a função novo conjunto que retorna um objeto:

const novo_conjunto = (descricao) => {
    return {
        descricao: descricao,
        total_passou: 0,
        total_falhou: 0,
        teste_atual: "",
        resultados: [],
        token_atual: null,
        // Lista de funções auxiliares
        ...
};
module.exports.novo_conjunto = novo_conjunto;

No módulo de correspondencias temos a função novo conjunto que retorna um objeto:

const correspondencias = (obtido, conjuto_testes) => {
    conjunto_atual = conjuto_testes;
    return {
        seja: new StandardFn(1, (esperado) => {
            if (Object.is(obtido, esperado)) {
                conjuto_testes.teste_passou();
                return;
            }
            conjuto_testes.teste_falhou("seja", obtido, esperado);
        }),
        // Outras funções omitidas...
        // Código baseado na biblioteca Jest
        seja_proximo: new StandardFn(1, (esperado, precisao = 0.1) => {
            if (typeof esperado !== "number") {
                conjuto_testes.erro("`esperado` precisa ser do tipo número");
            }
            if (typeof obtido !== "number") {
                conjuto_testes.erro("`obtido` precisa ser do tipo número");
            }

            let passou;
            if (obtido === Infinity && esperado === Infinity) {
                passou = true;
            } else if (obtido === -Infinity && esperado === -Infinity) {
                passou = true;
            } else {
                let dif_esperado = Math.pow(10, -precisao) / 2;
                let dif_obtido = Math.abs(esperado - obtido);
                passou = dif_obtido < dif_esperado;
            }

            if (passou) {
                conjuto_testes.teste_passou();
                return;
            }
            conjuto_testes.teste_falhou("seja_proximo", obtido, esperado);
        }),
        // Outras funções omitidas...
    };
};

module.exports.correspondencias = correspondencias;

E o módulo saida_testes basicamente mostra o resultado dos testes.

const reporta_seja = (esperado, obtido) => {
    let menssagem =
        "\tesperado_que(obtido).seja(esperado) //Object.is igualdade";
    menssagem += `\n\tEsperado: ${esperado}\n\tObtido: ${obtido}`;
    return menssagem;
};
// Outras implementações do tipo reporta_algo
const novo_resultado = (
    passou,
    nome_teste,
    nome_conjunto,
    onde,
    obtido,
    esperado
) => {
    let resultado = `  ${passou ? "✔" : "✖"}  ${nome_teste}`;

    if (passou) {
        return resultado;
    }

    resultado += `\n\t ● ${nome_conjunto} > ${nome_teste}  \n`;

    switch (onde) {
        case "seja":
            resultado += reporta_seja(esperado, obtido);
            break;
        // Outros cases...
    }
    return resultado;
};
module.exports.novo_resultado = novo_resultado;

@lucaspompeun
Copy link
Member

Hm... Interessante. Retornar a chamada de outra função tem sido suficiente pra permitir o uso de testes.novo_teste("Verifica se 1 != 2").esperado_que(1 == 2).seja_falso();?

@Andre0n
Copy link
Contributor Author

Andre0n commented Oct 5, 2023

Bom, não sei se foi isso que quis dizer:

var testes = importar("testes");

var retorna_1 = função(){
    retorna 1;
};

var testes_chamada = função(){

  testes.novo_conjunto("Testes função");

  testes.novo_teste("Verifica se a função retorna_1 retorna 1").esperado_que(retorna_1() == 1).seja_verdadeiro();
  escreva(testes.resultados());
};

testes_chamada();

A saída foi:

Testes função -- PASSOU
  ✔  Verifica se a função retorna_1 retorna 1
Testes: 1 passou, 0 falhou, 1 total

@lucaspompeun
Copy link
Member

Era isso mesmo, está ficando sensacional.

@Andre0n
Copy link
Contributor Author

Andre0n commented Oct 5, 2023

Valeu! Ainda tem bastante coisa para fazer, mas é basicamente isso. Vou tentar seguir lógica de ser um instrumento de aprendizagem.

@lucaspompeun
Copy link
Member

Pretendo, em breve, escrever testes unitários pro interpretador pra poder excluir o arquivo teste.egua.

@lucaspompeun lucaspompeun changed the title Repensando os testes da Linguagem Biblioteca de testes Oct 5, 2023
@Andre0n
Copy link
Contributor Author

Andre0n commented Oct 5, 2023

É uma boa ideia! Eu estava cogitando a ideia de abrir uma issue em relação a isso, mas achei que os testes fossem ficar escritos na linguagem mesmo. Bom era isso, vou terminar de implementar e documentar o código e subir uma PR em breve. Valeu!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants