A análise do princípio do DDT da Unittest

introdução

  O artigo anterior apresentou como usar o ddt para implementar o teste automatizado baseado em dados na estrutura Unittest do Python.


Depois de entender o uso do ddt, você já teve as seguintes perguntas:

  • Como o ddt converte seus dados de teste em seus casos de teste?

  • Como o ddt descompacta quando seu conjunto de dados tem vários parâmetros?

  • Quando você tem vários conjuntos de dados, como os casos de teste de divisão ddt são nomeados?

 

Assunto: O que compartilhei hoje é explorar os segredos da realização do ddt de orientado a dados.

  Ao ler o código-fonte do ddt, não é difícil descobrir que o núcleo do ddt é o decorador @ddt (cls), e o código do núcleo deste decorador é a função da classe de invólucro . Abaixo, colarei diretamente o código- fonte do invólucro , vamos ver juntos Olha:

def wrapper(cls):
    # 先遍历被装饰类的name, 和func
    # 对于func,先看被装饰的是DATA_ATTR还是FILE_ATTR
    for name, func in list(cls.__dict__.items()):
        # 如果被装饰的是DATA_ATTR
        if hasattr(func, DATA_ATTR):
            #获取@data提供数据的index和内容并且遍历它们
            for i, v in enumerate(getattr(func, DATA_ATTR)):
                # 重新生成新的测试函数名,这个函数名会展示在测试报告中
                test_name = mk_test_name(
                    name,
                    getattr(v, "__name__", v),
                    i,
                    fmt_test_name
                )
                test_data_docstring = _get_test_data_docstring(func, v)
                # 如果类函数被@unpack装饰
                if hasattr(func, UNPACK_ATTR):
                    # 如果提供的数据是tuple或者list
                    if isinstance(v, tuple) or isinstance(v, list):
                        # 则添加一个case到测试类中
                        # list或tuple传不定数目的值, 用*v即可。
                        add_test(
                            cls,
                            test_name,
                            test_data_docstring,
                            func,
                            *v
                        )
                    else:
                        # unpack dictionary
                        # 添加一个case到测试类中
                        # dict中传不定数目的值,用**v
                        add_test(
                            cls,
                            test_name,
                            test_data_docstring,
                            func,
                            **v
                        )
                else:
                    # 如不需要unpack,则直接添加一个case到测试类
                    add_test(cls, test_name, test_data_docstring, func, v)
            # 删除原来的测试类
            delattr(cls, name)
        # 如果被装饰的是file_data
        elif hasattr(func, FILE_ATTR):
            # 获取file的名称
            file_attr = getattr(func, FILE_ATTR)
            # 根据process_file_data解析这个文件
            # 在解析的最后,会调用mk_test_name生成多个测试用例
            process_file_data(cls, name, func, file_attr)
            # 测试用例生成后,会删除原来的测试用例
            delattr(cls, name)
    return cls

  

    Vamos analisar este pedaço de código. Para cada classe de teste decorada por @ddt, ddt primeiro percorre os próprios atributos da classe de teste para obter os métodos de teste desta classe de teste. Esta parte depende principalmente desta declaração:

# wrapper源码第4行
for name, func in list(cls.__dict__.items()):

 

    Em seguida, o ddt julga se há decoradores @data ou @file_data em todas as funções (ou seja, funções de classe), principalmente com base nessas duas declarações:

# 被@data装饰, wrapper源码第6行
if hasattr(func, DATA_ATTR):
# 被file_data 装饰,wrapper源码第47行
elif hasattr(func, FILE_ATTR):

 

    Em seguida, o programa irá inserir dois ramos: decorado por @data, ou seja, os dados são fornecidos diretamente por ddt; decorados por @file_data, ou seja, os dados são fornecidos por arquivos externos.

 

1. Decorado por @data, ou seja, os
  dados são fornecidos diretamente por ddt. Se os dados forem fornecidos diretamente por meio de @data, um novo nome de caso de teste é gerado para cada conjunto de dados.

# 在本例中, i, v的第一次循环,值为 
# i:0 v:['Testing', 'Testing']
# wrapper源码第8行
for i, v in enumerate(getattr(func, DATA_ATTR)):
    test_name = mk_test_name(
        name,
        getattr(v, "__name__", v),
        i,
        fmt_test_name
    )

  O test_name é gerado usando a função mk_test_name.

  Nota: ddt realiza o caso de teste que transfere seus dados de teste para você neste momento. Na verdade, isso não é conseguido passando, mas dividindo os dados de teste e gerando novos casos de teste.

 

      Na função mk_test_name, ddt divide a função de teste original em diferentes funções de teste por meio de regras específicas.

test_name = mk_test_name(name,getattr(v, "__name__", v),i,fmt_test_name)

  Entre os parâmetros de mk_test_name:

  • nome é o nome da função de teste original

  • v é o nosso conjunto de dados de teste

  • i é o índice deste conjunto de dados

     

  fmt_test_name especifica o formato do nome da nova função de teste.Este formato é baseado no formato do índice do nome da função de teste original primeiro teste data_segundo dados de teste.

 

  Por exemplo, nossos dados de teste ['Teste', 'Teste'] serão convertidos em test_baidu_search_1 _ ['Teste', 'Teste'] ', mas porque os símbolos' ['e' 'e', ​​'são caracteres ilegais, Portanto , ele será substituído por'_ ', então o novo nome do caso de teste final é test_baidu_search_1___Testing____Testing__. A lógica deste bloco está nas duas últimas linhas da função mk_test_name:

# ddt内容函数mk_test_name,test_name处理逻辑如下
test_name = "{0}_{1}_{2}".format(name, index, value)
return re.sub(r'\W|^(?=\d)', '_', test_name)

 

      Imediatamente a seguir, o ddt procura a sua função de teste para ver se está decorada com @unpack. Nesse caso, significa que nossa função de teste tem vários parâmetros. Neste momento, precisamos descompactar nossos dados de teste para que cada parâmetro de nossa função de teste possa receber o valor de entrada.

  Desta forma, o ddt pega o test_name gerado na etapa anterior e o valor de descompactar agora (os dados são uma lista, tupla ou dicionário, que determina se descompactar usa * v ou ** v), por meio de add_test para gerar um novo caso de teste, e registre-o em nosso teste Abaixo da classe, todas essas ações são feitas no código a seguir.

# wrapper源码里的18行到43行
if hasattr(func, UNPACK_ATTR):
    if isinstance(v, tuple) or isinstance(v, list):
        add_test(
            cls,
            test_name,
            test_data_docstring,
            func,
            *v
        )
    else:
        # unpack dictionary
        add_test(
            cls,
            test_name,
            test_data_docstring,
            func,
            **v
        )
else:
    add_test(cls, test_name, test_data_docstring, func, v)

 

Nota:
  Neste momento, existem mais funções de teste na classe de teste. Quantas mais funções de teste dependem do número de grupos de dados de teste fornecidos por ddt. Se houver vários grupos, vários casos de teste serão gerados e registrados no original classe de teste. ;

  Desempacotar é, na verdade, passar vários dados de teste de um caso de teste para a função de teste recém-gerada, e esses dados de teste correspondem aos parâmetros da função de teste um a um.

  Finalmente, o ddt excluirá o método de teste original original (porque a função de teste original se tornou uma nova função de teste com base em cada conjunto de dados).

# wrapper源码45行
delattr(cls, name)

  Desta forma, o ddt gera múltiplos conjuntos de casos de teste através da função mk_test_name de acordo com o número de grupos de dados de teste, e os registra no TestSuite de unittest através da função add_test.

 

2. Decorado por @file_data, ou seja, os dados são fornecidos por um arquivo externo.
  Se a função de teste for decorada por @file_data, o ddt primeiro obterá o nome do arquivo de dados em file_data, e então seguirá para a próxima etapa através da função process_file_data.

# wrapper源码的第49到52行
file_attr = getattr(func, FILE_ATTR)
process_file_data(cls, name, func, file_attr)

  Parece que existem apenas duas linhas curtas. Na verdade, o ddt faz muitas operações dentro da função process_file_data.

  Primeiro, ddt obterá primeiro o endereço absoluto do arquivo de dados que fornecemos e julgará se é um arquivo yaml ou json pelo nome do sufixo e, em seguida, chamará o método de carregamento de yaml ou json para obter os dados fornecidos no Arquivo.

  Depois de obter os dados, finalmente, por meio da função mk_test_name e da função add_test, vários casos de teste são gerados e registrados no TestSuite de unittest.

A última coisa é excluir a função de teste original:

# wrapper源码54行
delattr(cls, name)

Esta é toda a lógica de realização do ddt.

 

Resumindo

  O código-fonte do DDT é muito clássico, não existem muitas linhas de código, vale a pena uma leitura aprofundada. Pondere e estude cuidadosamente o código-fonte do DDT, que o ajudará a melhorar sua tecnologia de desenvolvimento de teste. É recomendável usar um método de depuração de etapa única, combinado com o conteúdo compartilhado hoje, e ler o código DDT enquanto executa o código de teste, o que o ajudará a aprofundar sua compreensão do princípio DDT.

Bem-vindo a prestar atenção na conta pública [The Way of Infinite Testing], responder a [receber recursos],
recursos de aprendizagem de programação Python
, automação de IU do APP da estrutura Python + Appium, Automação
da IU da Web da estrutura Python + Selenium,
API da estrutura Python + Unittest automação,

Recursos e códigos são enviados gratuitamente ~
Há um código QR da conta oficial no final do artigo, você pode digitalizá-lo no WeChat e segui-lo.

Observações: Minha conta pública pessoal foi oficialmente aberta, dedicada ao compartilhamento de tecnologia de teste, incluindo: teste de big data, teste funcional, desenvolvimento de teste, automação de interface de API, operação e manutenção de teste, teste de automação de IU, etc., público de pesquisa do WeChat conta: "Wuliang The Way of Testing" ou digitalize o código QR abaixo:

 Dê atenção e vamos crescer juntos!

Acho que você gosta

Origin blog.csdn.net/weixin_41754309/article/details/113093724
Recomendado
Clasificación