unitarios unitarias test pruebas angular unit-testing typescript jasmine

test - Cómo escribir pruebas unitarias para Angular/TypeScript para métodos privados con Jasmine



pruebas unitarias angular 6 (10)

Como la mayoría de los desarrolladores no recomiendan probar la función privada , ¿por qué no probarla?

P.ej.

YourClass.ts

export class FooBar { private _status: number; constructor( private foo : Bar ) { this.initFooBar({}); } private initFooBar(data){ this.foo.bar( data ); this._status = this.foo.foo(); } }

TestYourClass.spec.ts

describe("Testing foo bar for status being set", function() { ... //Variable with type any let fooBar; fooBar = new FooBar(); ... //Method 1 //Now this will be visible fooBar.initFooBar(); //Method 2 //This doesn''t require variable with any type fooBar[''initFooBar''](); ... }

Gracias a @Aaron, @Thierry Templier.

¿Cómo se prueba una función privada en angular 2?

class FooBar { private _status: number; constructor( private foo : Bar ) { this.initFooBar(); } private initFooBar(){ this.foo.bar( "data" ); this._status = this.fooo.foo(); } public get status(){ return this._status; } }

La solución que encontré

  1. Coloque el código de prueba dentro del cierre o Agregue código dentro del cierre que almacene referencias a las variables locales en objetos existentes en el ámbito externo.

    Más tarde, elimine el código de prueba con una herramienta. http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/

¿Me ha sugerido una mejor manera de resolver este problema si ya lo hizo?

PD

  1. La mayor parte de la respuesta para un tipo similar de pregunta como esta no da una solución al problema, es por eso que hago esta pregunta

  2. La mayoría de los desarrolladores dicen que no prueba las funciones privadas, pero no digo que sean incorrectas o correctas, pero es necesario que mi caso sea privado.


Como muchos ya han dicho, por mucho que quieras probar los métodos privados, no debes hackear tu código o transpiler para que funcione para ti. TypeScript moderno negará la mayoría de los hacks que la gente ha proporcionado hasta ahora.

Solución

TLDR ; si un método debe ser probado, entonces debe desacoplar el código en una clase en la que pueda exponer el método para que sea público para ser probado.

La razón por la que tiene el método privado es porque la funcionalidad no necesariamente pertenece a ser expuesta por esa clase, y por lo tanto, si la funcionalidad no pertenece allí, debe desacoplarse en su propia clase.

Ejemplo

Me encontré con este artículo que hace un gran trabajo al explicar cómo debe abordar probar métodos privados. Incluso cubre algunos de los métodos aquí y cómo son malas implementaciones.

https://patrickdesjardins.com/blog/how-to-unit-test-private-method-in-typescript-part-2

Nota : Este código se elimina del blog vinculado anteriormente (estoy duplicando en caso de que cambie el contenido detrás del enlace)

antes de

export class FooBar { private _status: number; constructor( private foo : Bar ) { this.initFooBar({}); } private initFooBar(data){ this.foo.bar( data ); this._status = this.foo.foo(); } } Después

(function(System) {(function(__moduleName){System.register([], function(exports_1, context_1) { "use strict"; var __moduleName = context_1 && context_1.id; var FooBar; return { setters:[], execute: function() { FooBar = (function () { function FooBar(foo) { this.foo = foo; this.initFooBar({}); } FooBar.prototype.initFooBar = function (data) { this.foo.bar(data); this._status = this.foo.foo(); }; return FooBar; }()); exports_1("FooBar", FooBar); } } })(System);


El punto de "no probar métodos privados" realmente es probar la clase como alguien que la usa .

Si tiene una API pública con 5 métodos, cualquier consumidor de su clase puede usarlos y, por lo tanto, debe probarlos. Un consumidor no debe acceder a los métodos / propiedades privados de su clase, lo que significa que puede cambiar miembros privados cuando la funcionalidad expuesta pública permanece igual.

Si confía en la funcionalidad interna extensible, use protected lugar de private .
Tenga en cuenta que protected sigue siendo una API pública (!) , Solo que se usa de manera diferente.

expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/); // TS2341: Property ''initFooBar'' is private and only accessible within class ''FooBar''

Prueba unitaria de propiedades protegidas de la misma manera en que un consumidor las usaría, mediante subclases:

// @ts-ignore expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);


Esta ruta que tomo es una en la que creo funciones fuera de la clase y asigno la función a mi método privado.

export class MyClass { private _myPrivateFunction = someFunctionThatCanBeTested; } function someFunctionThatCanBeTested() { //This Is Testable }

Ahora no sé qué tipo de reglas de OOP estoy rompiendo, pero para responder a la pregunta, así es como pruebo los métodos privados. Doy la bienvenida a cualquiera para que asesore sobre Pros y Contras de esto.


Estoy con usted, aunque es un buen objetivo "probar solo la unidad de la API pública", hay momentos en que no parece tan simple y usted siente que está eligiendo entre comprometer la API o las pruebas unitarias. Ya lo sabes, ya que eso es exactamente lo que estás pidiendo que hagas, así que no voy a entrar en eso. :)

En TypeScript he descubierto algunas formas en que puede acceder a miembros privados por el simple hecho de realizar pruebas unitarias. Considera esta clase:

class MyThing { private _name:string; private _count:number; constructor() { this.init("Test", 123); } private init(name:string, count:number){ this._name = name; this._count = count; } public get name(){ return this._name; } public get count(){ return this._count; } }

A pesar de que TS restringe el acceso a los miembros de la clase usando private , protected , public , el JS compilado no tiene miembros privados, ya que esto no es una cosa en JS. Se utiliza exclusivamente para el compilador de TS. Por lo tanto:

  1. Puede hacer valer a any y evitar que el compilador le advierta sobre restricciones de acceso:

    (thing as any)._name = "Unit Test"; (thing as any)._count = 123; (thing as any).init("Unit Test", 123);

    El problema con este enfoque es que el compilador simplemente no tiene idea de lo que está haciendo correctamente, por lo que no obtiene los errores de tipo deseados:

    (thing as any)._name = 123; // wrong, but no error (thing as any)._count = "Unit Test"; // wrong, but no error (thing as any).init(0, "123"); // wrong, but no error

  2. Puede usar el acceso a la matriz ( [] ) para acceder a los miembros privados:

    thing["_name"] = "Unit Test"; thing["_count"] = 123; thing["init"]("Unit Test", 123);

    Si bien parece funky, TSC realmente validará los tipos como si accediera a ellos directamente:

    thing["_name"] = 123; // type error thing["_count"] = "Unit Test"; // type error thing["init"](0, "123"); // argument error

    Para ser honesto, no sé por qué esto funciona. Aparentemente, esta es una "escotilla de escape" intencional para darle acceso a miembros privados sin perder la seguridad del tipo. Esto es exactamente lo que creo que quieres para tu prueba de unidad.

Aquí hay un ejemplo de trabajo en TypeScript Playground .


Estoy de acuerdo con @toskv: no recomendaría hacer eso :-)

Pero si realmente desea probar su método privado, puede ser consciente de que el código correspondiente para TypeScript corresponde a un método del prototipo de la función de constructor. Esto significa que se puede usar en tiempo de ejecución (mientras que probablemente tendrá algunos errores de compilación).

Por ejemplo:

class User{ public getUserInformationToDisplay(){ //... this.getUserAddress(); //... } private getUserAddress(){ //... this.formatStreet(); //... } private formatStreet(){ //... } }

será trasladado a:

class User{ private address:Address; public getUserInformationToDisplay(){ //... address.getUserAddress(); //... } } class Address{ private format: StreetFormatter; public format(){ //... format.ToString(); //... } } class StreetFormatter{ public toString(){ // ... } }

Vea este plunkr: https://plnkr.co/edit/calJCF?p=preview .


La respuesta de Aaron es la mejor y está funcionando para mí :) Lo votaría pero lamentablemente no puedo (falta reputación).

Debo decir que probar métodos privados es la única forma de usarlos y tener un código limpio en el otro lado.

Por ejemplo:

class Something { save(){ const data = this.getAllUserData() if (this.validate(data)) this.sendRequest(data) } private getAllUserData () {...} private validate(data) {...} private sendRequest(data) {...} }

Tiene mucho sentido no probar todos estos métodos a la vez porque tendríamos que burlarnos de esos métodos privados, que no podemos burlarnos porque no podemos acceder a ellos. Esto significa que necesitamos mucha configuración para que una prueba unitaria pruebe esto como un todo.

Dicho esto, la mejor manera de probar el método anterior con todas las dependencias es una prueba de extremo a extremo, porque aquí se necesita una prueba de integración, pero la prueba E2E no lo ayudará si está practicando TDD (Test Driven Development), pero prueba Cualquier método lo hará.


No escriba pruebas para métodos privados. Esto derrota el punto de las pruebas unitarias.

  • Deberías probar la API pública de tu clase
  • NO deberías probar los detalles de implementación de tu clase

Ejemplo

class SomeClass { public addNumber(a: number, b: number) { return a + b; } }

La prueba para este método no debería necesitar cambiar si luego la implementación cambia, pero el behaviour de la API pública sigue siendo el mismo.

class SomeClass { public addNumber(a: number, b: number) { return this.add(a, b); } private add(a: number, b: number) { return a + b; } }

No haga públicos los métodos y propiedades solo para probarlos. Esto generalmente significa que:

  1. Está intentando probar la implementación en lugar de la API (interfaz pública).
  2. Debe mover la lógica en cuestión a su propia clase para facilitar las pruebas.

Perdón por el necro en esta publicación, pero me siento obligado a opinar sobre un par de cosas que no parecen haber sido abordadas.

Primero, lo más importante: cuando nos encontramos necesitando acceso a miembros privados en una clase durante la prueba de la unidad, generalmente es una gran y gruesa bandera roja que nos hemos burlado de nuestro enfoque estratégico o táctico y hemos violado inadvertidamente el principio de responsabilidad individual al presionar comportamiento donde no pertenece. Sentir la necesidad de acceder a métodos que en realidad no son más que una subrutina aislada de un procedimiento de construcción es uno de los casos más comunes de esto; sin embargo, es como si tu jefe esperara que aparecieras listo para ir a trabajar y también tuviera una necesidad perversa de saber qué rutina mañanera seguiste para llegar a ese estado ...

La otra instancia más común de este suceso es cuando te encuentras tratando de probar la proverbial "clase de dios". Es un tipo especial de problema en sí mismo, pero sufre el mismo problema básico con la necesidad de conocer detalles íntimos de un procedimiento, pero eso está saliendo del tema.

En este ejemplo específico, hemos asignado efectivamente la responsabilidad de inicializar completamente el objeto Bar al constructor de la clase FooBar. En la programación orientada a objetos, uno de los principales núcleos es que el constructor es "sagrado" y debe protegerse contra datos no válidos que invalidarían su propio estado interno y lo dejarían preparado para fallar en otro lugar aguas abajo (en lo que podría ser muy profundo). tubería.)

Hemos fallado en hacer eso aquí al permitir que el objeto FooBar acepte una barra que no está lista en el momento en que se construye el FooBar, y lo hemos compensado mediante una especie de "pirateo" del objeto FooBar para resolver los problemas por sí mismo manos.

Este es el resultado de una falla en adherirse a otra unidad de programación orientada a objetos (en el caso de Bar), que es que el estado de un objeto debe estar completamente inicializado y listo para manejar cualquier llamada entrante a sus miembros públicos inmediatamente después de la creación. Ahora, esto no significa inmediatamente después de que se llame al constructor en todas las instancias. Cuando tiene un objeto que tiene muchos escenarios de construcción complejos, es mejor exponer a los setters a sus miembros opcionales a un objeto que se implemente de acuerdo con un patrón de diseño de creación (Factory, Builder, etc.) en cualquiera de En los últimos casos, estaría empujando la inicialización del objeto de destino a otro gráfico de objetos cuyo único propósito es dirigir el tráfico para llevarlo a un punto donde tenga una instancia válida de lo que está solicitando, y el producto no debe ser considerado "listo" hasta después de que este objeto de creación lo haya servido.

En su ejemplo, la propiedad "status" de la barra no parece estar en un estado válido en el que un FooBar pueda aceptarla, por lo que FooBar hace algo para corregir ese problema.

El segundo problema que veo es que parece que está intentando probar su código en lugar de practicar el desarrollo basado en pruebas. Esta es definitivamente mi propia opinión en este momento; pero, este tipo de prueba es realmente un antipatrón. Lo que termina haciendo es caer en la trampa de darse cuenta de que tiene problemas de diseño básicos que impiden que su código sea verificable después del hecho, en lugar de escribir las pruebas que necesita y posteriormente programarlas para las pruebas. De cualquier forma que encuentre el problema, aún debería terminar con la misma cantidad de pruebas y líneas de código si realmente hubiera logrado una implementación SÓLIDA. Entonces, ¿por qué tratar de aplicar ingeniería inversa en código comprobable cuando puede abordar el asunto al comienzo de sus esfuerzos de desarrollo?

Si lo hubiera hecho, se habría dado cuenta mucho antes de que tendría que escribir un código bastante asqueroso para probar su diseño y habría tenido la oportunidad desde el principio de realinear su enfoque cambiando el comportamiento a implementaciones que son fácilmente comprobables


Puedes llamar a métodos privados. Si encontró el siguiente error:

class OverlyComplicatedCalculator { public add(...numbers: number[]): number { return this.calculate((a, b) => a + b, numbers); } // can''t be used or tested via ".calculate()", but it is still part of your public API! protected calculate(operation, operands) { let result = operands[0]; for (let i = 1; i < operands.length; operands++) { result = operation(result, operands[i]); } return result; } }

solo usa // @ts-ignore :

it(''should be extensible via calculate()'', () => { class TestCalculator extends OverlyComplicatedCalculator { public testWithArrays(array: any[]): any[] { const concat = (a, b) => [].concat(a, b); // tests the protected method return this.calculate(concat, array); } } let testCalc = new TestCalculator(); let result = testCalc.testWithArrays([1, ''two'', 3]); expect(result).toEqual([1, ''two'', 3]); });