Notas básicas de Python para el desarrollo de pruebas (16): módulos y paquetes

16.1 Concepto

  • Las funciones, clases, constantes se dividen en diferentes archivos .py, estos archivos son módulos
  • Coloque varios módulos en una carpeta, esta carpeta es un paquete y el paquete generalmente contiene un archivo __init__.pyque expresa la interfaz del módulo expuesta por el paquete. El contenido también puede estar vacío, pero no es obligatorio.

Proyectos organizados por módulos y paquetes, la estructura organizativa se ve más clara, como la estructura de un proyecto de prueba automatizado basado en Pytest:

├── Pipfile
├── Pipfile.lock
├── config
│   ├── prod
│   └── test
├── conftest.py
├── data
│   └── test_in_theaters.yaml
├── pytest.ini
├── readme.md
├── tests
│   ├── test_assertions.py
│   ├── test_in_theaters.py
│   ├── test_json_path.py
│   ├── test_json_schema.py
│   ├── test_marks.py
│   ├── test_smtpsimple.py
│   └── test_time.py
└── utils
    ├── __init__.py
    ├── assertions.py
    └── commlib.py

16.2 Importación de módulos

Puede importar un solo módulo o todos los módulos en un paquete. La declaración de importación se coloca en la parte superior del archivo py. Se recomienda comenzar a importar desde el directorio debajo de la ruta raíz del proyecto. Por ejemplo, en el directorio de proyectos de automatización anterior, para importar módulos en el paquete utils en otros paquetes, es mejor comenzar desde el directorio utils para encontrar los módulos para importar.

16.2.1 Importación de un solo módulo

El método para importar un solo módulo, la sintaxis es: import package.module_name. Es el nombre del paquete + punto + nombre del módulo. Por ejemplo, en el proyecto anterior, importe el módulo de aserciones de commlib.py, puede escribirlo así:

import utils.assertions

También puede usar el formulario desde, desde+nombre del paquete+importar+nombre del módulo:

from utils import assertions

Es más recomendable usar la última forma, porque la primera forma debe usar su nombre completo al usar la función en el módulo, por ejemplo:

utils.assertions.assertsion(1,1)

Será más problemático escribir, especialmente cuando la ruta del módulo es larga. Cuando use el paquete importado desde, cuando use las funciones en él, simplemente comience con el nombre del módulo directamente:

from utils import assertions
assertions.ENV
assertions.A
assertions.assertsion(1,1)

También hay un método de importación, que importa propiedades, métodos y clases en un módulo.El asterisco representa todas las propiedades, métodos y clases que se pueden importar. Pero no se recomienda hacerlo.

from utils.assertions import *

En este momento, si el atributo está definido en el módulo importado __all__, solo __all__se pueden importar los atributos, métodos y clases especificados; si el atributo no está definido , se importan todos los atributos, métodos y clases públicos__all__ del módulo . Las propiedades, métodos y clases que comienzan con un guión bajo son privados para el módulo, es mejor no usarlos fuera del módulo, aunque no hay ningún error al usarlos, el IDE de Pycharm se los recordará.

__all__Hay esta variable en el archivo allure.py del marco de informe de prueba allure . Verifique el código fuente y el __all__contenido es el siguiente:

__all__ = [
    'title',
    'description',
    'description_html',
    'label',
    'severity',
    'suite',
    'parent_suite',
    'sub_suite',
    'tag',
    'id',
    'epic',
    'feature',
    'story',
    'link',
    'issue',
    'testcase',
    'step',
    'dynamic',
    'severity_level',
    'attach',
    'attachment_type'
]

Desde el IDE de Pycharm, import alluredespués de la reunión, puede ver el contenido de la __all__variable anterior a través de los métodos que pueden usar los puntos:

[Falló la transferencia de la imagen del enlace externo, el sitio de origen puede tener un mecanismo anti-leech, se recomienda guardar la imagen y subirla directamente (img-L009TTyh-1596963840405) (fotos/ todo .png)]

Esto también refleja una idea de diseño. Cuando su propio Python desarrollado contiene muchos módulos, para facilitar el uso de todos, puede resumir las funciones de muchos módulos en un solo módulo, al igual que allure.py, en el módulo allure. Importe funciones de otros módulos. y establezca una __all__variable, para que los usuarios solo puedan importar el módulo allure.

La misma idea de diseño se refleja en el marco de pruebas unitarias de pytest. Cuando importamos el módulo pytest, podemos usar __all__las funciones enumeradas en las variables en el módulo pytest. Estas funciones son de varios módulos en el _pytestpaquete .

16.2.2 Importación masiva de módulos

La sintaxis para la importación masiva de módulos desde un paquete es: from package_name import *. En lugar de importar un solo módulo, la diferencia es que el nombre del módulo se reemplaza con un asterisco. No se recomienda la importación masiva.

Por ejemplo, importe todos los módulos en el paquete utils del proyecto de automatización, en forma de from+package name+import+*, por ejemplo:

from utils import *

Una variable nombrada se puede definir en los __init__.pyarchivos . __all__En esta variable, qué módulos se pueden importar cuando se importan a través del método anterior. Por ejemplo, en el __init__.pyarchivo , si la __all__variable se define con el siguiente valor:

__all__ = ['assertions']

Luego, cuando use el from utils import *módulo de importación o import utilsimporte el paquete directamente, solo se importará el módulo de aserciones.
inserte la descripción de la imagen aquí

Como se mencionó anteriormente, __all__las funciones enumeradas en las variables en el código del módulo pytest son todas de _pytestpaquetes internos. También hay una variable _pytesten el archivo de este paquete interno , pero solo hay una , por lo que el paquete rara vez se usa en el trabajo .__init__.py__all____version___pytest

inserte la descripción de la imagen aquí

Para importar un módulo en Pycharm, primero puede escribir el código comercial. Cuando se usa un módulo pero no se ha importado, puede presionar la tecla de método abreviado alt+enter para importarlo automáticamente en el nombre del módulo.

16.3 Solucionar el problema de que no se encuentra el módulo

Aunque importamos el módulo, a veces habrá un aviso de que no se puede encontrar el módulo, y se informará un aviso cuando no se pueda encontrar el módulo ImportError: No module named xxx. Al importar un paquete, Python sys.pathbusca en la lista de directorios, buscando módulos en el paquete en orden. Si no se encuentra, se informará un ImportError. La forma de ver sys.path:

>>> import sys
>>> print(sys.path)
['', '/Users/chunming.liu/.pyenv/versions/3.7.7/lib/python37.zip', '/Users/chunming.liu/.pyenv/versions/3.7.7/lib/python3.7', '/Users/chunming.liu/.pyenv/versions/3.7.7/lib/python3.7/lib-dynload', '/Users/chunming.liu/.pyenv/versions/3.7.7/lib/python3.7/site-packages']

Conociendo la ruta de búsqueda del módulo, entonces, tenemos dos formas de resolver el ImportError:

  • La primera forma, puede agregar la ruta del módulo a PYTHONPATH
export PYTHONPATH="/Users/chunming.liu/learm/api_pytest"

Porque PYTHONPATH se agrega automáticamente a sys.path. Por lo tanto, después de agregar la ruta del módulo a PYTHONPATH, también se agrega a sys.path.

  • El segundo método, en el código, puede agregar manualmente la ruta del módulo a sys.path, sys.path.append ("el directorio donde se encuentra el archivo del módulo")

Es por eso que se recomienda comenzar a importar módulos desde la ruta raíz del proyecto, de modo que solo necesite importar el directorio raíz del proyecto a PYTHONPATH.

16.4 Instalación del módulo

El software de administración de paquetes propio de Python generalmente se descarga e instala desde la fuente PyPI oficial de Python en https://pypi.python.org/simple. Nuestros usuarios domésticos serán muy lentos al acceder y descargar. Puede especificar manualmente la dirección espejo del paquete nacional para acelerar la instalación:

$ pip install flask -i https://pypi.douban.com/simple

Los espejos domésticos del paquete de Python incluyen principalmente:

  • Aliyun http://mirrors.aliyun.com/pypi/simple/
  • Universidad de Ciencia y Tecnología de China https://pypi.mirrors.ustc.edu.cn/simple/
  • Douban (douban) http://pypi.douban.com/simple/
  • Universidad de Tsinghua https://pypi.tuna.tsinghua.edu.cn/simple/
  • Universidad de Ciencia y Tecnología de China http://pypi.mirrors.ustc.edu.cn/simple/

El método anterior es modificar temporalmente la dirección del espejo del paquete de Python. Si desea modificar permanentemente la dirección del espejo de Python, puede crear un nuevo archivo ~/.pip/pip.conf y escribir el siguiente contenido:

$ cat ~/.pip/pip.conf
[global]
index-url = http://pypi.douban.com/simple/ 
trusted-host = pypi.douban.com

Si la empresa crea una duplicación interna, es mejor utilizar la duplicación interna de la empresa.

16.5 Nombres de módulos

Si hay un archivo py llamado común, el contenido es el siguiente:

# content of commmon.py
data = dict()  # 保存中间结果


def walk(n):
    if n == 1:
        return 1
    if n == 2:
        return 2
    if n > 100:
        raise RecursionError("recursion depth exceed 100")
    if n in data:  # 如果在中间结果中,则直接返回,不用进入递推公式再次计算
        print(n, data)
        return data[n]
    result = walk(n - 1) + walk(n - 2)
    data[n] = result
    return result


walk(6)

Al usar import para importar el módulo common.py, ejecutará automáticamente el código data=dict()y ( walk(6)) que se pueden ejecutar en el módulo.

# content of mywalk.py 
from test.test_suite1 import func

print(func.walk(2))

## 输出
3 {
    
    3: 3, 4: 5}
4 {
    
    3: 3, 4: 5, 5: 8}
13
2

13 en la salida anterior es la salida en el módulo common.py walk(6). Esto no es lo que queremos, lo que queremos es que el módulo common.py walk(6)se pueda ejecutar cuando se ejecuta solo. Cuando el módulo se importa a otros archivos py y se ejecuta, el código ejecutable dentro del módulo walk(6)no se ejecuta.

Para lograr este efecto, puede agregar una if __name__ == '__main__'declaración al módulo y poner el código ejecutable en el módulo, por ejemplo, walk(6)en él.

if __name__ == '__main__':
    print(walk(6))

Esto tendrá dos efectos:

  • __name__Como parámetro integrado mágico de Python, es esencialmente un atributo del objeto del módulo. Cuando el módulo se ejecuta solo, la __name__ == '__main__'condición se cumple y print(walk(6))se ejecutará.
  • Cuando se importa el módulo, la __name__ == '__main__'condición no se cumple y print(walk(6))no se ejecutará.

Por lo tanto, la práctica habitual es poner el código de prueba del módulo __name__ == '__main__'dentro de la declaración condicional, de modo que cuando el módulo se ejecuta solo, se ejecuta el código de prueba.

16.6 Herramientas de línea de comandos

A veces, ejecutaremos módulos en la línea de comandos, como ejecutar casos de prueba escritos por el marco pytest. Esto se puede hacer python -m pytest. Tenga cuidado de no agregar pytest después

.py sufijo, al ejecutar pruebas de esta manera, un beneficio es que el directorio actual se agrega automáticamente sys.patha . No es necesario agregar manualmente el directorio del proyecto de automatización a la variable PYTHONPATH para evitar la situación de que no se puede encontrar el módulo.

Además, en el trabajo de prueba, a veces se desarrollan muchos dispositivos de línea de comando para cooperar con el trabajo de prueba. Por lo tanto, escribir y ejecutar herramientas de línea de comandos sigue siendo muy común en el trabajo de prueba.

Para escribir herramientas de línea de comandos, la común es usar el módulo argparse integrado y la otra es usar la biblioteca fire de terceros.

16.6.1 El módulo argparse

El módulo argparse viene con Python y puede sys.argvanalizar y procesar opciones y argumentos de la línea de comandos de . También genera automáticamente ayuda y manuales, y emite mensajes de error cuando el usuario pasa parámetros no válidos al programa.

Hay cuatro pasos para utilizar el módulo argparse para crear una herramienta de línea de comandos. Consulte el siguiente código:

# content of argparse_demo.py
import argparse


# 1. 设置解析器
parser = argparse.ArgumentParser("命令行工具描述。")

# 2. 定义参数
# 添加位置参数
parser.add_argument('integers', #位置参数名
                    metavar='N',  # -h查看帮助信息时,显示为 N
                    type=int,   # 输入的参数必须是int
                    nargs='+',  # +表示至少要有一个参数,?表示0或1个参数,*表示0或多个参数,也可以是数值,表示参数个数。
                    help='an integer for the accumulator')
# 添加选项参数
parser.add_argument('--sum', #--开头的是个命令行选项
                    dest='accumulate',  # 这个选项解析后对应的属性
                    action='store_const',  # action表示对属性做的操作,这里的store_const表示将const的值存到dest属性中。
                    const=sum,  # 指定了--sum 参数时,accumulate将是 sum() 函数
                    default=max,  # 若不提供 --sum,accumulate默认值为 max 函数
                    help='sum the integers (default: find the max)')

# 3. 解析参数
args = parser.parse_args(['--sum', '1', '2', '3'])
print(args) # Namespace(accumulate=<built-in function sum>, integers=[1, 2, 3])
# 4. 业务逻辑
if args.accumulate:
    print(args.accumulate(args.integers))

Primero configure un analizador y agregue una descripción de la herramienta de línea de comandos.

El código anterior agrega un parámetro posicional, un parámetro de opción. El parámetro posicional recibe datos de tipo int y puede recibir uno o más parámetros, que se almacenan en el atributo enteros. El parámetro de opción se almacena en la propiedad de acumulación. Cuando la opción de línea de comando --sum está presente, el valor en esta propiedad es la función de suma. Cuando no hay una opción de línea de comando, el valor predeterminado en esta propiedad es la función max.

Todos los argumentos de la línea de comandos se analizan y almacenan en la variable args. El contenido de esta variable es el siguiente:

Namespace(accumulate=<built-in function sum>, integers=[1, 2, 3])

El módulo argparse agrega automáticamente la opción -h para mostrar el manual de la herramienta de línea de comandos:

$ python argparse_demo.py -h
usage: 命令行工具描述。 [-h] [--sum] N [N ...]

positional arguments:
  N           an integer for the accumulator

optional arguments:
  -h, --help  show this help message and exit
  --sum       sum the integers (default: find the max)

Por lo general, no pasamos argumentos a la función parser.parse_args(), la forma normal de usar esta herramienta de línea de comandos es:

$ python argparse_demo.py 1 2 3 
3
$ python argparse_demo.py 1 2 3 --sum
6

Si no desea escribir el comando python cada vez, sino ejecutar directamente el archivo py, puede escribir la primera línea del archivo Python con qué software se ejecutará el archivo, porque nuestro archivo py definitivamente quiere ser ejecutado en Python, para que pueda ejecutarse en el archivo py de Python, escriba el siguiente código en la primera línea:

#!/usr/bin/env python3

Luego agregue permisos ejecutables al archivo py:

chmod +x argparse_demo.py

Ahora puede ejecutar el módulo py en la línea de comando de la siguiente manera:

$ argparse_demo.py 1 2 3 --sum

Para obtener más información sobre el uso de este módulo, puede consultar la documentación oficial.

16.6.2 El módulo de incendios

Fire es una herramienta para hacer que el software de línea de comandos sea de código abierto de Google. En comparación con argparse, el módulo de disparo es relativamente cómodo de usar. Lo mismo es para lograr la función de acumulación anterior, usando el módulo de fuego solo se necesita definir una función:

#!/usr/bin/env python3
# content of fire_demo.py
import fire

def accumulate(*numbers):
    return sum(numbers)


if __name__ == '__main__':
    fire.Fire()

En el caso de múltiples funciones, puede poner múltiples funciones en una clase:

#!/usr/bin/env python3
# content of tcp_demo.py
import socket
from concurrent.futures.thread import ThreadPoolExecutor

import fire


class TCPDemo:

    def server(self, host='0.0.0.0', port=5555):
        pool = ThreadPoolExecutor(8)
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind((host, port))
            s.listen(5)
            print("服务器监听在{}:{},等待客户端连接...".format(host, port))
            while True:
                client_sock, client_address = s.accept()
                pool.submit(self.handler, client_sock, client_address)

    def client(self, server_ip, server_port):
        tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            tcp_client.connect((server_ip, server_port))
        except socket.error:
            print('fail to setup socket connection')
            tcp_client.close()
        while True:
            data = input("> ")
            if not data:
                break
            tcp_client.send(data.encode("utf-8"))
            msg = tcp_client.recv(1024)
            print("服务端说:" + msg.decode('utf-8'))

    @staticmethod
    def handler(client_sock, client_address):
        """
        处理收到的data。原样返回给客户端
        """
        print('Got connection from {}:{}'.format(client_address[0], client_address[1]))
        with client_sock:
            while True:
                data = client_sock.recv(1024).decode("utf-8")
                # 收到的数据不为空
                if data:
                    response = data
                    print("客户端说:" + response)
                    client_sock.send(response.encode("utf-8"))


if __name__ == '__main__':
    fire.Fire(TCPDemo)

El método de uso es similar a argparse:

$ python tcp_demo.py server  10.110.215.104  23900   # 如果不填写后面的参数,则默认是本地 5555端口
$ ./tcp_demo.py client 10.110.215.104  23900 

En el trabajo real, hay muchos casos de desarrollo de herramientas de línea de comandos para ayudarnos en las pruebas. Por ejemplo, para probar un servicio que produce datos para kafka, es necesario verificar que haya datos en el tema de kafka, y luego se puede desarrollar una herramienta de línea de comandos para consumir el tema, por ejemplo, la dirección kafka y el nombre del tema se proporcionan de la siguiente manera: cuando hay un mensaje en el tema, se puede imprimir en la consola para su verificación.

$ python kafka_client.py bootstrap_server  topic_test-vehicle_data 

Para implementar comandos anidados, puede organizar varias clases, como las siguientes:

#!/usr/bin/env python3
# content of fire_demo.py
import fire


class IngestionStage(object):

    def run(self):
        return 'Ingesting! Nom nom nom...'


class DigestionStage(object):

    def run(self, volume=1):
        return ' '.join(['Burp!'] * volume)

    def status(self):
        return 'Satiated.'


class Pipeline(object):

    def __init__(self):
        self.ingestion = IngestionStage()
        self.digestion = DigestionStage()

    def run(self):
        self.ingestion.run()
        self.digestion.run()


if __name__ == '__main__':
    fire.Fire(Pipeline)

Por lo tanto, todo el programa de línea de comandos admite los siguientes comandos:

  • $ python fire_demo.py run
  • $ python fire_demo.py ingestion run
  • $ python fire_demo.py digestion run
  • $ python fire_demo.py digestion status

Para ver la información de ayuda, debe especificar la instancia de la clase: $ python fire_demo.py ingestion --help, se generará la siguiente información de ayuda:

NAME
    fire_demo.py ingestion

SYNOPSIS
    fire_demo.py ingestion COMMAND

COMMANDS
    COMMAND is one of the following:

     run

Para obtener un método de uso del módulo de incendios más completo, consulte la documentación oficial .

Supongo que te gusta

Origin blog.csdn.net/liuchunming033/article/details/107896540
Recomendado
Clasificación