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.
Czy napisałeś ten projekt rozszerzenia? Próbowałem, ale zatrzymuje się z komunikatem, że nie powiodło się i bez informacji debugowania, –
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
Naprawiłem problemy, miałem problemy z plikami ts i teraz chcę dodać do tego generację Service Mocks. –