Desfazer alterações no metaclass em testes de unidade

Posted . Visible to the public.

ASSUNTO

Continuando e completando o assunto do card de 11 meses atrás Desfazer alterações no metaclass em testes de unidade (leia-o primeiro):

CONCLUSÕES

  • Teste que herda da classe GrailsUnitTestCase na implementação do Grails 2.2.4 não sofre com o problema de metaclass alterado em testes de unidade desde que seja utilizada super.tearDown() em sua implementação. Lembrando que caso você não sobrescreva o método void tearDown() este comportamento herdado já é executado por padrão. Perceba que o uso de registerMetaClass(Classe) (implementação exclusiva de GrailsUnitTestCase) de qualquer forma depende do super.tearDown() e não faz nenhum milagre sozinho, já o super.tearDown() sozinho faz.

  • Teste que herda da classe GMockTestCase na implementação do abandonado Gmock 0.8.3 já passa a sofrer com o problema de metaclass alterado em testes de unidade. A solução é a apresentada no card anterior, com o uso da seguinte chamada no final do próprio método de teste ou no tearDown da classe:

      GroovySystem.metaClassRegistry.removeMetaClass(Classe)
    
  • Teste que herda da classe GroovyTestCase (que por sinal é a superclasse de GMockTestCase) também sofre com o problema de metaclass alterado em testes de unidade. A solução também é o uso do removeMetaClass no final do próprio método de teste ou no tearDown da classe.

  • Teste que herda da classe Specification/UnitSpec/IntegrationSpec na implementação do spock 0.7 também sofre com o problema de metaclass alterado em testes de unidade. A solução também é o uso do removeMetaClass, que aqui pode ser utilizada no próprio teste na seção cleanup, ou em um dos métodos cleanupSpec ou cleanup da classe, de acordo com o escopo necessário.

  • Teste que não herda nenhuma classe e usa a estrutura de anotações @TestFor/@Mock/@WithGMock, segundo o analisado, não sofre com o problema de metaclass alterado em testes de unidade de forma alguma. Aparentemente cada teste, seja na mesma classe ou não, é completamente independente quanto aos metaclass alterados. Na "desconfiança" seria indicado o uso da chamada removeMetaClass em (a) um método qualquer com a anotação @After ou (b) no método tearDown para evitar problemas futuros.

COMPROVAÇÕES

Crie as seguintes classes e mande executar os testes de todas elas de uma vez só.

GrailsUnitTestCase e GMockTestCase e GroovyTestCase

class ArquivoCausaBug1Tests extends GrailsUnitTestCase {

    void tearDown() {
        super.tearDown() //Necessário para impedir problemas
        // processamento adicional qualquer ...
    }

    void testA() {
        boolean replaceCalled = false
        String.metaClass.replace = { CharSequence a, CharSequence b ->
            replaceCalled = true
            return 'metaclass usado'
        }
        assertEquals 'metaclass usado', 'aba'.replace('b', 'a')
        assertEquals true, replaceCalled
    }

    void testB() {
        assertEquals 'aaa', 'aba'.replace('b', 'a')
    }

}

class ArquivoCausaBug2Tests extends GMockTestCase {

    void tearDown() {
        super.tearDown()
        GroovySystem.metaClassRegistry.removeMetaClass(String) //Necessário para impedir problemas
    }

    void testA() {
        boolean replaceCalled = false
        String.metaClass.replace = { CharSequence a, CharSequence b ->
            replaceCalled = true
            return 'metaclass usado'
        }
        assertEquals 'metaclass usado', 'aba'.replace('b', 'a')
        assertEquals true, replaceCalled
    }

    void testB() {
        assertEquals 'aaa', 'aba'.replace('b', 'a')
    }

}

class ArquivoCausaBug3Tests extends GroovyTestCase {

    void tearDown() {
        super.tearDown()
        GroovySystem.metaClassRegistry.removeMetaClass(String) //Necessário para impedir problemas
    }

    void testA() {
        boolean replaceCalled = false
        String.metaClass.replace = { CharSequence a, CharSequence b ->
            replaceCalled = true
            return 'metaclass usado'
        }
        assertEquals 'metaclass usado', 'aba'.replace('b', 'a')
        assertEquals true, replaceCalled
    }

    void testB() {
        assertEquals 'aaa', 'aba'.replace('b', 'a')
    }

}

class ArquivoIndependenteSofreBugTests extends GrailsUnitTestCase {

    void testB() {
        assertEquals 'aaa', 'aba'.replace('b', 'a')
    }

}

Specification / UnitSpec / IntegrationSpec

class ArquivoCausaBugSpec extends UnitSpec {

    void 'test A'() {
        setup:
        boolean replaceCalled = false
        String.metaClass.replace = { CharSequence a, CharSequence b ->
            replaceCalled = true
            return 'metaclass usado'
        }

        expect:
        'metaclass usado' == 'aba'.replace('b', 'a')
        replaceCalled

        cleanup:
        GroovySystem.metaClassRegistry.removeMetaClass(String) //Necessário para impedir problemas
    }

    void 'test B'() {
        expect:
        'aaa' == 'aba'.replace('b', 'a')
    }

}

class ArquivoIndependenteSofreBugSpec extends UnitSpec {

    void 'test B'() {
        expect:
        'aaa' == 'aba'.replace('b', 'a')
    }

}

Anotações

@TestFor(DomainClass)
@WithGMock
class ArquivoCausaBug3Tests {

    void testA() {
        assertEquals 'aaa', 'aba'.replace('b', 'a')
    }

    void testB() {
        boolean replaceCalled = false
        String.metaClass.replace = { CharSequence a, CharSequence b ->
            replaceCalled = true
            return 'metaclass usado'
        }
        assertEquals 'metaclass usado', 'aba'.replace('b', 'a')
        assertEquals true, replaceCalled
    }

    void testC() {
        assertEquals 'aaa', 'aba'.replace('b', 'a')
    }

}

FONTES ORIGINAIS DE ESTUDO (além dos arquivos próprios criados)

Bruno Vieira
Last edit
Posted by Bruno Vieira to ZeroGlosa (2014-03-11 17:14)