Para los futuros estudiantes del curso "JavaScript QA Engineer" y todos los interesados en el tema de la automatización de pruebas, preparamos una traducción de un artículo útil.
También lo invitamos a participar en el seminario web abierto sobre el tema "Lo que un evaluador debe saber sobre JS" . En la lección, los participantes, junto con un experto, considerarán las características de JS que deben tenerse en cuenta al escribir pruebas.
Las pruebas unitarias son geniales ... ¡cuando funcionan de manera confiable! De hecho, hay un viejo adagio que dice que "una mala prueba es peor que ninguna prueba". Puedo confirmar que las semanas pasadas persiguiendo una prueba de "falso negativo" accidental no son efectivas. En cambio, este tiempo podría usarse para escribir código de trabajo para ayudar al usuario.
: .
, , , .
,
( )
.
?
— , . . , « », "Gang Of Four's Design Pattern" . .
, , .
:
interface ISomeObj {
percentage: string;
}
export const makeSomeObj = () => {
return {
percentage: Math.random()
};
}
, , .
, , .
,
. , - . JSON-. Cypress ( ), JSON . , . JSON .
, . , , , .
// This file is "src/pages/newYorkInfo.tsx"
import * as React from 'react';
interface IUser {
state: string;
address: string;
isAdmin: boolean;
deleted: boolean | undefined;
}
export const NewYorkUserPage: React.FunctionComponent<{ user: IUser }> = props => {
if (props.user.state === 'NY' && !props.user.deleted) {
const welcomeMessage = `Welcome`;
return <h1 id="ny-dashboard">{welcomeMessage}</h1>;
} else {
return <div>ACCESS DENIED</div>;
}
};
, JSON .
// fixtures/user.json
{
state: 'NY',
isAdmin: true,
address: '55 Main St',
}
. , - psuedo- Cypress, , , .
// When the UI calls the user endpoint, return the JSON as the mocked return value
cy.route('GET', '/user/**', 'fixture:user.json');
cy.visit('/dashboard');
cy.get('#ny-dashboard').should('exist')
, , . ?
— , JSON-
JSON- ? , , (). , JSON-. 52 JSON-, . , , 104 . !
. , Product Owner : « , ».
, name
.
// This file is "src/pages/newYorkInfo.tsx"
import * as React from 'react';
interface IUser {
name: string;
state: string;
address: string;
isAdmin: boolean;
deleted: boolean | undefined;
}
export const NewYorkUserPage: React.FunctionComponent<{ user: IUser }> = props => {
if (props.user.state === 'NY' && !props.user.deleted) {
const welcomeMessage = `Welcome ${props.user.name.toLowerCase()}!`;
return <h1 id="ny-dashboard">{welcomeMessage}</h1>;
} else {
return <div>ACCESS DENIED</div>;
}
};
, , JSON . JSON name
, :
Uncaught TypeError: Cannot read property 'toLowerCase' of undefined
name 52 JSON . Typescript.
: TypeScript
JSON .ts , Typescript :
// this file is "testData/users"
import {IUser} from 'src/pages/newYorkInfo';
// Property 'name' is missing in type '{ state: string; isAdmin: true; address: string; deleted: false; }' but required in type 'IUser'.ts(2741)
export const generalUser: IUser = {
state: 'NY',
isAdmin: true,
address: '55 Main St',
deleted: false,
};
, .
import { generalUser } from 'testData/users';
// When the UI calls the user endpoint, return the JSON as the mocked return value
cy.route('GET', '/user/**', generalUser);
cy.visit('/dashboard');
cy.get('#ny-dashboard').should('exist')
Typescript! , name: 'Bob Smith'
GeneralUser:
, , , !
, . , .
, , , -. , , , , . deleted: false
generalUser
.
! , . .
( ) , . , ( ) deletedUser
, 1 . - — 5000 .
, .
// this file is "testData/users"
import {IUser} from 'src/pages/newYorkInfo';
export const nonAdminUser: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: false,
address: '55 Main St',
deleted: false,
};
export const adminUser: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: true,
address: '55 Main St',
deleted: false,
};
export const deletedAdminUser: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: true,
address: '55 Main St',
deleted: true,
};
export const deletedNonAdmin: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: false,
address: '55 Main St',
deleted: true,
};
// and on and on and on again...
.
:
? !
// src/factories/user
import faker from 'faker';
import {IUser} from 'src/pages/newYorkInfo';
export const makeFakeUser = (): IUser => {
return {
name: faker.name.firstName() + ' ' + faker.name.lastName(),
state: faker.address.stateAbbr(),
isAdmin: faker.random.boolean(),
address: faker.address.streetAddress(),
deleted: faker.random.boolean(),
}
}
makeFakeUser()
, .
, , , , . IUser, .
. , - . , .
import { makeFakeUser } from 'src/factories/user';
import {IUser} from 'src/pages/newYorkInfo';
// Arrange
const randomUser = makeFakeUser();
const deletedUser: IUser = { ...randomUser, ...{
deleted: true
};
cy.route('GET', '/user/**', deletedUser);
// Act
cy.visit('/dashboard');
// Assert
cy.find('ACCESS DENIED').should('exist')
, , . , , , API , "Access Denied"
.
, .
: mergePartially
spread
, . , , :
interface IUser {
userName: string;
preferences: {
lastUpdated?: Date;
favoriteColor?: string;
backupContact?: string;
mailingAddress: {
street: string;
city: string;
state: string;
zipCode: string;
}
}
}
, .
, , DRY. , , , "Main Street".
const userOnMainSt = makeFakeUser({
preferences: {
mailingAddress: {
street: 'Main Street'
}
}
});
, , , 7 . - . .
makeFakeUser
?
, mergePartially ( : mergePartially
).
const makeFakeUser = (override?: NestedPartial<IDeepObj>): IDeepObj => {
const seed: IDeepObj = {
userName: 'Bob Smith',
preferences: {
mailingAddress: {
street: faker.address.streetAddress(),
city: faker.address.city(),
state: faker.address.stateAbbr(),
zipCode: faker.address.zipCode(),
},
},
};
return mergePartially.deep(seed, override);
};
, , .
, .