Teste de Unidade de Qualidade: Parte 2
Escolha o seu botão na escola de Londres ou na escola clássica de testes de unidade.
Esta é a parte II de uma série de testes de unidade de qualidade, no primeiro artigo, analisamos os testes de unidade de alto nível, se você não leu o artigo, leia-o primeirohttps://hellosagar.hashnode.dev/quality-unit-testing-part-1.
Para recapitular um pouco da parte 1
Unit testing means
1. Testing a piece of code.
2. Blazingly fast to run .relatively to Integration and E2E tests.
3. In an isolated manner.
You can't argue on the first two points, but the third one is where the debate is started.
Here, the isolation can vary depending on the approach you are following. There are mainly two schools of thought are:
- Classical school of unit testing or Detroit or Chicago Style.
- London school of unit testing or Mockist.
Exposição (contexto de plotagem)
Temos que escrever um teste de unidade para a classe Classroomque contém o comportamento de sala de aula de uma escola. Isso adiciona ao banco de dados o usuário que possui um endereço de e-mail escolar válido.
class Classroom(
private val admin: Admin
) {
fun add(email: String, type: Type): Boolean {
return if (email.endsWith("@school.com")){
admin.add(email = email, type = type)
}else {
false
}
}
}
class Admin {
private val people: MutableMap<String, Type> = mutableMapOf()
fun get(email: String): Boolean {
return people[email] != null
}
fun add(email: String, type: Type): Boolean {
return if (people[email] == null) {
people[email] = type
true
} else {
false
}
}
}
- SUT ou Sistema em teste: Refere-se à classe que está sendo testada.
- MUT ou Método em teste: Refere-se ao método no SUT que está sendo testado. SUT e MUT são freqüentemente usados como sinônimos.
- Dependência: Objeto que o SUT precisa para funcionar corretamente.
- Teste duplo: versão mais simples de uma dependência para facilitar o teste.
Existem dois tipos de dependências:
- Dependência compartilhada : A dependência que é compartilhada entre diferentes testes e as mudanças em seu estado são visíveis em todos os testes. Isso geralmente é uma variável de nível de classe ou uma variável estática de uma classe diferente. Exemplos são bancos de dados e sistemas de arquivos.
- Dependência privada : não é compartilhada entre diferentes testes e geralmente é uma variável de nível de método. É de mais dois tipos: -
- Dependência mutável : o estado de um objeto é modificado durante a execução do teste de unidade.
- Value Object : Objeto imutável que representa um valor simples, como um número ou uma string, e não possui nenhuma identidade ou comportamento associado.
- Unidade : Testa apenas uma única classe por vez.
- Isolamento : Substitua todas as dependências por um teste duplo, para permitir que o SUT se concentre apenas na classe única, ou seja, SUT.
- Dependência : usa test double para todas as dependências, exceto as imutáveis.
- Assert: Testa a interação para verificar o comportamento esperado.
- Organizar: Configure as pré-condições e entradas necessárias, que podem envolver a criação de objetos e a configuração de simulações.
- Act: Invoque o MUT com as entradas necessárias.
- Assert: Verifique se os testes produziram o resultado esperado.
- Na linha 1 do segundo caso de teste, apenas definimos o e- mail necessário para passar como entrada para o MUT.
- Na linha 2, em vez de uma instância real da
Adminclasse, estamos usando um mock que atua como uma dependência mutável privada daClassroomclasse. - Na linha 3, estamos mencionando que, quando
add()o método deAdminuma classe é invocado com o email e oSTUDENTtipo fornecidos, queremos retornartrue. - Na linha 4, estamos configurando o SUT com a entrada necessária, ou seja
Admin, classe. - Na linha 1, estamos afirmando a saída retornada pelo MUT e verificando também essa interação, ou seja, se o e- mail terminar com o
@schoole-mail, sabemos queAdminclasseadd()o método deve ser invocado e é isso que verificamos também. - Ele remove a adivinhação de qual parte da base de código está quebrada que faz com que o teste falhe, pois se concentra apenas no comportamento do SUT, removendo todas as influências externas.
- Sem lidar com as dependências circulares e o grande gráfico de objetos como o conjunto de bonecas russas :)
- A maior desvantagem é a superespecificação, que acopla os testes aos detalhes de implementação isso acontece porque os mocks são poderosos e com grandes poderes vêm grandes responsabilidades.
- Unidade : Considere uma unidade como uma unidade de comportamento necessária para testar como se levasse mais de uma classe, mas é sempre uma boa ideia manter uma classe por teste.
- Isolamento : significa executar testes em paralelo, em sequência ou em qualquer ordem, mas isso não deve afetar a saída um do outro.
- Dependência : usa o teste duplo apenas para as dependências compartilhadas, como banco de dados ou sistema de arquivos, resto é instâncias de objetos prontos para produção.
- Assert: Verifica o valor de retorno ou alteração no estado da classe.
- Na linha 1, apenas definimos o e- mail necessário para passar como entrada para o MUT.
- Na linha 2, aqui estamos usando a instância do objeto em vez do objeto simulado, como fizemos na abordagem de Londres.
- Na linha 3, estamos configurando a pré-condição para que justifique o comportamento do teste de unidade, por exemplo, aqui estamos verificando se o e-mail já existe, o MUT deve retornar,
falseportanto, antes de invocar o MUT, estamos adicionando o valor naAdminclasse para simular aquele ambiente. - Na linha 4, estamos configurando o SUT com a entrada necessária, ou seja
Admin, classe. - Verificamos o valor de retorno do MUT, mas também verificamos a mudança no estado da
Adminclasse - Testes produzidos que são focados na saída ou comportamento do estado que o torna dissociado da implementação.
- Promove alta coesão (acoplamento frouxo) por não depender da interação.
- A cobertura redundante é um antipadrão associado a essa abordagem em que vários testes abordam a mesma funcionalidade e, se esse código quebrar, todos os testes falharão, o que é considerado um desperdício de recursos.
Essa abordagem também é conhecida como “ teste de caixa branca” ou abordagem “ de fora para dentro ” porque no TDD, quando a implementação ainda não está definida, o desenvolvedor pode começar a definir o comportamento dos testes, substituindo a dependência por simulações, ou seja , teste duplo e, em seguida, detalhando de de cima para baixo , definindo testes de unidade sem se preocupar com as especificidades da implementação da classe.
Características:
Minha linguagem de escolha é Kotlin e uso mockk para zombar de dependências.
internal class ClassroomLondonStyleTest{
@Test
fun `WHEN email domain name is invalid, THEN returns false`(){
// Arrange
val admin = mockkClass(Admin::class)
val classroom = Classroom(admin)
// Act
val output = classroom.add("[email protected]", Type.STAFF)
// Assert
Assert.assertFalse(output)
verify(exactly = 0) { admin.add(any(), any()) }
}
@Test
fun `WHEN email already exist, THEN returns false`(){
// Arrange
val email = "[email protected]"
val admin = mockkClass(Admin::class)
every { admin.add(email, Type.STUDENT) } returns false
val classroom = Classroom(admin)
// Act
val output = classroom.add(email, Type.STUDENT)
// Assert
Assert.assertFalse(output)
verify(exactly = 1) { admin.add("[email protected]", Type.STUDENT) }
}
@Test
fun `WHEN email is entered first time, THEN returns true`(){
// Arrange
val email = "[email protected]"
val admin = mockkClass(Admin::class)
every { admin.add(email, Type.STUDENT) } returns true
val classroom = Classroom(admin)
// Act
val output = classroom.add(email, Type.STUDENT)
// Assert
Assert.assertTrue(output)
verify(exactly = 1) { admin.add("[email protected]", Type.STUDENT) }
}
}
Se você notou que dividi o teste em 3 fases usando AAA para organizar os testes, isso foi apresentado no livro de Martin Fowler Refactoring: Improving the Design of Existing Code .
Começando com a fase de arranjo:
Invocar o add()método MUT, ou seja, passando entradas emaile typeque são objetos de valor e salva a saída do MUT para a fase assert.
Em seguida, fase de afirmação:
Escola clássica de testes de unidade ou abordagem estilo Detroit ou Chicago
Essa abordagem também é conhecida como “ teste de caixa preta” ou “ abordagem de dentro para fora ” porque, ao seguir o TDD aqui, geralmente usamos uma instância real de objetos e não zombamos de nada, a menos que seja uma dependência compartilhada . Portanto, ele começa a partir de uma unidade de código de baixo nível e segue seu caminho até o topo.
Características:
internal class ClassroomChicagoStyleTest{
@Test
fun `WHEN email domain name is invalid, THEN returns false`(){
// Arrange
val admin = Admin()
val classroom = Classroom(admin)
// Act
val output = classroom.add("[email protected]", Type.STAFF)
// Assert
Assert.assertFalse(output)
}
@Test
fun `WHEN email already exist, THEN returns false`(){
// Arrange
val email = "[email protected]"
val admin = Admin()
admin.add(email, Type.STUDENT)
val classroom = Classroom(admin)
// Act
val output = classroom.add(email, Type.STUDENT)
// Assert
Assert.assertFalse(output)
}
@Test
fun `WHEN email is entered first time, THEN returns true`(){
// Arrange
val admin = Admin()
val classroom = Classroom(admin)
// Act
val output = classroom.add("[email protected]", Type.STUDENT)
// Assert
Assert.assertTrue(output)
Assert.assertTrue(admin.get("[email protected]"))
}
}
Vamos pegar o segundo caso de teste aqui também ( WHEN email is entered first time, THEN returns true) para fins de explicação.
Começando com a fase de arranjo:
Invocando o MUT, ou seja add(), o método passando entradas emaile typeambos são objetos de valor e salvam a saída do MUT da fase assert.
Em seguida, fase de afirmação:
Portanto, não é como um sobre o outro, mas é um pouco dos dois, você deve considerar as vantagens e desvantagens de ambos e tomar uma decisão.
Por exemplo, se você considerar a abordagem da escola de Londres, entenderá o risco de falsos positivos que podem ser criados devido à refatoração futura, mas esse risco afeta apenas um pequeno subconjunto de testes se sua base de código for modular e você não estiver fazendo muito de coisas em uma classe em si. E, se você considerar a abordagem da escola clássica, terá que entender o risco de cobertura redundante, mas se estiver executando seu teste em cada PR, não demorará muito para detectar o bug. Outro ponto que você pode considerar também é que, se o seu design interno for um produto de refatoração, você pode considerar o uso da abordagem clássica, porque muitas vezes refatorou essa classe, ou se o seu design interno é um produto de design inicial, considere o London abordagem escolar.
Então, o que você deve usar bem depende de sua situação (como diz todo engenheiro ). Agora que você sabe, pode fazer sua própria escolha.
Agradeço a Mihir, Nishant, Yogesh e prabhatexit0 por revisar meu rascunho para fornecer feedback para correções e sugestões.
Por favor, compartilhe seus comentários e sugestões nos comentários e me corrija se você não concordar comigo em algum ponto. Mostre seu apoio dando alguns , isso aumentará minha confiança para continuar e compartilhá-lo com outros engenheiros, certifique-se de se inscrever para ser notificado da próxima parte e siga para mais.
Sempre pronto para discutir qualquer coisa legal , sinta -se à vontade para se conectar .
LinkedIn : hellosagar
Twitter: hellosagarCode
Github: hellosagar





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)