PieceofScript es un lenguaje simple para escribir scripts para pruebas automáticas de la API HTTP JSON.
PieceofScript le permite:
- Describir los métodos API en formato YAML, con el nombre del método en un lenguaje casi natural, lo cual es conveniente para leer las pruebas.
- lo suficientemente flexible como para describir modelos en formato YAML y generar datos aleatorios a partir de ellos
- escriba scripts de llamadas API complejos en un lenguaje fácil de leer con sintaxis simple
- obtener resultados de pruebas en formatos JUnit y HTML
Escribí esta "bicicleta" porque la interfaz SoapUI me hizo caer. Quería describir simple y claramente las pruebas en un editor de texto sin una GUI especial. Además, git no digiere el enorme archivo xml que emite SoapUI, por lo que es difícil poner pruebas para una tarea específica en la misma rama donde se realizó la tarea. La interfaz de Postman es mucho más agradable, pero cuando se desarrolla, lleva mucho tiempo componer / modificar las solicitudes allí y repetirlas en la secuencia correcta. Quería automatizarlo. También estudié otras herramientas de prueba, cada una tenía un "
defecto fatal ", por lo que en el ataque del
síndrome NIH abrí un IDE.
Esto es lo que salió de eso.

El intérprete está escrito en PHP y es un archivo phar; requiere la versión PHP 7.2, aunque también puede funcionar en 7.1. Código fuente y documentación
https://github.com/maximw/PieceofScript . Documentación en desarrollo. Esto resultó ser la parte más difícil y tediosa.
Proyecto de prueba, su estructura y lanzamiento.Script de pruebaMétodos de prueba de APILlamada al método APIGeneración de modelos y datos de prueba.Funciones incorporadasCasos de pruebaVariables y alcancesTipos y operacionesGuardar datos entre ejecucionesSalida a stdout e informesEjemplos : suficientes palabras, ¡muestra el código!
Comentarios y planes para el futuro, si los hayProyecto de prueba, su estructura y lanzamiento.
El proyecto es un directorio con un conjunto de archivos de script, archivos de descripción de métodos API y generadores de datos de prueba.
En la versión mínima, el proyecto se ve así:
./tests endpoints.yaml - API generators.yaml - start.pos -
El archivo de inicio es el script desde el cual comienza el proceso de prueba. Se establece al inicio:
pos.phar run ./start.pos --junit=junit_report.xml -vvv --config=config.yaml
Todas las rutas relativas se leen desde el directorio de trabajo que contiene el archivo de inicio.
El archivo de configuración se puede especificar en la línea de comando con la opción
--config o poner
config.yaml en el directorio de trabajo. La configuración es opcional, debe subir allí según sea necesario.
Más sobre la configuración .
Script de prueba
Por mi parte, decidí escribir scripts en archivos con la extensión .pos, para que pueda hacer configuraciones de resaltado de código en el IDE con una extensión de extensión. Pero el intérprete es completamente indiferente a la extensión.
Aquí hay un ejemplo de script simple para un foro imaginario donde se realiza la prueba de crear y leer una publicación de diferentes usuarios.
require "./globals.pos"
Sí, no se ve muy bien sin retroiluminación.
Cada línea del script comienza con un operador o es una llamada a un método API. Si de repente el nombre del método API comienza con una palabra que coincide con uno de los operadores, puede usar el símbolo "
> ":
>Include $user to group $userGroup
Los operadores no distinguen entre mayúsculas y minúsculas. afirmar, ASSERT o aSsErT (pero ¿por qué escribir así?) funcionará.
Cada declaración o llamada al método API debe estar en una línea separada. Pero el ajuste de línea también es posible si el último carácter de la cadena es
\ (hola, Python).
Detalles poco interesantes sobre saltos de línea y sangríasSi se utiliza el ajuste de línea en un comentario, la siguiente línea también se considerará parte del comentario. Cuando se
ajustan líneas dentro de bloques (caso de
prueba ,
si ,
mientras ,
foreach ), es importante
sangrar para que la siguiente línea caiga en el mismo bloque.
var $n = 20 var $i = 2 var $fib1 = 1; \ $fib2 = 1 while $i <= $n var $fib_sum = \ $fib2 + $fib1 print toString($i) + " :" + \ toString($fib_sum) var $fib1 = $fib2 var $fib2 = $fib_sum var $i = $i + 1
Al ejecutar sentencias de bloque (caso de
prueba ,
si ,
while ,
foreach ), un bloque está determinado por la sangría de sus líneas. La sangría se cuenta como el número de espacios en blanco al comienzo de una línea. Tanto el espacio como la pestaña cuentan como un carácter, pero las pestañas generalmente se muestran en los editores como espacios múltiples. Por lo tanto, para evitar confusiones, es mejor usar pestañas o espacios, pero no todos juntos.
Lista completa de operadores
require fileName : adjunte el archivo al lugar donde se llama al operador. El archivo adjunto comenzará inmediatamente desde la primera línea. Al finalizar, el intérprete vuelve a la siguiente línea del archivo fuente. Si el archivo solicitado no es legible, se generará un error. La ruta relativa se calcula a partir del directorio de trabajo.
include fileMask : similar a require, pero si el archivo solicitado no es legible, no habrá ningún error. Esto es conveniente, por ejemplo, para crear configuraciones para diferentes entornos de prueba. Además, include puede conectar todos los archivos por máscara a la vez. Entonces, por ejemplo, puede descargar directorios completos de archivos que contienen casos de prueba. Pero al mismo tiempo, no se garantiza ningún orden de descarga de archivos.
var $ variable1 = expresión1 ; $ variable2 = expresión2 ; ...; $ variableN = expresiónN - asigna valores a las variables. Si la variable aún no existe, se creará en el contexto actual.
let $ variable1 = expresión1 ; $ variable2 = expresión2 ; ...; $ variableN = expresiónN - asigna valores a las variables. Si la variable no está en el contexto actual, se intentará crear o modificar la variable en el contexto global.
const $ const1 = expresión1 ; $ const2 = expresión2 ; ...; $ constN = expresiónN - establece el valor de las constantes en el contexto actual. La diferencia entre constantes y variables es solo que no se pueden cambiar; cuando intenta asignar un valor a una constante, se emitirá una advertencia después de la declaración. Si ya hay una variable con el mismo nombre, se generará un error al intentar declararla como una constante. De lo contrario, todo lo que es cierto para las variables también lo es para las constantes.
importar $ variable1 ; $ variable2 ; ...; $ variableN : copia variables del contexto global al actual. Puede ser útil si necesita operar sobre el valor de una variable global, pero no cambiarlo.
testcase testCaseName : anuncia un caso de prueba, que luego se puede llamar como una unidad con la instrucción de
ejecución .
Lea más sobre casos de prueba más adelante en el artículo .
afirmar expresión : verifique que la
expresión sea
verdadera , de lo contrario imprima un informe sobre la prueba fallida.
La expresión must es la misma que
afirmar , solo si la prueba falla, el caso de prueba actual se detendrá. Y fuera del contexto del caso de prueba, el script se terminará por completo. Se puede utilizar si se detecta un error con el que las comprobaciones adicionales no tienen sentido.
ejecutar testCaseName : ejecuta el caso de prueba especificado para su ejecución.
ejecutar sin especificar el nombre del caso de prueba iniciará todos los casos de prueba declarados que no requieren argumentos en el orden de su declaración.
expresión while : un bucle, mientras que la
expresión es verdadera, ejecuta sentencias con líneas sangradas más que
while .
foreach $ array ; $ element : recorre la matriz, el cuerpo del bucle se ejecuta para cada elemento siguiente de la matriz. También es posible obtener la clave
foreach $ array ; $ clave ; $ elemento . Las variables
$ key y
$ element se crean / sobrescriben en el contexto actual.
si expresión - si la
expresión es verdadera, ejecuta declaraciones con líneas sangradas más que
siexpresión de impresión1; expresión2 ; ... expresiónN - imprime el valor de
expresiónM en stdout. Se puede usar para depurar, solo funciona con el nivel de "hablador"
--verbosity = 1 o
-v y más.
expresión dormida - pausa para un número dado, opcionalmente un número entero, segundos. A veces necesitas darle un descanso a la API probada.
Pausar expresión : no en modo interactivo (opción de línea de comando
-n ) es similar a la
suspensión .
La expresión es opcional, en este caso no habrá pausa. Y en el modo interactivo, haga una pausa antes de presionar Enter.
cancelar - finalizar la prueba. El intérprete termina el trabajo, crea informes.
Métodos de prueba de API
Esto es realmente lo que necesita probar: llame con ciertos parámetros y verifique si la respuesta cumple con las expectativas.
Los métodos API se describen en formato YAML. De forma predeterminada, las descripciones deben estar en el archivo
endpoints.yaml del directorio actual y / o en los archivos
* .yaml en su subdirectorio
./endpoints . Antes de la prueba, el intérprete intentará leer todos estos archivos a la vez.
Estructura de
endpoints.yaml de ejemplo:
Auth $user: method: "POST" url: $domain + "/login" headers: Content-Type: "application/json" format: "json" data: login: $user.login password: $user.password after: - assert $response.code == 200 - let $user.auth_token = $response.body.auth_token Create post $post by $user: method: "POST" url: $domain + "/posts" format: "json" data: $post headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert $response.code == 201 Read post $postId by $user: method: "GET" url: $domain + "/posts/" + $postId headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert ($response.code == 200) || ($response.code == 404) Create comment $comment on $post by $user: method: "POST" url: $domain + "/comments/create/" + $post.id format: "json" data: $comment headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert $response.code == 201
El nombre del método API (el nivel superior de la estructura YAML) por el que se puede llamar es una cadena en un formato casi arbitrario.
Los argumentos se pueden especificar en cualquier parte del nombre. Deben estar separados por espacios del resto de las palabras. Por ejemplo,
$ comment ,
$ post y
$ user en el último método.
Además, en cualquier parte del nombre puede especificar valores de método opcionales entre llaves dobles.
Get comments of $post {{$page=1; $perPage=$defaultGlobalPageSize}}: method: "GET" url: $domain + "/comments/" + $post.id query: page: $page per_page: $perPage headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert $response.code == 200
En las expresiones que especifican valores opcionales, las variables de contexto global están disponibles.
Los valores opcionales pueden ser útiles para que no los especifique cada vez que llame al método API. Si el tamaño de la página necesita cambiarse en un solo lugar, ¿por qué indicarlo en todos los demás lugares? Ejemplos de llamadas a este método:
Get comments of $newPost // $page $perPage Get comments of $newPost {{$page=$currentPage+1}} Get comments of {$newPost} {{$perPage=10;$page=100}}
El resto de las variables utilizadas (
$ dominio en el ejemplo anterior) se tomarán del contexto global. Te contaré más
sobre los contextos más adelante.
Me parece conveniente dar a los métodos API nombres legibles por humanos en un lenguaje natural, entonces el script de prueba es más fácil de leer. Los nombres no distinguen entre mayúsculas y minúsculas, es decir, el método
Auth $ User se puede llamar como
auth $ User y como
AUTH $ User . Sin embargo, los nombres de las variables distinguen entre mayúsculas y minúsculas, más
sobre las variables a continuación.
Nota importante. El formato YAML le permite no encerrar cadenas entre comillas. Pero para el intérprete, una cadena sin comillas es una expresión que necesita ser evaluada. Por ejemplo, declarar una
url: http://example.com/login
campo dará como resultado un error de sintaxis durante la ejecución. Por lo tanto, será correcto:
url: "http://example.com/login"
o
url: "http://"+$domain+"/login"
Campos de descripción del método API
Método : método HTTP requerido
url : la URL real, requerida
encabezados : lista de encabezados HTTP, opcional
cookies - lista de cookies opcional
auth : datos para la autenticación HTTP, opcional
auth: login: $login password: $password type: "basic"
consulta : una lista de parámetros de URL, opcional
formato - uno de los valores:
- ninguno : la solicitud no tiene cuerpo
- json - enviar a JSON
- raw : envía la cadena "tal cual"
- formulario - en formato application / x-www-form-urlencoded
- multipart - en formato multipart / form-data
Opcional,
ninguno predeterminado
datos - cuerpo de la solicitud, se enviará en el formato especificado en
formato , opcional
Los archivos especificados en los campos de
archivo deben ser legibles. Si se especifica una URL,
allow_url_fopen debe estar habilitado en php.ini
before : sentencias que se ejecutarán antes de la solicitud HTTP, opcional
after : sentencias que se ejecutarán después de la solicitud HTTP, opcional
La idea de los bloques
antes y
después para realizar comprobaciones o procesar cualquier dato que se necesita cada vez antes o después de ejecutar la solicitud HTTP no está determinada tanto por las necesidades de prueba como por la lógica empresarial. Por ejemplo, copiar el token de autorización emitido en el campo de la estructura $ user para llamar a todos los métodos API posteriores en nombre de este usuario. O para verificar el estado HTTP de la respuesta, a fin de no verificarlo cada vez después de una llamada en el script.
Llamada al método API
Para llamar al método API en el script, debe especificar su nombre y parámetros, si es necesario. Aquí hay un ejemplo de cómo llamar al último método API de la descripción anterior:
Create comment $comments.1 on {$newPost} by {$postAuthor}
Si el parámetro está encerrado entre llaves, se pasará por valor, de esta manera puede pasar cualquier expresión. Si especifica un parámetro sin llaves, se pasará por referencia; solo puede ser variables y accesos estáticos a los elementos de la matriz (a través de un punto, pero no a través de llaves []).
Create comment {$comments[$i]} on $posts.0 by $users.1 Read post {123} by $user Get comments of $users.1.id {{$page = 2}}
Cada vez que se llama al método API en el contexto de la llamada en sí (en las listas de declaraciones
antes y
después ) y en el contexto donde se llamó,
se crean las variables
$ request y
$ response . Estos son nombres reservados, no recomiendo usarlos para otros fines.
$ request está disponible en los bloques
antes y
después , y
$ response solo
está en
después ,
antes de que su valor se convierta en
Nulo . En el contexto de la llamada, estas variables están disponibles hasta la próxima llamada al método API, donde se reinicializarán.
$ Solicitud de estructura
$ request.method - String - Método HTTP
$ request.url - String - la URL solicitada
$ request.query - Array - una lista de parámetros GET
$ request.headers - Array - lista de encabezados de solicitud
$ request.cookies - Array - lista de cookies
$ reuqest.auth : matriz o nulo: datos para la autenticación HTTP
$ request.format - String - formato de datos de solicitud
$ request.data - escriba any - lo que se calculó en el campo de
datos$ Estructura de respuesta
$ response.network - Boolean - falso si el error estaba en el nivel de red por debajo de HTTP
$ response.code - Número o Nulo - código de respuesta, por ejemplo, 200 o 404
$ response.status - Cadena o Nulo - estado de respuesta, por ejemplo, "204 Sin contenido" o "401 no autorizado"
$ response.headers - Array - lista de encabezados de respuesta, los nombres de encabezado están en minúsculas
$ response.cookies - Array - lista de cookies
$ response.body - cualquier tipo - cuerpo de respuesta procesado como JSON, si hubo un error durante el análisis, entonces el elemento del
cuerpo no
existirá en absoluto:
@response.body == null
(
sobre la verificación de la existencia de variables )
$ response.raw - Cadena o Nulo - el cuerpo de respuesta sin procesar
$ response.duration - número de tipo - duración de la solicitud en segundos
Generación de modelos y datos de prueba.
Los generadores se utilizan para describir modelos y generar datos de prueba a partir de ellos. Las descripciones en formato YAML deben estar en el archivo
generators.yaml en el directorio de trabajo y / o archivos
* .yaml en el subdirectorio
./generators .
User: body: login: Faker\login() name: Faker\name() email: Faker\email() password: Faker\text(16) child: Child() birthday: dateFormat(Faker\datetime(), "U") settings: notifications_enabled: Faker\boolean() Child: body: name: Faker\name() gender: Faker\integer(1, 2) age: Faker\integer(0, 18) Comment($user): body: content: "Hi! I'm " + $user.name tags: - "tag1" - "tag2"
En el ejemplo anterior,
se declaran los tres generadores
Usuario () ,
Niño () y
Comentario () . En este caso, este último tiene el argumento
$ user y puede usar estos datos al generar. Los argumentos a los generadores siempre se pasan por valor. Además, el ejemplo utiliza varias funciones más integradas:
Faker \ name () ,
Faker \ email () ,
dateFormat () , etc.
Sección sobre funciones integradas .
Al llamar al generador de
Usuario () desde el ejemplo anterior, se generará una estructura que se verá así en JSON:
{ "login": "fgadrkq", "name": "Lucy Cechtelar", "email": "tkshlerin@collins.com", "password": "gbnaueyaaf", "child": { "name": "Adaline Reichel", "gender": 2, "age": 12 }, "birthday": 318038400, "settings": { "notifications_enabled": true } }
El valor del campo
hijo es el resultado del generador
Child () .
Como en la descripción de los métodos API, cualquier cadena que no esté entre comillas se trata como expresiones a evaluar. Esto puede ser no solo una llamada a otro generador, sino una expresión arbitraria, por ejemplo, en el generador de
Comentarios ($ usuario) , el campo de
contenido representa la concatenación de la cadena ¡Hola! Estoy y el nombre pasó a
$ userLos nombres de los generadores no distinguen entre mayúsculas y minúsculas y deben comenzar con una letra latina; pueden contener letras latinas, números, guiones bajos y barras diagonales inversas.
Debido a que la sintaxis para llamar a los generadores y las funciones integradas es la misma, comparten un espacio de nombres común. Por convención, sugiero utilizar una barra diagonal inversa como separador para especificar un "proveedor" o una biblioteca de funciones integradas, como las funciones Faker \ something (), basadas en la biblioteca
github.com/fzaninotto/Faker .
Los matices de usar generadores, no puedes leerUsando generadores, puede componer estructuras de datos:
# Userredentials $user Userredentials($user): body: login: $user.email password: $user.password # . , GlobalSearchResult($posts, $comments, $users): body: posts: title: " " list: $posts comments: title: " " list: $comments users: title: " " list: $users
GlobalSearchResult no son datos de prueba que se envían en la solicitud al método API, sino un modelo de respuesta que se puede comparar con lo que la API enviará, por ejemplo, utilizando funciones
similares () o
idénticas () .
El generador puede cambiar la estructura obtenida en el
cuerpo utilizando estructuras calculadas en los campos de
reemplazo y
eliminación . Te mostraré un ejemplo.
Supongamos que ya tiene un generador de
Usuario () que crea la estructura de datos correcta para el usuario. Ahora debe verificar cómo responderá la API si proporciona datos incorrectos. Puedes ir de dos maneras:
- Cree un generador de usuarios "incorrecto" desde cero. Pero luego obtendremos duplicación de código, y más tarde, por ejemplo, al agregar un nuevo campo al usuario de acuerdo con las necesidades de la lógica empresarial, deberá realizar cambios en dos lugares. SECO!
- Puede "heredarlo" de la estructura de usuario existente () configurándolo en el cuerpo . Y en reemplazar y eliminar, configure los campos que se agregarán / cambiarán y eliminarán.
# , , # InvalidUser($user): body: $user replace: email: Faker\String(6, 15) # password: Faker\String(1, 5) # new_field: " , " remove: name: size($user.name) < 10 # , 10 # , # InvalidNewUser: body: User() replace: login: "!@#$%^&*" # remove: about: true settings: notifications: 100500 # , # true
Cuando el generador funciona, primero se calcula la estructura de datos en el
cuerpo , luego se sobrescribe y se complementa con elementos de
reemplazo, y luego los campos especificados en
eliminar se eliminan si su valor es equivalente a
verdadero . Si el resultado del cálculo del
cuerpo ,
reemplazar o
eliminar no
es una matriz, entonces no habrá error, pero tampoco tiene sentido, porque no habrá campos que puedan ser reemplazados y eliminados.
Funciones incorporadas
Lista completa de funciones integradas . Por ejemplo, daré solo algunos de ellos.
Después del nombre de la función y la lista de argumentos, se indica el
tipo del valor de retorno, si se define uno.
Operaciones con variables:
similar ($ var, $ sample, $ checkTypes)
Boolean : devuelve
verdadero si los argumentos son del mismo tipo, si
$ var es una matriz, entonces todas las claves de cadena que están en
$ sample deben estar en
$ var , si
$ checkTypes es verdadero, entonces los tipos de los elementos correspondientes deben coincidir. En otras palabras, los elementos de la matriz
$ var son un subconjunto de los elementos de
$ sample .
idéntico ($ var, $ sample, $ checkTypes) Boolean es un análogo de similar () , con el inverso adicional, en el caso de matrices, todas las claves de cadena en $ var también deberían estar en $ sample . En otras palabras, los elementos de la matriz $ var son iguales a los elementos de la matriz $ sample hasta el tipo de elemento.max ($ var1, $ var2, ... $ varN): el máximo de los valores pasados (si se pueden comparar).min ($ var1, $ var2, ... $ varN): el mínimo de los valores pasados.if ($ condition, $ var1, $ var2): si $ condition == true, devolverá $ var1, de lo contrario $ var2. Sustitución del operador de coaching (hola MySQL).elección ($ condition1, $ var1, $ condition2, $ var2, ..., $ conditionN, $ varN): devolverá el primer $ varK encontrado si $ conditionK == verdadero.Trabajar con cuerdas:
size ($ string) Number : la longitud de la cadena en la codificación UTF-8.regex ($ string, $ regex) Boolean - verifica la cadena para la expresión regular .regexMatch ($ string, $ regex) Array - devolverá una matriz de cadenas - coincide con los grupos regulares $ regex .Procesamiento de matriz:
matriz ($ var1, $ var2, ... $ varN) Matriz : crea una matriz a partir de los elementos pasados.size ($ array) Number : la cantidad de elementos en la matriz.keys ($ array) Array : una lista de claves en la matriz.slice ($ array, $ offset, $ length) Array : parte de la matriz de $ offset de length $ length, ( más ).append ($ array, $ value) Array : agrega un elemento al final de la matriz.prepend ($ array, $ value) Array : agrega un elemento al principio de la matriz.Procesamiento de fecha:
dateFormat ($ date, $ format) String - formato de fecha, ( más sobre formatos ).dateModify ($ date, $ format) Date - cambia la fecha, conveniente para usar con formatos relativos .Generación de datos de prueba aleatoria:
Faker \ integer ($ min, $ max) Número - entero aleatorio de $ min a $ max inclusiveFaker \ ipv4 () Cadena - aleatorio IPv4Faker \ arrayElement ($ array) Cadena - elemento aleatorio de la matrizFaker \ name () Cadena - nombre aleatorioFaker \ email () Cadena - correo electrónico aleatorioAhora no hay muchas funciones integradas. Agregué solo lo que me parece necesario al probar. Puede agregar nuevas funciones según sea necesario en las nuevas versiones. Y en el futuro, si tiene demanda, agregaré la capacidad de crear funciones conectadas dinámicamente implementadas como clases especiales en PHP.Casos de prueba
Un caso de prueba es una secuencia de declaraciones que se pueden llamar como una unidad. Algún análogo del procedimiento en lenguajes de programación.La instrucción testcase crea un caso de prueba , seguido del nombre del caso de prueba, con una sintaxis similar a los nombres de los métodos API . Se prohíben los casos de prueba anidados. testcase Registration $device
La instrucción de ejecución puede llamar a un caso de prueba separado o a todos los casos de prueba que no requieren argumentos. run Get all users
La idea de tal lanzamiento es que los casos de prueba se pueden usar como pruebas independientes e independientes de una parte de la lógica empresarial y como procedimientos para evitar la duplicación de código en escenarios de prueba complejos.Los argumentos se pasan al caso de prueba por referencia o por valor en la analogía completa de pasar argumentos a métodos API.Variables y alcances
Los nombres de las variables distinguen entre mayúsculas y minúsculas y comienzan con un signo $ (sí, sí, soy un pshpshnik).Si el tipo de variable de la matriz , entonces el acceso a campos o elementos de valor individuales se produce a través del punto: $users.12.password
. Entre puntos, solo se permiten números o letras latinas, guiones bajos y números con la primera letra latina. Los nombres de campo también distinguen entre mayúsculas y minúsculas.Es posible un acceso dinámico a un elemento de matriz:$post.comments[$i + 1].content
Hay cuatro tipos de contextos: el alcance de las variables.Contexto global : creado al principio, contiene todas las variables declaradas al ejecutar declaraciones fuera de los casos de prueba y fuera de las llamadas al método API.Contexto del caso de prueba : se crea uno nuevo cada vez que se ejecuta el caso de prueba con la instrucción de ejecución .Contexto del método API : se crea cuando se llama al método API, cuando se ejecutan los operadores especificados en las secciones antes y después .Contexto generador- en los generadores no existe la posibilidad de crear nuevas variables o cambiarlas existentes, por lo tanto, las variables de contexto global y los argumentos son de solo lectura. Las variables siempre se pasan por valor al contexto del generador.Nota importante. En todos los contextos, las variables de contexto global están disponibles si sus homónimos no se crean en el contexto actual.Ejemplos de operadores para trabajar con variables: const $a = 1 let $a = 2
let $a = 1; $a = $a + 1; $a = $a + 2 print $a
Testcase Context example changes $argument1, $argument2 and $argument3 var $a = "changed" let $b = "changed" const $c = "changed" import $i let $i = "changed" var $argument1 = "changed" var $argument2 = "changed" var $argument3 = "changed"
Sistema de tipos y operaciones
Se utiliza la tipificación dinámica laxa.Los valores se almacenan en contenedores sobre los tipos de PHP correspondientes. Para una mejor comprensión, vea el sistema de tipos PHP. Hice un poco menos de libertad en la conversión de tipo dinámico. Por ejemplo, al agregar una cadena y un número "2" + 2
, se generará un error y PHP realizará silenciosamente la adición. Quizás deba revisar las reglas de tipeo dinámico en el futuro, pero hasta ahora he tratado de encontrar un equilibrio entre la conveniencia y el rigor necesarios para realizar pruebas confiables.Tipos de datos disponibles en PieceofScript:NumberEs un numero. Por razones de simplicidad, no hice tipos separados para Integer y Float. La única diferencia significativa entre los números enteros y reales en PieceofScript es el uso de una matriz como claves: los números reales se redondearán a enteros.7 -42 3.14159
Cadena : cadenas entre comillas dobles, es posible escapar con una barra"I say \"Hello world!\""
nula , establecida por la constantenull
booleana sin distinción entre mayúsculas y minúsculas , es el resultado de operaciones booleanas y operaciones de comparación, establecida por constantes sin distinción entre mayúsculas y minúsculastrue false
Fecha , fecha y hora. "Under the hood" es un DateTime . Las constantes se especifican entre comillas simples, en uno de los formatos .'now', '2008-08-07 18:11:31', 'last day of next month'
La matriz es una matriz, el único tipo no escalar. EnvolverLa Matriz . No hay literales de este tipo, pero las matrices pueden ser el resultado del trabajo de generadores, funciones integradas (por ejemplo, array () - hola PHP 5.3 y versiones posteriores), o simplemente puede acceder a las claves variables que se crearán dinámicamente tras la asignación. let $a.1 = 100 let $i = 1 let $a[$i + 1] = 200 let $a.sum = $a.1 + $a.2 print " "; $a.sum // 300 var $b = array(true, 3, $a, "Hi") // [true, 3, {1: 100, 2: 200, "sum":300}, "Hi"]
Al acceder a una variable o elemento de matriz inexistente, el script de prueba se detendrá y se generará un error. Pero cuando se ejecutan declaraciones de aserción o obligación , si se accede a una variable inexistente, no habrá error, pero la verificación se considerará fallida.Comprobando la existencia y el tipo de una variable
Debemos mencionar por separado la construcción de verificar la existencia y el tipo de la variable @ .Si se especifica @ en el nombre de la variable en lugar de $ , el resultado de esta construcción será uno de:- una cadena con el nombre del tipo de la variable o el tipo del elemento de la matriz, si se usaron claves;
- nulo si la variable no se encuentra en contextos accesibles o el elemento con la clave especificada en la matriz no existe.
Este diseño puede ser útil al verificar la estructura de las respuestas HTTP. var $a.string_field = "Hello World" var $a.number_field = 3.14 var $a.bool_field = true var $a.date_field = '+1 day' var $a.null_field = null var $a.array_field = array(1, "2") assert @a.string_field == "String" assert @a.number_field == "Number" assert @a.bool_field == "Boolean" assert @a.date_field == "Date" assert @a.null_field == "Null" assert @a.array_field == "Array" assert @a.array_field.0 == "Number" assert @a.array_field.1 == "String" assert @a.array_field.2 == null assert @notExistedVar == null
O en construcciones como: assert @comment.optional_field && $comment.optional_field > 20
Las operaciones booleanas se optimizan en el primer operando. Si el primer operando es falso , la operación && ni siquiera intentará calcular el segundo operando. Del mismo modo con || .
Guardar datos entre ejecuciones
Utilicé scripts separados no solo para probar después de completar la tarea, sino también durante el desarrollo. Complementé y cambié el script mientras implementaba la función. Más tarde, este script se convirtió en la base para escribir un caso de prueba, pero durante el desarrollo, uno tenía que hacer las mismas llamadas API una y otra vez. Al mismo tiempo, cada vez en la secuencia de comandos fue mucho tiempo crear nuevas entidades desde cero (por ejemplo, registro de usuarios), crear basura en la base de datos e interferir en todos los aspectos con el desarrollo. Por lo tanto, decidí agregar la capacidad de guardar y restaurar los valores de las variables entre los inicios en el almacenamiento de valores clave.El ahorro está habilitado por la opción de línea de comando --storage , que establece el nombre del archivo de almacenamiento: pos.phar run ./start.pos --storage=storage.yaml
Los datos se guardan en formato YAML, lo que facilita su lectura y edición.storage \ get (string $ key, $ defaultValue, boolean $ saveValue = true): si la clave $ key no existe o el archivo de almacenamiento no está especificado, devuelve $ defaultValue. De lo contrario, devuelve el valor almacenado. Si el argumento $ saveValue es verdadero y no se encontró la clave $ key, $ defaultValue se escribirá allí.storage \ set (string $ key, $ value): guarda $ value con la clave $ key y devuelve $ value. Si el archivo de almacenamiento no se ha configurado, simplemente devuelve $ value.almacenamiento \ clave (cadena $ regexp = nulo) Matriz- devuelve una matriz de todas las claves disponibles. Si el argumento $ regexp no es nulo, se devolverán las claves correspondientes a esta expresión regular. Si el archivo de almacenamiento no se ha configurado, devuelve una matriz vacía.Salida a stdout
PieceofScript puede generar informes en formato JUnit y HTML. El primero es necesario para la integración con sistemas CI / CD, por ejemplo Jenkins. El segundo es ver convenientemente los resultados de la prueba usted mismo, por ejemplo, cuando realiza pruebas localmente. Los archivos de informes se pueden configurar al inicio: pos.phar run ./start.pos --junit=junit_report.xml --html=report.html
Un ejemplo de un informe HTMLEn stdout se muestra diversa información sobre el trabajo del intérprete. Hay 5 niveles estándar de salida de información. Todo lo que se muestra en el mismo nivel también se muestra en los otros más "habladores".Silencioso : el nivel más "silencioso" se establece mediante la opción de línea de comandos -q .En este nivel, no se emite nada a stdout, incluso errores críticos de intérprete. Pero por el código de retorno distinto de cero, puedes entender que algo salió mal.Normal es el nivel predeterminado, sin especificar opciones.En este nivel, se generan errores en el intérprete. Solicitudes erróneas a métodos API y afirmaciones fallidas y comprobaciones obligatorias .Detallado - establecido por opción-v .En este nivel, se muestran los resultados de la declaración de impresión .Muy detallado : establecido por la opción -vv .En este nivel, se muestran las advertencias del intérprete.Depurar : establecido por la opción -vvv .En este nivel, se muestran todas las líneas de script que se ejecutan. Todas las solicitudes y respuestas de los métodos API, los resultados de todas las comprobaciones de afirmación y obligación .Ejemplos
El proverbio "Es mejor ver una vez que escuchar cien veces" es cierto y en la interpretación "es mejor ver el código una vez que leer su descripción cien veces". Preparé y puse los ejemplos en el https://github.com/maximw/PosExamples repositorio .Virustotal
Virustotal.com : un servicio para verificar archivos y enlaces maliciosos. Documentación API . Las pruebas se realizan para la parte pública de la API, con la excepción de los métodos de comentarios, porque No quiero arrojar basura en una verdadera API de "combate" con datos de prueba.Para acceder a la API, debe registrarse , obtener la clave y agregarla al archivo Virustotal / globals.pos .Ejecución de pruebas: pos.phar run ./Virustotal/start.pos
Che allí para un exe-shnik se encuentra en un repositorio?Para las pruebas, copié hiddeninput.exe del componente Consola del repositorio de Symfony. Este archivo se puede eliminar y, para las pruebas, utilice cualquier otro tamaño de hasta 32 mb.
Pastelería
Pasebin análogo. Documentación API .Para acceder a la API que necesita para registrarse , obtenga la clave y agréguela al archivo Pastery / globals.pos .Ejecución de pruebas: pos.phar run ./Pastery/start.pos
Es de destacar que con estas pruebas se encontró un error en el límite en el número de vistas. Ya ha sido arreglado por los desarrolladores de Pastery.El rick y morty
Creo que esta serie animada es conocida por muchos y amada por muchos. Documentación API . API consta de tres secciones casi idénticas de personaje, ubicación y episodio. Por lo tanto, los escenarios son casi los mismos, y solo mirar los casos de prueba es solo una de las secciones.Ejecución de pruebas: pos.phar run ./RickAndMorty/20MinutesTest.pos
Si conoce una API pública que sería interesante probar de esta manera, escriba un correo electrónico personal.Comentarios y planes para el futuro, si los hay
0) Tengo una lista de mejoras pequeñas y grandes que aún no necesito, pero que pueden ser útiles.Ver lista- Agregue un informe sobre el trabajo en formato Json con la posibilidad de sobrescribir después de varias ejecuciones de script
- body replace remove
- , YAML
- HTTP- ,
- , run . .
- HTML- stdout, -vvv
- https
- application/x-www-form-urlencoded CURLFile . Guzzle 6,
- « »,
- API,
- HTML-, bootstrap- « », .
1) Para la validación de modelos en respuestas API usando generadores, hasta ahora solo hay dos funciones: similar () e idéntica () . La validación con ellos es demasiado "torpe". Por supuesto, ya es posible validar las respuestas "a mano", y en algunos casos no es posible de ninguna otra manera, pero quiero hacerlo más conveniente y, cuando sea posible, evitar verificar manualmente la respuesta. Hay algunas ideas sobre cómo hacer posible generar y validar modelos utilizando la misma descripción del modelo, evitando la duplicación. Pero hasta ahora estas ideas no se han formado lo suficiente como para que pueda implementarlas en código.2) Creo que el andamiaje para métodos API basados en descripciones en OpenAPI ( Swagger ), RAML , colecciones será muy útilCartero . Pero vale la pena sentarse a trabajar mucho si PieceofScript lo vale.3) Sería bueno hacer complementos para algunos IDEs, con resaltado de código y autocompletado. El autocompletado de nombres de casos de prueba, métodos API, operadores y variables sería simplemente conveniente. Pero todavía no ha "cavado" en esta dirección. Comprender la creación de resaltado para Sublime Text and Language Server Protocol . Me alegraría si hay personas de ideas afines que ya están versados en tales cosas.4) No sé qué prioridad poner la capacidad de crear funciones conectadas dinámicamenteimplementado en PHP. Por un lado, todo es simple allí, es suficiente para lidiar con la carga automática y hacer una especificación de las clases y espacios de nombres utilizados. Por otro lado, las funciones complejas con sus dependencias inevitablemente causarán un conflicto de espacio de nombres entre dependencias (en el peor de los casos, diferentes versiones). También hay algo en lo que pensar.5) Los buenos sistemas de prueba ejecutan pruebas independientes en paralelo. Ahora esto se puede hacer iniciando el intérprete varias veces con diferentes archivos de inicio, donde se conectan diferentes casos de prueba. Pero creo que necesitamos integrar esto en el intérprete con detección automática de lo que se puede lanzar en paralelo.PD: Por un lado, dado que este es mi "oficio", sería lógico poner una publicación en el centro "Soy PR". Por otro lado, no hago relaciones públicas, no busco ninguna ganancia comercial, solo una herramienta que hice para mí, decidí "peinar" y publicarlo.