Eu queria me ensinar a usar a abordagem TDD e tinha um projeto em que vinha trabalhando há um tempo. Não era um projeto grande, então pensei que seria um bom candidato para TDD. No entanto, sinto que algo deu errado. Deixe-me dar um exemplo:
Em um nível alto, meu projeto é um suplemento para o Microsoft OneNote que me permitirá rastrear e gerenciar projetos com mais facilidade. Agora, eu também queria manter a lógica comercial para isso o mais dissociada possível do OneNote, caso decidisse criar meu próprio armazenamento personalizado e back-end algum dia.
Primeiro, comecei com um teste básico de aceitação de palavras simples para descrever o que queria que meu primeiro recurso fizesse. Parece algo assim (emburrecendo-o por questões de concisão):
- Cliques do usuário criar projeto
- Tipos de usuário no título do projeto
- Verifique se o projeto foi criado corretamente
Ignorando o material da interface do usuário e algum planejamento intermediário, chego ao meu primeiro teste de unidade:
[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
var testController = new Controller();
Project newProject = testController(A.Dummy<String>());
Assert.IsNotNull(newProject);
}
Por enquanto, tudo bem. Vermelho, verde, refatorador, etc. Tudo bem, agora ele precisa realmente salvar coisas. Cortando alguns passos aqui, acabo com isso.
[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
var fakeDataStore = A.Fake<IDataStore>();
var testController = new Controller(fakeDataStore);
String expectedTitle = fixture.Create<String>("Title");
Project newProject = testController(expectedTitle);
Assert.AreEqual(expectedTitle, newProject.Title);
}
Eu ainda estou me sentindo bem neste momento. Ainda não tenho um armazenamento de dados concreto, mas criei a interface como previa que fosse.
Vou pular algumas etapas aqui porque esta postagem está ficando longa o suficiente, mas segui processos semelhantes e, eventualmente, chego a esse teste para meu armazenamento de dados:
[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
/* snip init code */
testDataStore.SaveNewProject(A.Dummy<IProject>());
A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}
Isso foi bom até que eu tentei implementá-lo:
public String SaveNewProject(IProject project)
{
Page projectPage = oneNoteInterop.CreatePage(...);
}
E há o problema exatamente onde está o "...". Percebo agora, neste ponto, que o CreatePage requer um ID de seção. Eu não percebi isso de volta quando estava pensando no nível do controlador, porque estava preocupado apenas em testar os bits relevantes para o controlador. No entanto, todo o caminho até aqui agora percebo que tenho que pedir ao usuário um local para armazenar o projeto. Agora eu tenho que adicionar um ID de local ao armazenamento de dados, depois adicionar um ao projeto, adicionar um ao controlador e adicioná-lo a TODOS os testes que já foram escritos para todas essas coisas. Tornou-se entediante muito rapidamente e não posso deixar de sentir que seria mais rápido se eu desenhasse o design com antecedência, em vez de deixá-lo ser projetado durante o processo TDD.
Alguém pode me explicar se fiz algo errado nesse processo? Existe alguma maneira de evitar esse tipo de refatoração? Ou isso é comum? Se é comum, existem maneiras de torná-lo mais indolor?
Obrigado a todos!
fonte
Respostas:
Embora o TDD seja (justamente) apresentado como uma maneira de projetar e expandir seu software, ainda é uma boa idéia pensar sobre o design e a arquitetura com antecedência. IMO, "esboçar o design antes do tempo" é um jogo justo. Muitas vezes, isso estará em um nível mais alto do que as decisões de design às quais você será conduzido através do TDD.
Também é verdade que quando as coisas mudam, você geralmente precisará atualizar os testes. Não há como eliminar isso completamente, mas há algumas coisas que você pode fazer para tornar seus testes menos frágeis e minimizar a dor.
Tanto quanto possível, mantenha os detalhes da implementação fora de seus testes. Isso significa apenas testar através de métodos públicos e, sempre que possível, favorecer a verificação baseada no estado em vez da interação . Em outras palavras, se você testar o resultado de algo e não as etapas para chegar lá, seus testes deverão ser menos frágeis.
Minimize a duplicação no seu código de teste, como faria no código de produção. Este post é uma boa referência. No seu exemplo, parece doloroso adicionar a
ID
propriedade ao seu construtor porque você o invocou diretamente em vários testes diferentes. Em vez disso, tente extrair a criação do objeto para um método ou inicializá-lo uma vez para cada teste em um método de inicialização de teste.fonte
Talvez talvez não
Por um lado, o TDD funcionou bem, oferecendo testes automatizados à medida que você constrói a funcionalidade e quebrando imediatamente quando você precisava alterar a interface.
Por outro lado, talvez se você tivesse iniciado com o recurso de alto nível (SaveProject) em vez de um recurso de nível inferior (CreateProject), teria notado a falta de parâmetros mais cedo.
Então, novamente, talvez você não teria. É um experimento irrepetível.
Mas se você estiver procurando uma lição para a próxima vez: comece do topo. E pense no design o quanto quiser primeiro.
fonte
https://frontendmasters.com/courses/angularjs-and-code-testability/ De cerca de 2:22:00 até o fim (cerca de 1 hora). Lamentamos que o vídeo não seja gratuito, mas não encontrei um que o explique tão bem.
Uma das melhores apresentações para escrever código testável está nesta lição. É uma classe AngularJS, mas a parte de teste é baseada no código java, principalmente porque o que ele está falando não tem nada a ver com a linguagem e tudo a ver com a escrita de um bom código testável em primeiro lugar.
A mágica é escrever código testável, em vez de escrever testes de código. Não se trata de escrever código que finge ser um usuário.
Ele também passa algum tempo escrevendo as especificações na forma de asserções de teste.
fonte