É uma prática ruim que os testes de unidade sejam dependentes um do outro?

9

Digamos que eu tenha algum tipo de teste de unidade como este:

let myApi = new Api();

describe('api', () => {

  describe('set()', () => {
    it('should return true when setting a value', () => {
      assert.equal(myApi.set('foo', 'bar'), true);
    });
  });

  describe('get()', () => {
    it('should return the value when getting the value', () => {
      assert.equal(myApi.get('foo'), 'bar');
    });
  });

});

Então agora eu tenho 2 testes de unidade. Um define um valor em uma API. O outro testa para garantir que o valor adequado seja retornado. No entanto, o segundo teste depende do primeiro. Devo adicionar um .set()método no segundo teste antes do get()com o único objetivo de garantir que o segundo teste não dependa de mais nada?

Além disso, neste exemplo, devo instanciar myApicada teste em vez de fazê-lo uma vez antes dos testes?

Jake Wilson
fonte

Respostas:

15

Sim, é uma prática ruim. Os testes de unidade precisam ser executados independentemente um do outro, pelos mesmos motivos pelos quais você precisa de qualquer outra função para executar de forma independente: você pode tratá-lo como uma unidade independente.

Devo adicionar um método .set () no segundo teste antes do get () com o único objetivo de garantir que o segundo teste não dependa de mais nada?

Sim. No entanto, se esses são apenas métodos getter e setter vazios, eles não contêm nenhum comportamento e você realmente não precisa testá-los, a menos que tenha uma reputação de dedilhar coisas de maneira que o getter / setter compila, mas define ou obtém o campo errado.

Robert Harvey
fonte
No meu exemplo, vamos dizer que myApié um objeto instanciado. Devo restabelecer myApiem cada teste de unidade? Ou reutilizá-lo entre os testes tem o potencial de fazer com que os testes dêem falsos positivos, etc.
Jake Wilson
1
@JakeWilson Há um livro chamado Pragmatic Unit Testing que vai sobre os conceitos básicos de teste de unidade, como forma de evitar testes interferem uns com os outros, etc.
rwong
Outro exemplo: se você usa JUnit, a ordem da sua função não é definida pela ordem em que você as escreveu na classe. @JakeWilson Se a sua API for apátrida, você poderá reutilizá-la, se não a restabelecer.
Walfrat
2

Tente seguir a estrutura de organizar-agir-afirmar para cada teste.

  1. Organize seus objetos etc. e coloque-os em um estado conhecido (um acessório de teste). Às vezes, essa fase inclui declarações para mostrar que você está de fato no estado em que pensa estar.
  2. Aja, ou seja: execute o comportamento que você está testando.
  3. Afirme que você obteve o resultado esperado.

Seus testes não se preocupam em criar um estado conhecido primeiro, portanto eles não fazem sentido isoladamente.

Além disso, os testes de unidade não necessariamente testam apenas um único método - os testes de unidade devem testar uma unidade. Normalmente, esta unidade é uma classe. Alguns métodos como get()apenas fazem sentido em combinação com outro.

Testar getters e setters é sensato, principalmente em linguagens dinâmicas (apenas para garantir que eles estejam realmente lá). Para testar um getter, precisamos fornecer um valor conhecido primeiro. Isso pode acontecer através do construtor ou através de um levantador. Ou visto de outra maneira: testar o getter está implícito nos testes do setter e do construtor. E o levantador, ele sempre retorna true, ou apenas quando o valor foi alterado? Isso pode levar aos seguintes testes (pseudocódigo):

describe Api:

  it has a default value:
    // arrange
    api = new Api()
    // act & assert
    assert api.get() === expected default value

  it can take custom values:
    // arrange & act
    api = new Api(42)
    // act & assert
    assert api.get() === 42

  describe set:

    it can set new values:
      // arrange
      api = new Api(7)
      // act
      ok = api.set(13)
      // assert
      assert ok === true:
      assert api.get() === 13

    it returns false when value is unchanged:
      // arrange
      api = new Api(57)
      // act
      ok = api.set(57)
      // assert
      assert ok === false
      assert api.get() === 57

Reutilizar o estado de um teste anterior tornaria nossos testes muito frágeis. Reordenar os testes ou alterar o valor exato em um teste pode causar falhas aparentemente não relacionadas. Assumir que um estado específico também pode ocultar bugs se for aprovado nos testes que realmente devem falhar. Para evitar isso, alguns executores de teste têm opções para executar os casos de teste em uma ordem aleatória.

No entanto, há casos em que reutilizamos o estado fornecido pelo teste anterior. Em particular, quando a criação de um equipamento de teste leva muito tempo, combinamos muitos casos de teste em um conjunto de testes. Embora esses testes sejam mais frágeis, eles ainda podem ser mais valiosos agora, porque podem ser realizados com mais rapidez e frequência. Na prática, a combinação de testes é desejável sempre que os testes envolvem um componente manual, quando um banco de dados grande é necessário ou ao testar máquinas de estado.

amon
fonte