4 consejos para comprender las pruebas de unidades de front-end

Con la complejidad de cada proyecto, los altos requisitos de reutilización del código y la alta cohesión y el bajo acoplamiento entre los módulos de código de front-end, el proceso de prueba unitaria en la ingeniería de front-end se vuelve muy necesario.

1. ¿Qué son las pruebas unitarias de front-end?

En primer lugar, tenemos que aclarar cuál es la prueba:

Para detectar si un objetivo específico cumple con el estándar, se utilizan herramientas o métodos especiales para la verificación, y finalmente se obtiene un resultado específico.

Para el proceso de desarrollo de front-end, el objetivo específico aquí se refiere al código que escribimos, y la herramienta es el marco de prueba (biblioteca), caso de prueba, etc. que necesitamos usar. El resultado del departamento de inspección es mostrar si la prueba se superó o dar un informe de prueba, para facilitar la resolución de problemas y posterior corrección del problema.

Basándonos en la declaración "qué" de las pruebas, para facilitar la comprensión avanzada de los colegas que solo están involucrados en el desarrollo front-end, enumeraremos el "qué" de las pruebas unitarias:

Las pruebas que requieren acceso a la base de datos no son pruebas unitarias

Las pruebas que requieren acceso a la red no son pruebas unitarias

Las pruebas que requieren acceso al sistema de archivos no son pruebas unitarias

- El arte de modificar código

Para la explicación citada de la prueba unitaria "lo que no es", hasta ahora. En vista de las limitaciones de espacio, creo que los colegas de desarrollo de front-end tendrán una comprensión preliminar propia después de ver el contenido de la cita.

2. El significado de las pruebas unitarias y por qué necesita pruebas unitarias

2.1 La importancia de las pruebas unitarias

Para la ingeniería de front-end actual, un proyecto estándar y completo, las pruebas son muy necesarias. Muchas veces acabamos de completar el proyecto e ignoramos la parte de la prueba del proyecto. La importancia de la prueba radica en los siguientes puntos:

  1. TDD (Test Driven Development) ha demostrado ser un principio de escritura de software eficaz, que puede cubrir interfaces más funcionales.
  2. Realice comentarios rápidamente sobre la salida de su función y verifique sus ideas.
  3. Para garantizar la seguridad de la refactorización del código, no existe un código fijo y los casos de prueba pueden brindarle tranquilidad para la estructura del código modificable.
  4. El código fácil de probar indica un buen diseño. Antes de hacer una prueba unitaria, debes crear una instancia. Si esta cosa tiene muchas dependencias, la estructura de la prueba consumirá mucho tiempo y afectará la eficiencia de tu prueba. ¿Qué debes hacer? Para confiar en la separación, una clase debe intentar garantizar una única función, como la separación de vista y función, para que su código también sea fácil de mantener y comprender.
2.2 ¿Por qué necesitamos pruebas unitarias?
  1. La primera es la razón fundamental de una prueba unitaria de front-end: JavaScript es un lenguaje dinámico, carece de verificación de tipos y no puede localizar errores durante la compilación; problemas de compatibilidad de host de JavaScript. Por ejemplo, el rendimiento de las operaciones DOM en diferentes navegadores.
  2. Corrección: la prueba puede verificar la exactitud del código, por lo que puede estar seguro antes de conectarse.
  3. Automatización: por supuesto, también puede probar manualmente e imprimir información interna a través de la consola, pero esto es algo que se realiza una sola vez. La siguiente prueba debe realizarse desde cero y no se puede garantizar la eficiencia. Al escribir casos de prueba, puede escribir una vez y ejecutar varias veces.
  4. Explicativo: Los casos de prueba se utilizan para probar la importancia de las interfaces y los módulos, por lo que la forma de utilizar estas API estará involucrada en los casos de prueba. Si otros desarrolladores quieren utilizar estas API, leer casos de prueba es una buena forma, a veces más clara que la documentación.
  5. Impulse el desarrollo y el diseño de la guía: el requisito previo para que el código se pruebe es la capacidad de prueba del código en sí, por lo que para garantizar la capacidad de prueba del código, debe prestar atención al diseño de la API durante el desarrollo. TDD avanza la prueba para desempeñar ese papel.
  6. Garantía de refactorización: Los productos de la industria de Internet son iterativamente rápidos y debe haber un proceso de refactorización de código después de la iteración ¿Cómo se puede garantizar la calidad del código refactorizado? Respaldado por casos de prueba, puede refactorizar audazmente.

3. Cómo escribir casos de prueba unitarios

3.1 Principio
  • Al probar el código, solo considere la prueba, no la implementación interna
  • Los datos simulan la realidad tanto como sea posible, cuanto más cerca de la realidad, mejor
  • Considere completamente las condiciones de contorno de los datos
  • Concéntrese en el código clave, complejo y central y concéntrese en las pruebas
  • Utilice AOP (beforeEach, afterEach) para reducir la cantidad de código de prueba y evitar funciones inútiles
  • La combinación de pruebas y desarrollo funcional favorece el diseño y la refactorización de código.
3.2 Dos metodologías de prueba unitaria de uso común

En las pruebas unitarias, hay dos metodologías de uso común: TDD (desarrollo impulsado por pruebas) y BDD (desarrollo impulsado por el comportamiento)

3.3 Creo que tendrá una visión personal de TDD y BDD después de leerlo. Aquí hablaré sobre mi comprensión de TDD y BDD:

TDD (Desarrollo basado en pruebas)

La idea básica es promover todo el desarrollo mediante pruebas.

  • El propósito principal de las pruebas unitarias no es poder escribir códigos de prueba que superen todas las pruebas con una gran cobertura, sino probar varias posibilidades de lógica funcional desde la perspectiva del usuario (llamador), ayudando así a mejorar la calidad del código.

  • Probar es un medio, no un fin. El objetivo principal de las pruebas no es demostrar que el código es correcto, sino ayudar a encontrar errores, incluidos errores de bajo nivel.

  • Prueba rápido. Ejecución rápida, escritura rápida

  • Mantenga el código de prueba conciso

  • No ignorará las pruebas fallidas. Una vez que el equipo comienza a aceptar 1 falla de construcción de prueba, se adapta gradualmente a 2, 3, 4 o más fallas. En este caso, el equipo de prueba ya no funciona

Cabe señalar que :

  • ¡No debe malinterpretar el propósito principal de TDD!

  • Las pruebas no son para cobertura y precisión

  • Pero como ejemplo, diga a los desarrolladores qué código escribir

  • Luz roja (el código no es perfecto, la prueba está colgando) -> luz verde (escribe el código, la prueba pasa) -> refactor (optimiza el código y asegúrate de que la prueba pasa)

El proceso de TDD es :

  1. Análisis de la demanda, pensando en la realización. Considere cómo "usar" el código de producto, ya sea un método de instancia o un método de clase, si pasar parámetros del constructor o llamadas a métodos, nombres de métodos, valor de retorno, etc. En este momento, en realidad está haciendo diseño y el diseño está incorporado en el código. La prueba es roja en este momento

  2. Implementar el código para que la prueba sea "luz verde"

  3. Refactorizar, luego repetir la prueba

  4. Finalmente, cumpla con todos los requisitos, a saber:

    • Cada concepto se expresa claramente

    • Sin autorrepetición en el código

    • Nada extra

    • Pasado la prueba

BDD (desarrollo impulsado por el comportamiento):

El desarrollo impulsado por el comportamiento (BDD) se enfoca en obtener una comprensión del comportamiento esperado del software a través de discusiones con las partes interesadas (en resumen, los clientes), y el enfoque está en la comunicación

El proceso de BDD es:

  1. Definir objetivos específicos y medibles desde una perspectiva empresarial.

  2. Encuentre una forma de lograr las funciones más importantes para el negocio

  3. Luego, describe cada comportamiento ejecutable específico como una historia. El método de descripción se basa en un vocabulario común, que tiene una capacidad de expresión precisa y un significado coherente. Por ejemplo, expect, should,assert

  4. Encuentra el lenguaje y el método adecuados para realizar el comportamiento.

  5. El probador comprueba si el resultado de la operación del producto se ajusta al comportamiento esperado. Entregue productos que cumplan con las expectativas del usuario en la mayor medida posible y eviten los problemas causados ​​por expresiones inconsistentes

4. Flujo de trabajo de prueba de front-end de Mocha / Karma + Travis.CI

El contenido anterior habla de la metodología de las pruebas unitarias a partir de lo que son las pruebas unitarias. Entonces, ¿cómo usar marcos comunes para pruebas unitarias? ¿Cuál es el entorno de herramientas para las pruebas unitarias? ¿Qué es un ejemplo práctico de prueba unitaria?

Primero, debo presentar brevemente a Mocha, Karma y Travis.CI

Mocha : Mocha es un marco de prueba de front-end rico en funciones. El llamado "marco de prueba" es una herramienta para ejecutar pruebas. A través de él, puede agregar pruebas para aplicaciones JavaScript para garantizar la calidad del código. Mocha se puede ejecutar en función del entorno Node.js o en el entorno del navegador.

Karma : una herramienta de gestión de procesos de ejecución de pruebas de JavaScript (Test Runner) basada en Node.js. Esta herramienta se puede utilizar para probar los principales navegadores web, también se puede integrar en las herramientas de CI (integración continua) y también se puede utilizar con otros editores de código. Una característica poderosa de esta herramienta de prueba es que puede monitorear los cambios de archivo, luego ejecutarlo por sí mismo y mostrar los resultados de la prueba a través de console.log. Una característica poderosa de Karma es que puede monitorear la transformación de un conjunto de archivos e inmediatamente comenzar a probar los archivos guardados sin salir del editor de texto. Los resultados de las pruebas generalmente se muestran en la línea de comandos, no en un editor de código. Esto también permite que Karma se use básicamente con cualquier editor JS.

Travis.CI : proporciona servicios de integración continua (Integración continua, CI para abreviar). Vincula los proyectos en Github, siempre que haya nuevo código, se rastreará automáticamente. Luego, proporcione un entorno en ejecución, realice pruebas, complete la compilación e implemente en el servidor.

La integración continua significa que mientras el código cambie, las compilaciones y las pruebas se ejecutan automáticamente y los resultados se retroalimentan. Después de asegurarse de que cumple con las expectativas, el nuevo código se "integra" en la red troncal.

La ventaja de la integración continua es que puede ver los resultados de cada pequeño cambio en el código, acumulando así pequeños cambios, en lugar de fusionar un gran bloque de código al final del ciclo de desarrollo.

Biblioteca de afirmaciones

Después de la introducción del marco de herramientas básico, creo que aquellos que saben un poco sobre pruebas sabrán que para hacer pruebas unitarias, es necesario escribir scripts de prueba, por lo que los scripts de prueba deben usar la biblioteca de aserciones. "Afirmación", comprensión personal es "usar el código para determinar la exactitud del código de prueba, verificar y exponer el error de este código". Luego, para las pruebas unitarias de front-end, existen las siguientes bibliotecas de aserciones de uso común:

Mira un ejemplo de código:

expect(add(1, 1)).to.be.equal(2);

Este es un código de afirmación.

La llamada "afirmación" consiste en juzgar si el resultado de ejecución real del código fuente es coherente con el resultado esperado, y arrojar un error si es inconsistente. La afirmación anterior significa que cuando se llama a add (1, 1), el resultado debe ser igual a 2. Todos los casos de prueba (que bloquea) deben contener una o más afirmaciones. Es la clave para escribir casos de prueba. La función de aserción es implementada por la biblioteca de aserciones. Mocha no tiene una biblioteca de aserciones, por lo que la biblioteca de aserciones debe introducirse primero.

Introduzca un ejemplo de código de biblioteca de aserciones:

var expect = require('chai').expect;

Hay muchos tipos de bibliotecas de aserciones. Mocha no limita cuál usar. Le permite usar cualquier biblioteca de aserciones que desee. La biblioteca de aserciones introducida por el código anterior es chai, y está especificada para usar su estilo de aserción de espera. Las siguientes bibliotecas de aserciones comunes:

Aquí presentamos principalmente las API de uso común en node assert

  • afirmar (valor [, mensaje])
  • asert.ok (valor [, mensaje])
  • assert.equal (actual, esperado [, mensaje])
  • asert.notEqual (real, esperado [, mensaje])
  • assert.strictEqual (actual, esperado [, mensaje])
  • assert.notStrictEqual (actial, esperado [, mensaje])
  • assert.deepEqual (actual, esperado [, mensaje])
  • assert.notDeepEqual (real, esperado [, mensaje])
  • assert.deepStrictEqual (actual, esperado [, mensaje])
  • asert.notDeepStrictEqual (real, esperado [, mensaje])
  • assert.throws (bloque [, error] [, mensaje])
  • assert.doesNotThrow (bloque [, error] [, mensaje])

afirmar (valor [, mensaje])

Confirme si el valor del valor es verdadero. El juicio de igualdad aquí usa == en lugar de ===. mensaje es la descripción de la aserción y es un parámetro opcional.

const assert = require('assert');
assert(true);

asert.ok (valor [, mensaje])

Utilice el mismo método assert(value[, message]).

assert.equal (actual, esperado [, mensaje])

Espere que los valores reales y esperados sean iguales. Los datos reales y esperados utilizados para la comparación son datos de tipos básicos (cadena, número, aprendizaje booleano, nulo, indefinido). La comparación usa == en lugar de ===.

it('assert.equal', () => {
  assert.equal(null, false, 'null compare with false');  // 报错
  assert.equal(null, true, 'null compare with true');  // 报错
  assert.equal(undefined, false, 'undefined compare with false'); // 报错
  assert.equal(undefined, true, 'undefined compare with true'); // 报错
  assert.equal('', false, '"" compare with false');  // 正常
})

notEqual (real, esperado [, mensaje])

Utilice el mismo que assert.equal(actual, expect[, message])acaba de negar los resultados esperados (es decir, no es igual a).

assert.strictEqual (actual, esperado [, mensaje])

Use lo mismo assert.equal(actual, expect[, message])pero más internamente usando == === en su lugar.

assert.notStrictEqual (actial, esperado [, mensaje])

Utilice el mismo que assert.strictEqual(actual, expect[, message])acaba de negar los resultados esperados (es decir, no estrictamente iguales).

it('assert.strictEqual', () => {
  assert.strictEqual('', false); // 报错
})

assert.deepEqual (actual, esperado [, mensaje])

El método deepEqual se utiliza para comparar dos objetos. El proceso de comparación consiste en comparar si los valores clave y de valor de los dos objetos son los mismos. Al comparar, utilice == en lugar de ===.

it('assert.deepEqual', () => {
  const a = { v: 'value' };
  const b = { v: 'value' };
  assert.deepEqual(a, b);
})

assert.notDeepEqual (real, esperado [, mensaje])

Utilice los mismos assert.deepEqual(actual, expect[, message])resultados esperados negados (es decir, no estrictamente la misma profundidad).

assert.deepStrictEqual (actual, esperado [, mensaje])

Use lo mismo assert.deepEqual(actual, expect[, message])pero más internamente usando == === en su lugar.

asert.notDeepStrictEqual (real, esperado [, mensaje])

Utilice lo mismo assert.deepStrictEqual(actual, expect[, message])pero el resultado se invierte (es decir, no es estrictamente la misma profundidad).

assert.throws (bloque [, error] [, mensaje])

Aserción y captura de errores, afirma que el bloque de código especificado informará un error o arrojará un error. Si el código se ejecuta sin errores, la aserción falla y la aserción es anormal.

it('throws', () => {
  var fun = function() {
    xxx
  };
  assert.throws(fun, 'fun error');
})

assert.doesNotThrow (bloque [, error] [, mensaje])

Aserción y captura de errores, el uso es similar a los lanzamientos, pero opuesto al resultado esperado de los lanzamientos. Afirme que el bloque de código especificado no informará un error ni arrojará un error cuando se ejecute. Si el código se ejecuta incorrectamente, la aserción falla y la aserción es anormal.

it('throws', () => {
  var fun = function() {
    xxx
  };
  assert.doesNotThrow(fun, 'fun error');
})

Después de la introducción de las herramientas correspondientes, hablaré sobre la experiencia práctica personal en operación para el uso de Mocha, Karma y Travis.CI.

Moca

  • Instalar mocha
npm install mocha -g

Por supuesto, también se puede instalar globalmente o no, y solo se puede instalar localmente en el proyecto.

npm install mocha --save
  • Crea un archivo de prueba test.js
var assert = require('assert')

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1, 2, 3].indexOf(-1))
    })
  })
})

Este archivo es una prueba simple y Arrayun indexOf()método. Aquí estoy usando bibliotecas de aserción proporcionadas por Assertmódulos de nodo en la API. Aquí la aserción -1 es igual a la matriz [1, 2, 3]para realizar el indexOf(-1)valor devuelto más tarde, si se pasa la prueba, no se queja, si hay errores informará un error.

Aquí usamos el global instalado mochapara ejecutar este archivo mocha test.js.
El siguiente es el resultado devuelto

Ejemplos de casos de prueba básicos

const assert = require('assert');

describe('测试套件描述', function() {
  it('测试用例描述: 1 + 2 = 3', function() {
    // 测试代码
    const result = 1 + 2;
    // 测试断言
    assert.equal(result, 3);
  });
});

Los casos de prueba de Mocha incluyen principalmente las siguientes partes:

  1. describir el conjunto de pruebas definido (conjunto de pruebas)
  2. caso de prueba definido por él
  3. Código de prueba
  4. Afirmaciones

Nota: Puede haber varios conjuntos de pruebas y casos de prueba en cada archivo de prueba. Mocha puede ejecutarse no solo en el entorno del nodo, sino también en el entorno del navegador; también npm i mocha -ges factible ejecutarlo en el nodo instalando mocha globalmente y luego ejecutando los casos de prueba en la línea de comando.

Aquí hay una pequeña introducción detallada a la redacción del guión de prueba.

El papel de Mocha es ejecutar scripts de prueba, primero debe aprender a escribir scripts de prueba. El llamado "script de prueba" es un script que se utiliza para probar el código fuente. A continuación se muestra el código de un módulo adicional add.js.

// add.js
function add(x, y) {
  return x + y;
}

module.exports = add;

Para probar si el módulo de adición es correcto, es necesario escribir un script de prueba. Por lo general, el script de prueba tiene el mismo nombre que el script de origen que se va a probar, pero el sufijo es .test.js (para pruebas) o .spec.js (para especificaciones). Por ejemplo, el nombre del script de prueba de add.js es add.test.js.

// add.test.js
var add = require('./add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });
});

El código anterior es un script de prueba que se puede ejecutar de forma independiente. El script de prueba debe incluir uno o más bloques de descripción, y cada bloque de descripción debe incluir uno o más bloques.

El bloque de descripción se denomina "conjunto de pruebas" y representa un conjunto de pruebas relacionadas. Es una función, el primer parámetro es el nombre de la suite de pruebas ("prueba de función de adición"), y el segundo parámetro es una función que se ejecuta realmente.

El bloque it se denomina "caso de prueba", que representa una única prueba y es la unidad de prueba más pequeña. También es una función El primer parámetro es el nombre del caso de prueba ("1 más 1 debe ser igual a 2"), y el segundo parámetro es una función real a ejecutar.

La ventaja de la afirmación de esperar es que está muy cerca del lenguaje natural. Aquí hay algunos ejemplos.

// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });

// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;

// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);

// include
expect([1, 2, 3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);

Básicamente, las afirmaciones de espera se escriben de la misma manera. La cabeza es el método de espera y la cola es el método de afirmación, como igual, a / an, ok, coincidencia, etc. Use to o to.be para conectarse entre los dos. Si la aserción de espera no es verdadera, se generará un error. De hecho, siempre que no se produzcan errores, el caso de prueba pasará.

it('1 加 1 应该等于 2', function() {});

En el caso de prueba anterior, no hay código adentro. Dado que no se arrojó ningún error, aún se aprobará.

Karma

Algunos módulos de uso común basados ​​en pruebas de karma

Instalación del módulo

# 基础测试库
npm install karma-cli -g
npm install karma mocha karma-mocha --save-dev

# 断言库
npm install should --save-dev
npm install karma-chai --save-dev

# 浏览器相关
npm install karma-firefox-launcher --save-dev
npm install karma-chrome-launcher --save-dev

Si intercambia experiencia en pruebas de software, pruebas de interfaz, pruebas automatizadas y entrevistas. Si está interesado, puede agregar comunicación de prueba de software: 1085991341, y habrá intercambios técnicos con colegas.

Configuración

La configuración aquí se refiere principalmente a karma.conf.jsla configuración relacionada. Si desea usar karma y mocha, es mejor npm install karma-cli -ginstalarlos globalmente karma-cli.

Dos campos a tener en cuenta:

  • singleRun: si el valor es verdadero, el navegador saldrá automáticamente y cerrará la ventana del navegador después de ejecutar la prueba. El valor de singleRun se puede asignar dinámicamente según el entorno operativo y se pueden agregar NODE_ENVvariables al comando de inicio .
  • Navegadores: configuración del navegador (se pueden configurar varios navegadores); si no se puede iniciar el navegador, se requiere la configuración del navegador correspondiente. Si el navegador no se inicia al configurar el navegador de inicio automático, es posible que deba configurarlo en --no-sandboxmodo.
{
  "browsers": ["Chrome", "ChromeHeadless", "ChromeHeadlessNoSandbox"],
  "customLaunchers": {
    "ChromeHeadlessNoSandbox": {
      "base": "ChromeHeadless",
      "flags": ["--no-sandbox"]
    }
  }
}

o

{
  "browsers": ["Chrome_travis_ci"],
  "customLaunchers": {
    "Chrome_travis_ci": {
      "base": "Chrome",
      "flags": ["--no-sandbox"]
    }
  }
}

Acceso al proyecto Github Travis.CI para la integración y los pasos de prueba automatizados

  • Cree y complete un proyecto que pueda probarse en github. La finalización aquí se refiere a la necesidad de completar las funciones básicas del proyecto y el código del caso de prueba.
  • Configure travis-ci para que reconozca el archivo de configuración que se lee, de modo que cuando travis-ci esté conectado, pueda conocer parte de la configuración durante la prueba.
  • github y travis-ci son un sitio, en otras palabras, si las dos cosas se pueden conectar. Los usuarios deben iniciar sesión en travis-ci y autorizar el acceso a su proyecto de github y realizar la configuración del proyecto relacionada.
  • Una vez completada la conexión, puede ejecutar el código de prueba escrito según sus necesidades o puede configurar tareas regulares para ejecutar la prueba.

Creación de proyectos, mejora de funciones del proyecto y código de prueba.

  • Requisitos del proyecto: implementar un método de suma
  • Prueba: mediante el mochamétodo de suma para realizar la prueba.

Aquí está la estructura del proyecto, creada después de la finalización del proyecto mediante la npm i mocha -Dinstalación de mochamódulos. Luego, ejecute localmente npm testpara ver si la prueba pasó. Si la prueba pasa, significa que podemos continuar con el siguiente paso.

Crear archivo de configuración de prueba travis-ci

Cree el archivo de configuración travis-ci.travis.yml , el contenido del archivo.

language: node_js
node_js:
  - "node"
  - "8.9.4"

En este punto, el proceso de desarrollo del proyecto y escritura del código de prueba se completa básicamente, y el siguiente paso se puede conectar a las pruebas de travis-ci .

Acceder a travis-ci

Inicie sesión en el sitio web oficial de travis-ci a través de GitHub

Encuentre el proyecto que necesita probarse recién creado en GitHub e inicie la prueba

Verifique el proceso de prueba y encuentre el problema a tiempo.

Verifique si el estado de la prueba pasó la prueba. Si falla, verifique el problema y modifíquelo repetidamente; si pasa, puede agregar una marca de aprobación de prueba en el documento del proyecto.


El contenido anterior es el contenido completo de este artículo. El contenido anterior espera ser útil para usted. Los amigos que han sido ayudados son bienvenidos a dar me gusta y comentar.

Supongo que te gusta

Origin blog.csdn.net/Chaqian/article/details/106603727
Recomendado
Clasificación