2017-02-21 47 views
5

Dołączyłem do zespołu opracowującego aplikację Angular2, która wymaga wszystkich testów jednostkowych, które należy wykonać w ramach Jasmine. Zastanawiam się, czy istnieje narzędzie zdolne do generowania plików spec dla każdej klasy (rodzaj kodu płyty kotła) poprzez umieszczanie przypadków testowych w oparciu o dostępne metody i/lub na podstawie atrybutów takich jak * ng-If w szablonach. Oto przykład składnika a.component.jsJak wygenerować testy jednostkowe dla istniejącej aplikacji Angular2 z Jasmine Karma

import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; 
import {Http} from '@angular/http'; 


@Component({ 
    selector: 'a-component', 
    template : ` 
    <div *ng-If="model"> 
     <a-child-component [model]="model"> 
     </a-child-component> 
    </div>` 
}) 

export class AComponent implements OnInit { 
    @Input() anInput; 
    ngOnInit() {   
     if(this.anInput){ 
      this.model = anInput; 
     } 
    } 
    constructor(@Inject(Http) http){ 
     this.restAPI = http;  
    } 

    methodOne(arg1,arg2){ 
     //do something 
    } 

    methodTwo(arg1,arg2){ 
     //do something 
    } 

    //... 
} 

i generuje plik spec: a.componenet.spec.js

import { beforeEach,beforeEachProviders,describe,expect,it,injectAsync } from 'angular2/testing'; 
import { setBaseTestProviders } from 'angular2/testing'; 
import { TEST_BROWSER_PLATFORM_PROVIDERS,TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser'; 
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); 
import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; 
import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; 
import { MockComponent } from 'ng2-mock-component'; 
import { async } from '@angular/core/testing'; 
import { Http } from '@angular/http'; 
import { HttpMock } from '../mocks/http.mock'; 
import { AComponent } from './a.component'; 

let model = {"propOne":[],"propTwo":"valueTwo"}; 

describe('AComponent',() => { 
    let fixture; 

    beforeEach(() => { 
    TestBed.configureTestingModule({ 
     declarations: [ 
      AComponent, 
      MockComponent({ 
       selector: 'a-child-component', 
       template:'Hello Dad!' 
       ,inputs: ['model'] 
      }) 
     ], 
     providers: [{ provide: Http, useClass: HttpMock }] 
    }); 
    fixture = TestBed.createComponent(AComponent); 
    fixture.componentInstance.anInput= model;  
    }); 

    it('should create the component',() => { 
    // 
    }); 
    it('should test methodOne',() => { 
    // 
    }); 
    it('should test methodTwo',() => { 
    // 
    }); 
    it('should generate the child component when model is populated',() => { 
    // 
    }); 
) 

Odpowiedz

1

Zostało trochę czasu odkąd opublikował to pytanie. Opracowałem wizualne rozszerzenie kodu, aby pomóc w tym zadaniu, które chcę udostępnić Tobie. Celem tego rozszerzenia jest nie tylko utworzenie pliku spec, ale także generowanie kodu płyty kotła dla wszystkich przypadków testowych, które należy zapisać. Tworzy również Mocks i zastrzyki, które pozwolą Ci działać szybciej. dodaje on przypadek testowy, który zakończy się niepowodzeniem, jeśli nie wdrożyłeś wszystkich testów. Możesz go usunąć, jeśli nie spełnia Twoich wymagań. Wykonano to w przypadku projektu Angular2 ES6, ale można go zaktualizować do maszynopisu, jak chcesz:

// opis: To rozszerzenie utworzy plik spec dla danego pliku js. // jeśli plik JS jest angular2 componenet, to będzie wtedy wyglądać na szablonie html i utworzyć plik spec zawierający Mock klasy componenet dla każdego dziecka zawarte w html

var vscode = require('vscode'); 
var fs = require("fs"); 
var path = require("path"); 

// this method is called when your extension is activated 
// your extension is activated the very first time the command is executed 
function activate(context) { 
    var disposable = vscode.commands.registerCommand('extension.unitTestMe', function() { 
     // The code you place here will be executed every time your command is executed 
     var htmlTags = ['h1','h2','h3','h4','h5','a','abbr','acronym','address','applet','area','article','aside','audio','b','base','basefont','bdi','bdo','bgsound','big','blink','blockquote','body','br','button','canvas','caption','center','cite','code','col','colgroup','command','content','data','datalist','dd','del','details','dfn','dialog','dir','div','dl','dt','element','em','embed','fieldset','figcaption','figure','font','footer','form','frame','frameset','head','header','hgroup','hr','html','i','iframe','image','img','input','ins','isindex','kbd','keygen','label','legend','li','link','listing','main','map','mark','marquee','menu','menuitem','meta','meter','multicol','nav','nobr','noembed','noframes','noscript','object','ol','optgroup','option','output','p','param','picture','plaintext','pre','progress','q','rp','rt','rtc','ruby','s','samp','script','section','select','shadow','slot','small','source','spacer','span','strike','strong','style','sub','summary','sup','table','tbody','td','template','textarea','tfoot','th','thead','time','title','tr','track','tt','u','ul','var','video','wbr']; 
     var filePath; 
     var fileName; 
     if(vscode.window.activeTextEditor){ 
      filePath = vscode.window.activeTextEditor.document.fileName; 
      fileName = path.basename(filePath); 
      if(fileName.lastIndexOf('.spec.') > -1 || fileName.lastIndexOf('.js') === -1 || fileName.substring(fileName.lastIndexOf('.js'),fileName.length) !== '.js'){ 
       vscode.window.showErrorMessage('Please call this extension on a Javascript file'); 
      }else{ 
       var splitedName = fileName.split('.'); 
       splitedName.pop(); 
       var capitalizedNames = []; 
       splitedName.forEach(e => { 
        capitalizedNames.push(e.replace(e[0],e[0].toUpperCase())); 
       }); 
       var className = capitalizedNames.join(''); 

       // ask for filename 
       // var inputOptions = { 
       //  prompt: "Please enter the name of the class you want to create a unit test for", 
       //  value: className 
       // }; 
       // vscode.window.showInputBox(inputOptions).then(className => { 
       let pathToTemplate; 
       let worspacePath = vscode.workspace.rootPath; 
       let fileContents = fs.readFileSync(filePath); 
       let importFilePath = filePath.substring(filePath.lastIndexOf('\\')+1,filePath.lastIndexOf('.js')); 
       let fileContentString = fileContents.toString(); 
       let currentFileLevel = (filePath.substring(worspacePath.length,filePath.lenght).match(new RegExp("\\\\", "g")) || []).length; 
       let htmlFile; 
       if(fileContentString.indexOf('@Component({') > 0){ 
        pathToTemplate = worspacePath + "\\unit-test-templates\\component.txt"; 
        htmlFile = filePath.replace('.js','.html'); 
       }else if(fileContentString.indexOf('@Injectable()') > 0){ 
        pathToTemplate = worspacePath + "\\unit-test-templates\\injectableObject.txt"; 
       } 
       let fileTemplatebits = fs.readFileSync(pathToTemplate); 
       let fileTemplate = fileTemplatebits.toString(); 
       let level0,level1; 
       switch(currentFileLevel){ 
        case 1: 
         level0 = '.'; 
         level1 = './client'; 
        break; 
        case 2: 
         level0 = '..'; 
         level1 = '.'; 
        break; 
        case 3: 
         level0 = '../..'; 
         level1 = '..'; 
        break; 
       } 

       fileTemplate = fileTemplate.replace(/(ComponentName)/g,className).replace(/(pathtocomponent)/g,importFilePath); 
       //fileTemplate = fileTemplate.replace(/(pathtocomponent)/g,importFilePath); 
       //let templateFile = path.join(templatesManager.getTemplatesDir(), path.basename(filePath)); 
       let templateFile = filePath.replace('.js','.spec.js'); 
       if(htmlFile){ 
        let htmlTemplatebits = fs.readFileSync(htmlFile); 
        let htmlTemplate = htmlTemplatebits.toString(); 
        let componentsUsed = htmlTemplate.match(/(<[a-z0-9]+)(-[a-z]+){0,4}/g) || [];//This will retrieve the list of html tags in the html template of the component. 
        let inputs = htmlTemplate.match(/\[([a-zA-Z0-9]+)\]/g) || [];//This will retrieve the list of Input() variables of child Components 
        for(var q=0;q<inputs.length;q++){ 
         inputs[q] = inputs[q].substring(1,inputs[q].length -1); 
        } 
        if(componentsUsed && componentsUsed.length){ 
         for(var k=0;k<componentsUsed.length;k++){ 
          componentsUsed[k] = componentsUsed[k].replace('<',''); 
         } 
         componentsUsed = componentsUsed.filter(e => htmlTags.indexOf(e) == -1); 
         if(componentsUsed.length){ 
          componentsUsed = componentsUsed.filter((item, pos,self) =>{ 
           return self.indexOf(item) == pos;//remove duplicate 
          }); 
          let MockNames = []; 
          componentsUsed.forEach(e => { 
           var splitedTagNames = e.split('-'); 
           if(splitedTagNames && splitedTagNames.length > 1){ 
            var capitalizedTagNames = []; 
            splitedTagNames.forEach(f => { 
             capitalizedTagNames.push(f.replace(f[0],f[0].toUpperCase())); 
            }); 
            MockNames.push('Mock' + capitalizedTagNames.join('')); 
           }else{ 
            MockNames.push('Mock' + e.replace(e[0],e[0].toUpperCase())); 
           } 
          }) 
          let MockDeclarationTemplatebits = fs.readFileSync(worspacePath + "\\unit-test-templates\\mockInportTemplace.txt"); 
          let MockDeclarationTemplate = MockDeclarationTemplatebits.toString(); 
          let inputList = ''; 
          if(inputs && inputs.length){ 
           inputs = inputs.filter(put => put !== 'hidden');      
           inputs = inputs.filter((item, pos,self) =>{ 
            return self.indexOf(item) == pos;//remove duplicate 
           }); 
           inputs.forEach(put =>{ 
            inputList += '@Input() ' + put + ';\r\n\t'  
           }); 
          } 
          let declarations = ''; 
          for(var i=0;i < componentsUsed.length; i++){ 
           if(i != 0){ 
            declarations += '\r\n'; 
           } 
           declarations += MockDeclarationTemplate.replace('SELECTORPLACEHOLDER',componentsUsed[i]).replace('MOCKNAMEPLACEHOLDER',MockNames[i]).replace('HTMLTEMPLATEPLACEHOLDER',MockNames[i]).replace('ALLINPUTSPLACEHOLDER',inputList); 
          } 
          fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',declarations); 
          fileTemplate = fileTemplate.replace('ComponentsToImportPlaceHolder',MockNames.join(',')); 
         }else{ 
          fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',''); 
          fileTemplate = fileTemplate.replace(',ComponentsToImportPlaceHolder',''); 
         } 

        }else{ 
         fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',''); 
         fileTemplate = fileTemplate.replace(',ComponentsToImportPlaceHolder',''); 
        }   
       }else{ 
        fileTemplate = fileTemplate.replace('MockComponentsPlaceHolder',''); 
        fileTemplate = fileTemplate.replace(',ComponentsToImportPlaceHolder','');   
       } 
       fileTemplate = fileTemplate.replace(/(LEVEL0)/g,level0).replace(/(LEVEL1)/g,level1); 
       if(fs.existsSync(templateFile)){ 
        vscode.window.showErrorMessage('A spec file with the same name already exists. Please rename it or delete first.'); 
       }else{ 
        fs.writeFile(templateFile, fileTemplate, function (err) { 
         if (err) { 
           vscode.window.showErrorMessage(err.message); 
          } else { 
           vscode.window.showInformationMessage("The spec file has been created next to the current file"); 
          } 
        }); 
       } 
      } 
     }else{ 
      vscode.window.showErrorMessage('Please call this extension on a Javascript file'); 
     } 
    }); 
    context.subscriptions.push(disposable); 
} 
exports.activate = activate; 

// this method is called when your extension is deactivated 
function deactivate() { 
} 
exports.deactivate = deactivate; 

Do tego do pracy, potrzebujesz 2 plików szablonów, jednego dla komponentów i jednego dla usług wstrzykiwania. Możesz dodać rur i innego rodzaju TS klas

component.txt Szablon:

/** 
* Created by mxtano on 10/02/2017. 
*/ 
import { beforeEach,beforeEachProviders,describe,expect,it,injectAsync } from 'angular2/testing'; 
import { setBaseTestProviders } from 'angular2/testing'; 
import { TEST_BROWSER_PLATFORM_PROVIDERS,TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser'; 
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); 
import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; 
import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; 
import { async } from '@angular/core/testing'; 
import { YourService} from 'LEVEL1/service/your.service'; 
import { YourServiceMock } from 'LEVEL0/test-mock-class/your.service.mock'; 
import { ApiMockDataIfNeeded } from 'LEVEL0/test-mock-class/apiMockData'; 
import { FormBuilderMock } from 'LEVEL0/test-mock-class/form.builder.mock'; 
import { MockNoteEventController } from 'LEVEL0/test-mock-class/note.event.controller.mock';  
import { ComponentName } from './pathtocomponent'; 


MockComponentsPlaceHolder 

describe('ComponentName',() => { 
    let fixture; 
    let ListOfFunctionsTested = []; 
    beforeEach(() => { 
    TestBed.configureTestingModule({ 
     declarations: [ 
      ComponentName 
      ,ComponentsToImportPlaceHolder 
     ], 
     providers: [ 
      //Use the appropriate class to be injected 
      //{provide: YourService, useClass: YourServiceMock}     
      ] 
    }); 
    fixture = TestBed.createComponent(ComponentName);  
    //Insert initialising variables here if any (such as as link or model...) 
    }); 

    //This following test will generate in the console a unit test for each function of this class except for constructor() and ngOnInit() 
    //Run this test only to generate the cases to be tested. 
    it('should list all methods', async(() => { 
     //console.log(fixture.componentInstance); 
     let array = Object.getOwnPropertyNames(fixture.componentInstance.__proto__); 
     let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 
     let ARGUMENT_NAMES = /([^\s,]+)/g;   
     array.forEach(item => { 
       if(typeof(fixture.componentInstance.__proto__[item]) === 'function' && item !== 'constructor' && item !== 'ngOnInit'){ 
        var fnStr = fixture.componentInstance.__proto__[item].toString().replace(STRIP_COMMENTS, ''); 
        var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); 
        if(result === null) 
         result = []; 
        var fn_arguments = "'"+result.toString().replace(/,/g,"','")+"'"; 
        console.log("it('Should test "+item+"',()=>{\r\n\tListOfFunctionsTested.push('"+item+"');\r\n\t//expect(fixture.componentInstance."+item+"("+fn_arguments+")).toBe('SomeValue');\r\n});"); 
       } 
     }); 
     expect(1).toBe(1); 
    })); 


    //This test will make sure that all methods of this class have at leaset one test case 
    it('Should make sure we tested all methods of this class',() =>{ 
     let fn_array = Object.getOwnPropertyNames(fixture.componentInstance.__proto__); 
     fn_array.forEach(fn=>{ 
      if(typeof(fixture.componentInstance.__proto__[fn]) === 'function' && fn !== 'constructor' && fn !== 'ngOnInit'){ 
       if(ListOfFunctionsTested.indexOf(fn)=== -1){ 
        //this test will fail but will display which method is missing on the test cases. 
        expect(fn).toBe('part of the tests. Please add ',fn,' to your tests'); 
       } 
      } 
     }); 
    }) 

}); 

Oto szablon dla Mock Components odwołuje rozszerzeniem mockInportTemplace.txt:

@Component({ 
    selector: 'SELECTORPLACEHOLDER', 
    template: 'HTMLTEMPLATEPLACEHOLDER' 
}) 
export class MOCKNAMEPLACEHOLDER { 
    //Add @Input() variables here if necessary 
    ALLINPUTSPLACEHOLDER 
} 

Oto szablon z odniesieniem do rozszerzenia do wstrzykiwania:

import { beforeEach,beforeEachProviders,describe,expect,it,injectAsync } from 'angular2/testing'; 
import { setBaseTestProviders } from 'angular2/testing'; 
import { TEST_BROWSER_PLATFORM_PROVIDERS,TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser'; 
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); 
import { Component, Input, Output, Inject, OnChanges, EventEmitter, OnInit } from '@angular/core'; 
import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; 
import { async } from '@angular/core/testing'; 
import { RestAPIMock } from 'LEVEL0/test-mock-class/rest.factory.mock'; 
import {Http} from '@angular/http'; 
//import { Subject } from 'rxjs/Subject'; 
import { ComponentName } from './pathtocomponent'; 
import { ApiMockData } from 'LEVEL0/test-mock-class/ApiMockData'; 

describe('ComponentName',() => { 
    let objInstance; 
    let service; 
    let backend; 
    let ListOfFunctionsTested = []; 
    let singleResponse = { "properties": {"id": 16, "partyTypeId": 2, "doNotContact": false, "doNotContactReasonId": null, "salutationId": 1}}; 
    let restResponse = [singleResponse];  

    beforeEach(() => { 
     TestBed.configureTestingModule({ 
      providers: [ 
       ComponentName 
       //Here you declare and replace an injected class by its mock object 
       //,{ provide: Http, useClass: RestAPIMock } 
      ] 
     }); 
    }); 


    beforeEach(inject([ComponentName 
         //Here you can add the name of the class that your object receives as Injection 
         // , InjectedClass 
         ], (objInstanceParam 
         // , injectedObject 
         ) => { 
     objInstance = objInstanceParam; 
     //objInstance.injectedStuff = injectedObject; 
    })); 

    it('should generate test cases for all methods available',() => { 
     let array = Object.getOwnPropertyNames(objInstance.__proto__); 
     let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 
     let ARGUMENT_NAMES = /([^\s,]+)/g;   
     array.forEach(item => { 
       if(typeof(objInstance.__proto__[item]) === 'function' && item !== 'constructor' && item !== 'ngOnInit'){ 
        var fnStr = objInstance.__proto__[item].toString().replace(STRIP_COMMENTS, ''); 
        var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); 
        if(result === null) 
         result = []; 
        var fn_arguments = "'"+result.toString().replace(/,/g,"','")+"'"; 
        console.log("it('Should test "+item+"',()=>{\r\n\tListOfFunctionsTested.push('"+item+"');\r\n\t//expect(objInstance."+item+"("+fn_arguments+")).toBe('SomeValue');\r\n});"); 
       } 
     }); 
     expect(1).toBe(1); 
    }); 

    //This test will make sure that all methods of this class have at leaset one test case 
    it('Should make sure we tested all methods of this class',() =>{ 
     let fn_array = Object.getOwnPropertyNames(objInstance.__proto__); 
     fn_array.forEach(fn=>{ 
      if(typeof(objInstance.__proto__[fn]) === 'function' && fn !== 'constructor' && fn !== 'ngOnInit'){ 
       if(ListOfFunctionsTested.indexOf(fn)=== -1){ 
        //this test will fail but will display which method is missing on the test cases. 
        expect(fn).toBe('part of the tests. Please add ',fn,' to your tests'); 
       } 
      } 
     }); 
    }) 


}); 

The thre Pliki e muszą znajdować się wewnątrz projektu pod src w folderze, do którego odnoszą się szablony testów jednostkowych.

Po utworzeniu tego rozszerzenia w wizualnym kodzie, przejdź do pliku JS, dla którego chcesz wygenerować test jednostkowy, naciśnij klawisz F1 i wpisz UniteTestMe. upewnij się, że nie istnieje już plik spec.

+0

Czy napisałeś ten projekt rozszerzenia? Próbowałem, ale zatrzymuje się z komunikatem, że nie powiodło się i bez informacji debugowania, –

+0

Nie zrobiłem więcej rozwoju dla tego rozszerzenia. Polecam, abyś wszedł do kodu rozszerzenia i zobaczył, gdzie się on nie powiódł. zobacz ten post: https://code.visualstudio.com/docs/extensions/debugging-extensions – Mehdi

+0

Naprawiłem problemy, miałem problemy z plikami ts i teraz chcę dodać do tego generację Service Mocks. –