Pour les futurs étudiants du cours «JavaScript QA Engineer» et toutes les personnes intéressées par le sujet de l'automatisation des tests, nous avons préparé la traduction d'un article utile.
Nous vous invitons également à participer au webinaire ouvert sur le thème "Ce qu'un testeur doit savoir sur JS" . Dans la leçon, les participants, avec un expert, examineront les fonctionnalités de JS qui doivent être gardées à l'esprit lors de la rédaction des tests.
Les tests unitaires sont excellents ... quand ils fonctionnent de manière fiable! En fait, il y a un vieil adage selon lequel «un mauvais test est pire que pas de test du tout». Je peux confirmer que les semaines passées à courir après un test "faux négatif" accidentel ne sont pas efficaces. Au lieu de cela, ce temps pourrait être utilisé pour écrire du code de travail pour aider l'utilisateur.
: .
, , , .
,
( )
.
?
— , . . , « », "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);
};
, , .
, .