рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдФрд░ Node.js рдореЗрдВ рд╕рд░реНрд╡рд╢реНрд░реЗрд╖реНрда рдкрд░реАрдХреНрд╖рдг рддрдХрдиреАрдХ


рдпрд╣ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдФрд░ Node.js. рдореЗрдВ рд╡рд┐рд╢реНрд╡рд╕рдиреАрдпрддрд╛ рдкреНрд░рджрд╛рди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡реНрдпрд╛рдкрдХ рдорд╛рд░реНрдЧрджрд░реНрд╢рд┐рдХрд╛ рд╣реИред рдпрд╣рд╛рдВ рджрд░реНрдЬрдиреЛрдВ рдмреЗрд╣рддрд░реАрди рдкреЛрд╕реНрдЯ, рдХрд┐рддрд╛рдмреЗрдВ рдФрд░ рдЙрдкрдХрд░рдг рдПрдХрддреНрд░ рдХрд┐рдП рдЧрдП рд╣реИрдВред

рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЖрдо рддреМрд░ рдкрд░ рд╕реНрд╡реАрдХрд╛рд░ рдХрд┐рдП рдЧрдП рдкрд░реАрдХреНрд╖рдг рддрд░реАрдХреЛрдВ рд╕реЗ рдирд┐рдкрдЯреЗрдВ рдЬреЛ рдХрд┐рд╕реА рднреА рдЖрд╡реЗрджрди рдХреЛ рд░реЗрдЦрд╛рдВрдХрд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдФрд░ рдлрд┐рд░ рдЖрдк рдХреЗ рд▓рд┐рдП рдмреНрдпрд╛рдЬ рдХреЗ рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рддрд▓реНрд▓реАрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ: рдлреНрд░рдВрдЯрдПрдВрдб рдФрд░ рдЗрдВрдЯрд░рдлреЗрд╕, рдмреИрдХрдПрдВрдб, рд╕реАрдЖрдИ рдпрд╛ рдЙрдкрд░реЛрдХреНрдд рд╕рднреАред

рд╕рд╛рдордЧреНрд░реА



рдЦрдВрдб 0. рд╕реНрд╡рд░реНрдг рдирд┐рдпрдо


0. рдЧреЛрд▓реНрдбрди рд░реВрд▓: рд╕реНрдЯрд┐рдХ рдЯреВ рд▓реАрди рдЯреЗрд╕реНрдЯрд┐рдВрдЧ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдкрд░реАрдХреНрд╖рдг рдХреЛрдб рдСрдкрд░реЗрд╢рди рдореЗрдВ рдЬрд╛рдиреЗ рд╕реЗ рдЕрд▓рдЧ рд╣реИред рдпрд╣ рд╕рдВрднрд╡ рдХреЗ рд░реВрдк рдореЗрдВ рд╕рд░рд▓, рд▓рдШреБ, рдЕрдореВрд░реНрдд рд╕реЗ рдореБрдХреНрдд, рдПрдХрд▓, рдХрд╛рдо рдФрд░ рдорд┐рддрд╡реНрдпрдпреА рдореЗрдВ рдЕрджреНрднреБрдд рдмрдирд╛рдУред рдПрдХ рдЕрдиреНрдп рд╡реНрдпрдХреНрддрд┐ рдХреЛ рдкрд░реАрдХреНрд╖рдг рдХреЛ рджреЗрдЦрдирд╛ рдЪрд╛рд╣рд┐рдП рдФрд░ рддреБрд░рдВрдд рд╕рдордЭ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд┐ рд╡рд╣ рдХреНрдпрд╛ рдХрд░ рд░рд╣рд╛ рд╣реИред

рд╣рдорд╛рд░реЗ рдкреНрд░рдореБрдЦ рдЙрддреНрдкрд╛рджрди рдХреЛрдб рдХреЗ рд╕рд╛рде рд╡реНрдпрд╕реНрдд рд╣реИрдВ, рдЙрдирдХреЗ рдкрд╛рд╕ рдЕрддрд┐рд░рд┐рдХреНрдд рдЬрдЯрд┐рд▓рддрд╛ рдХреЗ рд▓рд┐рдП рдЦрд╛рд▓реА рд╕реНрдерд╛рди рдирд╣реАрдВ рд╣реИред рдпрджрд┐ рд╣рдо рдЬрдЯрд┐рд▓ рдХреЛрдб рдХреЗ рдПрдХ рдирдП рд╣рд┐рд╕реНрд╕реЗ рдХреЛ рдЕрдкрдиреЗ рдЦрд░рд╛рдм рджрд┐рдорд╛рдЧ рдореЗрдВ рдврд╛рд▓рддреЗ рд╣реИрдВ, рддреЛ рдпрд╣ рдХрд╛рд░реНрдп рдкрд░ рдкреВрд░реА рдЯреАрдо рдХреЗ рдХрд╛рдо рдХреЛ рдзреАрдорд╛ рдХрд░ рджреЗрдЧрд╛, рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рд╣рдо рдкрд░реАрдХреНрд╖рдг рдХрд░ рд░рд╣реЗ рд╣реИрдВред рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдЗрд╕ рд╡рдЬрд╣ рд╕реЗ, рдХрдИ рдЯреАрдореЗрдВ рдХреЗрд╡рд▓ рдкрд░реАрдХреНрд╖рдг рд╕реЗ рджреВрд░ рд╣реЛ рдЬрд╛рддреА рд╣реИрдВред

рдЯреЗрд╕реНрдЯ - рдпрд╣ рдПрдХ рджреЛрд╕реНрддрд╛рдирд╛ рдФрд░ рдореБрд╕реНрдХреБрд░рд╛рддрд╛ рд╣реБрдЖ рд╕рд╣рд╛рдпрдХ рдкрд╛рдиреЗ рдХрд╛ рдПрдХ рдЕрд╡рд╕рд░ рд╣реИ, рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдирд╛ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╣реИ рдФрд░ рдЬреЛ рдЫреЛрдЯреЗ рдирд┐рд╡реЗрд╢реЛрдВ рдкрд░ рднрд╛рд░реА рд░рд┐рдЯрд░реНрди рджреЗрддрд╛ рд╣реИред рд╡реИрдЬреНрдЮрд╛рдирд┐рдХреЛрдВ рдХрд╛ рдорд╛рдирдирд╛ тАЛтАЛрд╣реИ рдХрд┐ рд╣рдорд╛рд░реЗ рдорд╕реНрддрд┐рд╖реНрдХ рдореЗрдВ рджреЛ рдкреНрд░рдгрд╛рд▓рд┐рдпрд╛рдВ рд╣реИрдВ: рдПрдХ рдРрд╕реЗ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП рдЬрд┐рдиреНрд╣реЗрдВ рдкреНрд░рдпрд╛рд╕ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реЛрддреА рд╣реИ, рдЬреИрд╕реЗ рдХрд┐ рдЦрд╛рд▓реА рд╕рдбрд╝рдХ рдкрд░ рд╡рд╛рд╣рди рдЪрд▓рд╛рдирд╛, рдФрд░ рдЬрдЯрд┐рд▓ рд╕рдВрдЪрд╛рд▓рди рдХреЗ рд▓рд┐рдП рджреВрд╕рд░рд╛, рдЬрд╛рдЧрд░реВрдХрддрд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛, рдЬреИрд╕реЗ рдХрд┐ рдЧрдгрд┐рддреАрдп рд╕рдореАрдХрд░рдгреЛрдВ рдХреЛ рд╣рд▓ рдХрд░рдирд╛ред рдкрд╣рд▓реЗ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд▓рд┐рдП рдЕрдкрдиреЗ рдкрд░реАрдХреНрд╖рдг рдмрдирд╛рдПрдВ, рддрд╛рдХрд┐ рдЬрдм рдЖрдк рдХреЛрдб рдХреЛ рджреЗрдЦреЗрдВ рддреЛ рдЖрдкрдХреЛ HTML рдбреЙрдХреНрдпреВрдореЗрдВрдЯ рдХреЛ рдПрдбрд┐рдЯ рдХрд░рдиреЗ рдореЗрдВ рд╕рд░рд▓рддрд╛ рдХреА рдЕрдиреБрднреВрддрд┐ рд╣реЛ, рди рдХрд┐ 2X(17 ├Ч 24) рд╕реЙрд▓реНрдпреВрд╢рди 2X(17 ├Ч 24) ред

рдпрд╣ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рддрд░реАрдХреЛрдВ, рдЙрдкрдХрд░рдгреЛрдВ рдФрд░ рд▓рдХреНрд╖реНрдпреЛрдВ рдХреЗ рд╕рд╛рд╡рдзрд╛рдиреАрдкреВрд░реНрд╡рдХ рдЪрдпрди рджреНрд╡рд╛рд░рд╛ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рддрд╛рдХрд┐ рд╡реЗ рдХрд┐рдлрд╛рдпрддреА рд╣реЛрдВ рдФрд░ рдПрдХ рдмрдбрд╝рд╛ рдЖрд░рдУрдЖрдИ рджреЗрдВред рдХреЗрд╡рд▓ рдЙрддрдирд╛ рд╣реА рдЯреЗрд╕реНрдЯ рдХрд░реЗрдВ, рдЬрд┐рддрдирд╛ рд▓рдЪреАрд▓рд╛ рд╣реЛрдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реЗрдВред рдХрднреА-рдХрднреА рдпрд╣ рдХреБрдЫ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЛ рддреНрдпрд╛рдЧрдиреЗ рдФрд░ рдЧрддрд┐ рдФрд░ рд╕рд╛рджрдЧреА рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реНрд╡рд╕рдиреАрдпрддрд╛ рдХрд╛ рддреНрдпрд╛рдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд╛рдпрдХ рднреА рд╣реИред



рдиреАрдЪреЗ рджреА рдЧрдИ рдЕрдзрд┐рдХрд╛рдВрд╢ рд╕рд┐рдлрд╛рд░рд┐рд╢реЗрдВ рдЗрд╕ рд╕рд┐рджреНрдзрд╛рдВрдд рд╕реЗ рд▓реА рдЧрдИ рд╣реИрдВред
рдХреНрдпрд╛ рдЖрдк рддреИрдпрд╛рд░ рд╣реИрдВ?

рдЦрдВрдб 1. рдкрд░реАрдХреНрд╖рдг рдХрд╛ рдПрдирд╛рдЯреЙрдореА


рез.рез рдкреНрд░рддреНрдпреЗрдХ рдкрд░реАрдХреНрд╖рд╛ рдХреЗ рдирд╛рдо рдореЗрдВ рддреАрди рднрд╛рдЧ рд╣реЛрдиреЗ рдЪрд╛рд╣рд┐рдП


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдкрд░реАрдХреНрд╖рдг рд░рд┐рдкреЛрд░реНрдЯ рдореЗрдВ рдпрд╣ рджрд░реНрд╢рд╛рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд┐ рдХреНрдпрд╛ рдЖрд╡реЗрджрди рдХрд╛ рд╡рд░реНрддрдорд╛рди рд╕рдВрд╢реЛрдзрди рдЙрди рд▓реЛрдЧреЛрдВ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХреЛ рдкреВрд░рд╛ рдХрд░рддрд╛ рд╣реИ рдЬреЛ рдХреЛрдб рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдирд╣реАрдВ рд╣реИрдВ: DevOps рдЗрдВрдЬреАрдирд┐рдпрд░реЛрдВ рдХреА рддреИрдирд╛рддреА рдореЗрдВ рд╢рд╛рдорд┐рд▓ рдкрд░реАрдХреНрд╖рдХ, рд╕рд╛рде рд╣реА рд╕рд╛рде рджреЛ рд╕рд╛рд▓ рдореЗрдВ рдЦреБрдж рдХреЛред рдпрд╣ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рд╣реЛрдЧрд╛ рдпрджрд┐ рдкрд░реАрдХреНрд╖рдг рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХреА рднрд╛рд╖рд╛ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреА рд░рд┐рдкреЛрд░реНрдЯ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдЙрдирдХреЗ рдирд╛рдо рддреАрди рднрд╛рдЧреЛрдВ рд╕реЗ рдорд┐рд▓рдХрд░ рд╣реЛрддреЗ рд╣реИрдВ:

  1. рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХреНрдпрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ? рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, ProductsService.addNewProduct рд╡рд┐рдзрд┐ред
  2. рдХрд┐рди рдкрд░рд┐рд╕реНрдерд┐рддрд┐рдпреЛрдВ рдФрд░ рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рдХреЗ рддрд╣рдд? рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдХреАрдордд рд╡рд┐рдзрд┐ рдХреЗ рд▓рд┐рдП рдкрд╛рд░рд┐рдд рдирд╣реАрдВ рд╣реИред
  3. рдЕрдкреЗрдХреНрд╖рд┐рдд рдкрд░рд┐рдгрд╛рдо рдХреНрдпрд╛ рд╣реИ? рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рдирдпрд╛ рдЙрддреНрдкрд╛рдж рдЕрдиреБрдореЛрджрд┐рдд рдирд╣реАрдВ рд╣реИред

рдЕрдиреНрдпрдерд╛ред рдкрд░рд┐рдирд┐рдпреЛрдЬрди рд╡рд┐рдлрд▓ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ, "рдЙрддреНрдкрд╛рдж рдЬреЛрдбрд╝реЗрдВ" рдирд╛рдордХ рдкрд░реАрдХреНрд╖рдг рд╡рд┐рдлрд▓ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред рдХреНрдпрд╛ рдЖрдк рд╕рдордЭрддреЗ рд╣реИрдВ рдХрд┐ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХреНрдпрд╛ рдЧрд▓рдд рд╣реИ?

рдиреЛрдЯред рдкреНрд░рддреНрдпреЗрдХ рдЕрдзреНрдпрд╛рдп рдореЗрдВ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХреЛрдб рд╣реИ, рдФрд░ рдХрднреА-рдХрднреА рдПрдХ рдЙрджрд╛рд╣рд░рдг рд╣реИред рдмрд┐рдЧрд╛рдбрд╝рдиреЗ рд╡рд╛рд▓реЗ рджреЗрдЦреЗрдВред

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдкрд░реАрдХреНрд╖рдг рдХреЗ рдирд╛рдо рдореЗрдВ рддреАрди рднрд╛рдЧ рд╣реЛрддреЗ рд╣реИрдВред

 //1. unit under test describe('Products Service', function() { describe('Add new product', function() { //2. scenario and 3. expectation it('When no price is specified, then the product status is pending approval', ()=> { const newProduct = new ProductService().add(...); expect(newProduct.status).to.equal('pendingApproval'); }); }); }); 


1.2 рдПрдПрдП рдкреИрдЯрд░реНрди рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреА рд╕рдВрд░рдЪрдирд╛ рдХрд░реЗрдВ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдкреНрд░рддреНрдпреЗрдХ рдкрд░реАрдХреНрд╖рдг рдореЗрдВ рддреАрди рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдЦрдВрдб рд╢рд╛рдорд┐рд▓ рд╣реЛрдиреЗ рдЪрд╛рд╣рд┐рдП: рд╡реНрдпрд╡рд╕реНрдерд╛ (рддреИрдпрд╛рд░реА), рдЕрдзрд┐рдирд┐рдпрдо (рдХреНрд░рд┐рдпрд╛) рдФрд░ рдЕрд╕рд░ (рдкрд░рд┐рдгрд╛рдо)ред рдЗрд╕ рддрд░рд╣ рдХреА рд╕рдВрд░рдЪрдирд╛ рдХрд╛ рдкрд╛рд▓рди рдХрд░рдирд╛ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рддрд╛ рд╣реИ рдХрд┐ рдЖрдкрдХреЗ рдХреЛрдб рдХреЗ рдкрд╛рдардХ рдХреЛ рдкрд░реАрдХреНрд╖рдг рдпреЛрдЬрдирд╛ рдХреЛ рд╕рдордЭрдиреЗ рдХреЗ рд▓рд┐рдП рдорд╕реНрддрд┐рд╖реНрдХ рдкреНрд░реЛрд╕реЗрд╕рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ:

рд╡реНрдпрд╡рд╕реНрдерд╛: рд╕рднреА рдХреЛрдб рдЬреЛ рдкрд░реАрдХреНрд╖рдг рдкрд░рд┐рджреГрд╢реНрдп рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдПрдХ рд░рд╛рдЬреНрдп рдореЗрдВ рд╕рд┐рд╕реНрдЯрдо рд▓рд╛рддрд╛ рд╣реИред рдЗрд╕рдореЗрдВ рдкрд░реАрдХреНрд╖рдг рдбрд┐рдЬрд╛рдЗрдирд░ рдореЗрдВ рдореЙрдбреНрдпреВрд▓ рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдмрдирд╛рдирд╛, рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рд░рд┐рдХреЙрд░реНрдб рдЬреЛрдбрд╝рдирд╛, рдСрдмреНрдЬреЗрдХреНрдЯреНрд╕ рдХреЗ рдмрдЬрд╛рдп рд╕реНрдЯрдмреНрд╕ рдмрдирд╛рдирд╛ рдФрд░ рдХреЛрдИ рдЕрдиреНрдп рдХреЛрдб рд╢рд╛рдорд┐рд▓ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ рдЬреЛ рдЯреЗрд╕реНрдЯ рд░рди рдХреЗ рд▓рд┐рдП рд╕рд┐рд╕реНрдЯрдо рддреИрдпрд╛рд░ рдХрд░рддрд╛ рд╣реИред

рдЕрдзрд┐рдирд┐рдпрдо: рдПрдХ рдкрд░реАрдХреНрд╖рдг рдХреЗ рднрд╛рдЧ рдХреЗ рд░реВрдк рдореЗрдВ рдХреЛрдб рдирд┐рд╖реНрдкрд╛рджрдиред рдЖрдорддреМрд░ рдкрд░ рд╕рд┐рд░реНрдл рдПрдХ рд▓рд╛рдЗрдиред
рдореБрдЦрд░: рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рдкреНрд░рд╛рдкреНрдд рдореВрд▓реНрдп рдЙрдореНрдореАрджреЛрдВ рдкрд░ рдЦрд░рд╛ рдЙрддрд░рддрд╛ рд╣реИред рдЖрдорддреМрд░ рдкрд░ рд╕рд┐рд░реНрдл рдПрдХ рд▓рд╛рдЗрдиред

рдЕрдиреНрдпрдерд╛ред рдЖрдк рди рдХреЗрд╡рд▓ рдореБрдЦреНрдп рдХреЛрдб рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдореЗрдВ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдмрд┐рддрд╛рдПрдВрдЧреЗ, рдмрд▓реНрдХрд┐ рдЖрдкрдХрд╛ рдорд╕реНрддрд┐рд╖реНрдХ рднреА рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдиреМрдХрд░реА рд╕реЗ рдЙрдмрд▓реЗрдЧрд╛ - рдкрд░реАрдХреНрд╖рдг рд╕реЗред

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдПрдПрдП рдкреИрдЯрд░реНрди рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдПрдХ рдкрд░реАрдХреНрд╖рдг рд╕рдВрд░рдЪрд┐рддред

 describe.skip('Customer classifier', () => { test('When customer spent more than 500$, should be classified as premium', () => { //Arrange const customerToClassify = {spent:505, joined: new Date(), id:1} const DBStub = sinon.stub(dataAccess, "getCustomer") .reply({id:1, classification: 'regular'}); //Act const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); //Assert expect(receivedClassification).toMatch('premium'); }); }); 

рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдХреЛрдИ рднреА рдЕрд▓рдЧрд╛рд╡, рдПрдХ рдЯреБрдХрдбрд╝реЗ рдореЗрдВ, рд╡реНрдпрд╛рдЦреНрдпрд╛ рдХрд░рдирд╛ рдЕрдзрд┐рдХ рдХрдард┐рди рд╣реИред

 test('Should be classified as premium', () => { const customerToClassify = {spent:505, joined: new Date(), id:1} const DBStub = sinon.stub(dataAccess, "getCustomer") .reply({id:1, classification: 'regular'}); const receivedClassification = customerClassifier.classifyCustomer(customerToClassify); expect(receivedClassification).toMatch('premium'); }); 


1.3 рдЙрддреНрдкрд╛рдж рдХреА рднрд╛рд╖рд╛ рдореЗрдВ рдЙрдореНрдореАрджреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░реЗрдВ: BDD рдХреА рд╢реИрд▓реА рдореЗрдВ рд╕реНрдерд┐рддрд┐


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдПрдХ рдШреЛрд╖рдгрд╛рддреНрдордХ рд╢реИрд▓реА рдореЗрдВ рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ рдкрд░реАрдХреНрд╖рдг рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдПрдХ рдорд╕реНрддрд┐рд╖реНрдХ рдкреНрд░реЛрд╕реЗрд╕рд░ рдЪрдХреНрд░ рдЦрд░реНрдЪ рдХрд┐рдП рдмрд┐рдирд╛ рд╕рд╛рд░ рдХреЛ рддреБрд░рдВрдд рд╕рдордЭрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред рдЬрдм рдЖрдк рд╕рд╢рд░реНрдд рддрд░реНрдХ рдореЗрдВ рдкреИрдХ рдЕрдирд┐рд╡рд╛рд░реНрдп рдХреЛрдб рд▓рд┐рдЦрддреЗ рд╣реИрдВ, рддреЛ рдкрд╛рдардХ рдХреЛ рдмрд╣реБрдд рдкреНрд░рдпрд╛рд╕ рдХрд░рдирд╛ рдкрдбрд╝рддрд╛ рд╣реИред рдЗрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╕реЗ, рдЖрдкрдХреЛ рдПрдХ рдХрд╕реНрдЯрдо рдмреАрдбреАрдбреА рд╢реИрд▓реА рдореЗрдВ рдорд╛рдирд╡ рдЬреИрд╕реА рднрд╛рд╖рд╛ рдореЗрдВ рдЕрдкреЗрдХреНрд╖рд╛рдУрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЬреЛ рдХрд╕реНрдЯрдо рдХреЛрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ / рди рдХрд░реЗрдВред рдпрджрд┐ рдЪрд╛рдИ рдФрд░ рдЬреЗрд╕реНрдЯ рдореЗрдВ рдХреЛрдИ рдЖрд╡рд╢реНрдпрдХ рдЬреЛрд░ рдирд╣реАрдВ рд╣реИ, рдЬрд┐рд╕реЗ рдЕрдХреНрд╕рд░ рджреЛрд╣рд░рд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдЖрдк рдорд┐рд▓рд╛рди рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдЬреЗрд╕реНрдЯ рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдпрд╛ рдЪрд╛рдп рдХреЗ рд▓рд┐рдП рдЕрдкрдирд╛ рд╕реНрд╡рдпрдВ рдХрд╛ рдкреНрд▓рдЧрдЗрди рд▓рд┐рдЦ рд╕рдХрддреЗ рд╣реИрдВ ред

рдЕрдиреНрдпрдерд╛ред рдЯреАрдо рдХрдо рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрддреА рд╣реИ рдФрд░ with .skip() рдХрд╖реНрдЯрдкреНрд░рдж рдкрд░реАрдХреНрд╖рдгреЛрдВ with .skip() ред

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдореЛрдЪрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рдПрдХ рдЙрджрд╛рд╣рд░рдг ред

рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдкрд░реАрдХреНрд╖рдг рдХреЗ рд╕рд╛рд░ рдХреЛ рд╕рдордЭрдиреЗ рдХреЗ рд▓рд┐рдП, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдПрдХ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдЕрдирд┐рд╡рд╛рд░реНрдп рдХреЛрдб рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдордЬрдмреВрд░ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

 it("When asking for an admin, ensure only ordered admins in results" , ()={ //assuming we've added here two admins "admin1", "admin2" and "user1" const allAdmins = getUsers({adminOnly:true}); const admin1Found, adming2Found = false; allAdmins.forEach(aSingleUser => { if(aSingleUser === "user1"){ assert.notEqual(aSingleUser, "user1", "A user was found and not admin"); } if(aSingleUser==="admin1"){ admin1Found = true; } if(aSingleUser==="admin2"){ admin2Found = true; } }); if(!admin1Found || !admin2Found ){ throw new Error("Not all admins were returned"); } }); 

рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдЗрд╕ рдШреЛрд╖рдгрд╛рддреНрдордХ рдкрд░реАрдХреНрд╖рд╛ рдХреЛ рдкрдврд╝рдирд╛ рд╕реАрдзрд╛ рд╣реИред

 it("When asking for an admin, ensure only ordered admins in results" , ()={ //assuming we've added here two admins const allAdmins = getUsers({adminOnly:true}); expect(allAdmins).to.include.ordered.members(["admin1" , "admin2"]) .but.not.include.ordered.members(["user1"]); }); 


1.4 рдмреНрд▓реИрдХ рдмреЙрдХреНрд╕ рдкрд░реАрдХреНрд╖рдг рдХрд╛ рдкрд╛рд▓рди рдХрд░реЗрдВ: рдХреЗрд╡рд▓ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рддрд░реАрдХреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░реЗрдВ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдЗрдирд╕рд╛рдЗрдб рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рд╕реЗ рднрд╛рд░реА рдУрд╡рд░рд╣реЗрдб рд╣реЛ рдЬрд╛рдПрдЧрд╛ рдФрд░ рд▓рдЧрднрдЧ рдХреБрдЫ рднреА рдирд╣реАрдВ рдирд┐рдХрд▓реЗрдЧрд╛ред рдпрджрд┐ рдЖрдкрдХрд╛ рдХреЛрдб рдпрд╛ рдПрдкреАрдЖрдИ рд╕рд╣реА рдкрд░рд┐рдгрд╛рдо рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ, рддреЛ рдХреНрдпрд╛ рдпрд╣ рддреАрди рдШрдВрдЯреЗ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓рд╛рдпрдХ рд╣реИ рдХрд┐ рдпрд╣ рдЖрдВрддрд░рд┐рдХ рд░реВрдк рд╕реЗ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ рдФрд░ рдлрд┐рд░ рдЗрди рдирд╛рдЬреБрдХ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ? рдЬрдм рдЖрдк рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рд╡реНрдпрд╡рд╣рд╛рд░ рдХреА рдЬрд╛рдВрдЪ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рдПрдХ рд╕рд╛рде рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рд╕реНрд╡рдпрдВ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдЬрд╛рдВрдЪрддреЗ рд╣реИрдВ, рдЖрдкрдХреЗ рдкрд░реАрдХреНрд╖рдг рдХреЗрд╡рд▓ рддрднреА рд╡рд┐рдлрд▓ рд╣реЛрдВрдЧреЗ рдЬрдм рдХреЛрдИ рд╡рд┐рд╢рд┐рд╖реНрдЯ рд╕рдорд╕реНрдпрд╛ рд╣реЛ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЧрд▓рдд рдЖрдЙрдЯрдкреБрдЯ)ред рдЗрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреЛ рд╡реНрдпрд╡рд╣рд╛рд░ рдкрд░реАрдХреНрд╖рдг рднреА рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред рджреВрд╕рд░реА рдУрд░, рдпрджрд┐ рдЖрдк рдЗрдВрдЯрд░реНрдирд▓реНрд╕ ("рд╡реНрд╣рд╛рдЗрдЯ рдмреЙрдХреНрд╕" рд╡рд┐рдзрд┐) рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рддреЛ рдШрдЯрдХреЛрдВ рдХреЗ рдЖрдЙрдЯрдкреБрдЯ рдХреА рдпреЛрдЬрдирд╛ рдмрдирд╛рдиреЗ рдХреЗ рдмрдЬрд╛рдп, рдЖрдк рдЫреЛрдЯреЗ рд╡рд┐рд╡рд░рдгреЛрдВ рдкрд░ рдзреНрдпрд╛рди рдХреЗрдВрджреНрд░рд┐рдд рдХрд░реЗрдВрдЧреЗ, рдФрд░ рдЖрдкрдХреЗ рдкрд░реАрдХреНрд╖рдг рдЫреЛрдЯреЗ рдХреЛрдб рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рдХрд╛рд░рдг рдЯреВрдЯ рд╕рдХрддреЗ рд╣реИрдВ, рднрд▓реЗ рд╣реА рдкрд░рд┐рдгрд╛рдо рд╕рднреА рд╕рд╣реА рд╣реЛрдВ, рд▓реЗрдХрд┐рди рдПрд╕реНрдХреЙрд░реНрдЯ рдЬреНрдпрд╛рджрд╛ рд╕рдВрд╕рд╛рдзрди рд▓реЗрдЧрд╛ред

рдЕрдиреНрдпрдерд╛ред рдЖрдкрдХреЗ рдкрд░реАрдХреНрд╖рдг "рд╡реБрд▓реНрдл!" рдЪрд┐рд▓реНрд▓рд╛рддреЗ рд╣реБрдП рдПрдХ рд▓рдбрд╝рдХреЗ рдХреА рддрд░рд╣ рд╡реНрдпрд╡рд╣рд╛рд░ рдХрд░реЗрдВрдЧреЗ ред : рдЬреЛрд░ рд╕реЗ рд╕рдХрд╛рд░рд╛рддреНрдордХ рд░рд┐рдкреЛрд░реНрдЯ рдХрд░реЗрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рдирд┐рдЬреА рдЪрд░ рдХреЗ рдирд╛рдо рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рдХрд╛рд░рдг рдкрд░реАрдХреНрд╖рдг рд╡рд┐рдлрд▓ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ)ред рдпрд╣ рдЖрд╢реНрдЪрд░реНрдп рдХреА рдмрд╛рдд рдирд╣реАрдВ рд╣реИ рдХрд┐ рдЬрд▓реНрдж рд╣реА рд▓реЛрдЧ рд╕реАрдЖрдИ рд╕реВрдЪрдирд╛рдУрдВ рдХреЛ рдирдЬрд░рдЕрдВрджрд╛рдЬ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджреЗрдВрдЧреЗ, рдФрд░ рдПрдХ рджрд┐рди рд╡реЗ рдПрдХ рдЕрд╕рд▓реА рдмрдЧ рдХреЛ рдпрд╛рдж рдХрд░реЗрдВрдЧреЗ ...

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдмрд┐рдирд╛ рдХрд┐рд╕реА рдЕрдЪреНрдЫреЗ рдХрд╛рд░рдг рдХреЗ рдЗрдирд╕рд╛рдЗрдб рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдирд╛ред

рдореЛрдЪрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рдПрдХ рдЙрджрд╛рд╣рд░рдг ред

 class ProductService{ //this method is only used internally //Change this name will make the tests fail calculateVAT(priceWithoutVAT){ return {finalPrice: priceWithoutVAT * 1.2}; //Change the result format or key name above will make the tests fail } //public method getPrice(productId){ const desiredProduct= DB.getProduct(productId); finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice; } } it("White-box test: When the internal methods get 0 vat, it return 0 response", async () => { //There's no requirement to allow users to calculate the VAT, only show the final price. Nevertheless we falsely insist here to test the class internals expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0); }); 


1.5 рд╕рд╣реА рд╕рд┐рдореНрдпреБрд▓реЗрдЯреЗрдб рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЪреБрдиреЗрдВ: рд╕реНрдЯрдмреНрд╕ рдФрд░ рдЬрд╛рд╕реВрд╕реЛрдВ рдХреЗ рдкрдХреНрд╖ рдореЗрдВ рдирдХрд▓реА рд╡рд╕реНрддреБрдУрдВ рд╕реЗ рдмрдЪреЗрдВ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдирдХрд▓реА рдХреНрд░рд┐рдпрд╛рдиреНрд╡рдпрди (рдЯреЗрд╕реНрдЯ рдбрдмрд▓реНрд╕) рдПрдХ рдЖрд╡рд╢реНрдпрдХ рдмреБрд░рд╛рдИ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рд╡реЗ рдЖрд╡реЗрджрди рдХреЗ рдЗрдВрдЯрд░реНрди рдХреЗ рд╕рд╛рде рдЬреБрдбрд╝реЗ рд╣реБрдП рд╣реИрдВ, рдФрд░ рдХреБрдЫ рдмрд╣реБрдд рдорд╣рддреНрд╡ рдХреЗ рд╣реИрдВ ( рдирдХрд▓реА рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреА рд╕реНрдореГрддрд┐ рдХреЛ рддрд╛рдЬрд╝рд╛ рдХрд░реЗрдВ: рдирдХрд▓реА рд╡рд╕реНрддреБрдПрдВ (рдирдХрд▓реА), рд╕реНрдЯрдмреНрд╕ рдФрд░ рдЬрд╛рд╕реВрд╕реА рд╡рд╕реНрддреБрдПрдВ (рдЬрд╛рд╕реВрд╕) )ред рд╣рд╛рд▓рд╛рдВрдХрд┐, рд╕рднреА рддрдХрдиреАрдХреЗрдВ рд╕рдорд╛рди рдирд╣реАрдВ рд╣реИрдВред рдЬрд╛рд╕реВрд╕реА рдФрд░ рд╕реНрдЯрдмреНрд╕ рдХреЛ рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдХ рдЕрдирд┐рд╡рд╛рд░реНрдп рд╕рд╛рдЗрдб рдЗрдлреЗрдХреНрдЯ рд╣реИ - рд╡реЗ рдЗрдирд╕рд╛рдЗрдб рдХреЛ рднреА рдереЛрдбрд╝рд╛ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдФрд░ рдирдХрд▓реА рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдЗрдирд╕рд╛рдЗрдбреНрд╕ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рдЬреЛ рд╡рд┐рд╢рд╛рд▓ рдЙрдкрд░рд┐ рдХреА рдУрд░ рдЬрд╛рддрд╛ рд╣реИ, рдЬреИрд╕рд╛ рдХрд┐ рдЕрдзреНрдпрд╛рдп 1.4 рдореЗрдВ рд╡рд░реНрдгрд┐рдд рд╣реИред

рд╕рд┐рдореНрдпреБрд▓реЗрдЯреЗрдб рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ, рдЕрдкрдиреЗ рдЖрдк рд╕реЗ рд╕рдмрд╕реЗ рд╕рд░рд▓ рдкреНрд░рд╢реНрди рдкреВрдЫреЗрдВ: "рдХреНрдпрд╛ рдореИрдВ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░рддрд╛ рд╣реВрдВ рдЬреЛ рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рджрд╕реНрддрд╛рд╡реЗрдЬ рдореЗрдВ рджрд┐рдЦрд╛рдИ рджрд┐рдпрд╛ рд╣реИ рдпрд╛ рджрд┐рдЦрд╛рдИ рджреЗ рд╕рдХрддрд╛ рд╣реИ?" рдпрджрд┐ рдирд╣реАрдВ, рддреЛ рдпрд╣ рд╕рдлреЗрдж-рдмреЙрдХреНрд╕ рдкрд░реАрдХреНрд╖рдг рдХреА рдХрдореА рд╣реИред

рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рдЖрдк рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ рдХрд┐ рдХреНрдпрд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╡реНрдпрд╡рд╣рд╛рд░ рдХрд░рддрд╛ рд╣реИ рдЬреИрд╕реЗ рдХрд┐ рднреБрдЧрддрд╛рди рд╕реЗрд╡рд╛ рдЕрдиреБрдкрд▓рдмреНрдз рд╣реИ, рддреЛ рдЖрдк рдЗрд╕рдХреЗ рдмрдЬрд╛рдп рдПрдХ рд╕реНрдЯрдм рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ "рдиреЛ рдЖрдВрд╕рд░" рд╡рд╛рдкрд╕ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдпрд╣ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдХреНрдпрд╛ рдкрд░реАрдХреНрд╖рдг рдХреЗ рддрд╣рдд рдореЙрдбреНрдпреВрд▓ рд╕рд╣реА рдореВрд▓реНрдп рджреЗрддрд╛ рд╣реИред рддреЛ рдЖрдк рдХреБрдЫ рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рдХреЗ рддрд╣рдд рдЖрд╡реЗрджрди рдХреЗ рд╡реНрдпрд╡рд╣рд╛рд░ / рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ / рдЖрдЙрдЯрдкреБрдЯ рдХреА рдЬрд╛рдВрдЪ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЖрдк рдПрдХ рдЬрд╛рд╕реВрд╕ рдХреА рдорджрдж рд╕реЗ рдпрд╣ рднреА рдкреБрд╖реНрдЯрд┐ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдЬрдм рд╕реЗрд╡рд╛ рдЕрдиреБрдкрд▓рдмреНрдз рдереА, рддреЛ рдкрддреНрд░ рднреЗрдЬрд╛ рдЧрдпрд╛ рдерд╛, рдпрд╣ рд╡реНрдпрд╡рд╣рд╛рд░ рдкрд░реАрдХреНрд╖рдг рднреА рд╣реИ, рдЬреЛ рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рдмреЗрд╣рддрд░ рдкрд░рд┐рд▓рдХреНрд╖рд┐рдд рд╣реЛрддрд╛ рд╣реИ ("рдпрджрд┐ рднреБрдЧрддрд╛рди рдХреА рдЬрд╛рдирдХрд╛рд░реА рдХреЛ рдмрдЪрд╛рдпрд╛ рдирд╣реАрдВ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ рддреЛ рдПрдХ рдкрддреНрд░ рднреЗрдЬреЗрдВ")ред рдЙрд╕реА рд╕рдордп, рдпрджрд┐ рдЖрдк рдПрдХ рдлрд░реНрдЬреА рднреБрдЧрддрд╛рди рд╕реЗрд╡рд╛ рдмрдирд╛рддреЗ рд╣реИрдВ рдФрд░ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдХрд┐ рдЗрд╕реЗ рд╕рд╣реА рдЬреЗрдПрд╕ рдкреНрд░рдХрд╛рд░реЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдЖрдкрдХрд╛ рдкрд░реАрдХреНрд╖рдг рдЙрди рдЖрдВрддрд░рд┐рдХ рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рд▓рдХреНрд╖рд┐рдд рд╣реИ рдЬреЛ рдЖрд╡реЗрджрди рдХреА рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рд╕реЗ рд╕рдВрдмрдВрдзрд┐рдд рдирд╣реАрдВ рд╣реИрдВ рдФрд░ рдЬрд┐рдирдХреЗ рдЕрдХреНрд╕рд░ рдмрджрд▓рдиреЗ рдХреА рд╕рдВрднрд╛рд╡рдирд╛ рд╣реИред

рдЕрдиреНрдпрдерд╛ред рдХрд┐рд╕реА рднреА рдХреЛрдб рд░рд┐рдлреИрдХреНрдЯрд┐рдВрдЧ рдореЗрдВ рдХреЛрдб рдореЗрдВ рд╕рднреА рдирдХрд▓реА рд╡рд╕реНрддреБрдУрдВ рдХреЛ рдвреВрдВрдврдирд╛ рдФрд░ рдЕрдкрдбреЗрдЯ рдХрд░рдирд╛ рд╢рд╛рдорд┐рд▓ рд╣реИред рдПрдХ рд╕рд╣рд╛рдпрдХ рдорд┐рддреНрд░ рдХреЗ рдЯреЗрд╕реНрдЯ рдмреЛрдЭ рдореЗрдВ рдмрджрд▓ рдЬрд╛рддреЗ рд╣реИрдВред

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдирдХрд▓реА рд╡рд╕реНрддреБрдПрдВ рд╣рд┐рдореНрдордд рдХреЗ рд▓рд┐рдП рд╣реЛрддреА рд╣реИрдВред

рд╕рд┐рдиреЛрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрджрд╛рд╣рд░рдг ред

 it("When a valid product is about to be deleted, ensure data access DAL was called once, with the right product and right config", async () => { //Assume we already added a product const dataAccessMock = sinon.mock(DAL); //hmmm BAD: testing the internals is actually our main goal here, not just a side-effect dataAccessMock.expects("deleteProduct").once().withArgs(DBConfig, theProductWeJustAdded, true, false); new ProductService().deletePrice(theProductWeJustAdded); mock.verify(); }); 

рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдЬрд╛рд╕реВрд╕реЛрдВ рдХреЛ рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдХ рд╕рд╛рдЗрдб рдЗрдлреЗрдХреНрдЯ рд╣реИ - рд╡реЗ рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ рдЗрдирд╕рд╛рдЗрдб рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░рддреЗ рд╣реИрдВред

 it("When a valid product is about to be deleted, ensure an email is sent", async () => { //Assume we already added here a product const spy = sinon.spy(Emailer.prototype, "sendEmail"); new ProductService().deletePrice(theProductWeJustAdded); //hmmm OK: we deal with internals? Yes, but as a side effect of testing the requirements (sending an email) }); 


1.6 "рдлреВ" рдХрд╛ рдЙрдкрдпреЛрдЧ рди рдХрд░реЗрдВ, рдпрдерд╛рд░реНрдерд╡рд╛рджреА рдЗрдирдкреБрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдЕрдХреНрд╕рд░ рдЙрддреНрдкрд╛рджрди рдХреАрдбрд╝реЗ рдмрд╣реБрдд рд╡рд┐рд╢рд┐рд╖реНрдЯ рдФрд░ рдЖрд╢реНрдЪрд░реНрдпрдЬрдирдХ рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рд╣реЛрддреЗ рд╣реИрдВред рдкрд░реАрдХреНрд╖рдг рдХреЗ рджреМрд░рд╛рди рдбреЗрдЯрд╛ рдЬрд┐рддрдирд╛ рдЕрдзрд┐рдХ рдпрдерд╛рд░реНрдерд╡рд╛рджреА рд╣реЛрдЧрд╛, рд╕рдордп рдкрд░ рдХреАрдбрд╝реЗ рдХреЛ рдкрдХрдбрд╝рдиреЗ рдХреА рдЕрдзрд┐рдХ рд╕рдВрднрд╛рд╡рдирд╛ рд╣реЛрдЧреАред рдЫрджреНрдо рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЬреЛ рд╡рд┐рднрд┐рдиреНрди рдкреНрд░рдХрд╛рд░ рдФрд░ рдЙрддреНрдкрд╛рджрди рдбреЗрдЯрд╛ рдХрд╛ рдЕрдиреБрдХрд░рдг рдХрд░рддрд╛ рд╣реИ, рд╡рд┐рд╢реЗрд╖ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, Faker ред рдЗрд╕ рддрд░рд╣ рдХреЗ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдпрдерд╛рд░реНрдерд╡рд╛рджреА рдлреЛрди рдирдВрдмрд░, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЙрдкрдирд╛рдо, рдмреИрдВрдХ рдХрд╛рд░реНрдб, рдХрдВрдкрдиреА рдХреЗ рдирд╛рдо, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдкрд╛рда "рд▓реЛрд░реЗрдо рдЗрдкреНрд╕рдо" рдЙрддреНрдкрдиреНрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЖрдк рдкрд░реАрдХреНрд╖рдг рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ (рдЗрдХрд╛рдИ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╢реАрд░реНрд╖ рдкрд░, рдФрд░ рдЙрдирдХреЗ рдмрдЬрд╛рдп рдирд╣реАрдВ) рдЬреЛ рдореЙрдбреНрдпреВрд▓ рдХреЛ рдПрдХ рдкрд░реАрдХреНрд╖рдг рдореЗрдВ рдлрд┐рдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдирдХрд▓реА рдбреЗрдЯрд╛ рдХреЛ рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рдХрд░рддреЗ рд╣реИрдВ, рдпрд╛ рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдЙрддреНрдкрд╛рджрди рд╡рд╛рддрд╛рд╡рд░рдг рд╕реЗ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рдЖрдпрд╛рдд рдХрд░рддреЗ рд╣реИрдВред рдЖрдЧреЗ рднреА рдЬрд╛рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ? рдЕрдЧрд▓рд╛ рдЕрдзреНрдпрд╛рдп (рд╕рдВрдкрддреНрддрд┐-рдЖрдзрд╛рд░рд┐рдд рдкрд░реАрдХреНрд╖рдг рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ) рдкрдврд╝реЗрдВред

рдЕрдиреНрдпрдерд╛ред "рдлреВ" рдЬреИрд╕реЗ рд╕рд┐рдВрдереЗрдЯрд┐рдХ рдЗрдирдкреБрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЖрдкрдХрд╛ рд╡рд┐рдХрд╛рд╕ рдкрд░реАрдХреНрд╖рдг рд╕рдлрд▓ рджрд┐рдЦрд╛рдИ рджреЗрдЧрд╛, рдФрд░ рдЙрддреНрдкрд╛рджрди рдбреЗрдЯрд╛ рд╡рд┐рдлрд▓ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ рдЬрдм рдХреЛрдИ рд╣реИрдХрд░ @3e2ddsf . ##' 1 fdsfds . fds432 AAAA рдЬреИрд╕реА @3e2ddsf . ##' 1 fdsfds . fds432 AAAA рд▓рд╛рдЗрди @3e2ddsf . ##' 1 fdsfds . fds432 AAAA @3e2ddsf . ##' 1 fdsfds . fds432 AAAA @3e2ddsf . ##' 1 fdsfds . fds432 AAAA

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдПрдХ рдкрд░реАрдХреНрд╖рдг рд╕реВрдЯ рдЬреЛ рдЕрд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рдХреЗ рдЙрдкрдпреЛрдЧ рдХреЗ рдХрд╛рд░рдг рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЪрд▓рддрд╛ рд╣реИред

рдЬреЗрд╕реНрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рдПрдХ рдЙрджрд╛рд╣рд░рдг ред

 const addProduct = (name, price) =>{ const productNameRegexNoSpace = /^\S*$/;//no white-space allowed if(!productNameRegexNoSpace.test(name)) return false;//this path never reached due to dull input //some logic here return true; }; test("Wrong: When adding new product with valid properties, get successful confirmation", async () => { //The string "Foo" which is used in all tests never triggers a false result const addProductResult = addProduct("Foo", 5); expect(addProductResult).to.be.true; //Positive-false: the operation succeeded because we never tried with long //product name including spaces }); 

рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдпрдерд╛рд░реНрдерд╡рд╛рджреА рдЗрдирдкреБрдЯ рдпрд╛рджреГрдЪреНрдЫрд┐рдХред

 it("Better: When adding new valid product, get successful confirmation", async () => { const addProductResult = addProduct(faker.commerce.productName(), faker.random.number()); //Generated random input: {'Sleek Cotton Computer', 85481} expect(addProductResult).to.be.true; //Test failed, the random input triggered some path we never planned for. //We discovered a bug early! }); 


1.7 рдХрдИ рдЗрдирдкреБрдЯ рд╕рдВрдпреЛрдЬрдиреЛрдВ рдХреЛ рдорд╛рдиреНрдп рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдВрдкрддреНрддрд┐-рдЖрдзрд╛рд░рд┐рдд рдкрд░реАрдХреНрд╖рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдЖрдорддреМрд░ рдкрд░ рдкреНрд░рддреНрдпреЗрдХ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рд╣рдо рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ рдХреЗ рдХрдИ рдирдореВрдиреЛрдВ рдХрд╛ рдЪрдпрди рдХрд░рддреЗ рд╣реИрдВред рднрд▓реЗ рд╣реА рдЗрдирдкреБрдЯ рдкреНрд░рд╛рд░реВрдк рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рдХреЗ рд╕рдорд╛рди рд╣реИ (рдЕрдзреНрдпрд╛рдп "" рдлреВ рдХрд╛ рдЙрдкрдпреЛрдЧ рди рдХрд░реЗрдВ "рджреЗрдЦреЗрдВ), рд╣рдо рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ (рд╡рд┐рдзрд┐ ('', true, 1) , рд╡рд┐рдзрд┐ ("string" , false" , 0) рдХреЗрд╡рд▓ рдХреБрдЫ рд╕рдВрдпреЛрдЬрдиреЛрдВ рдХреЛ рдХрд╡рд░ рдХрд░рддреЗ рд╣реИрдВред )ред рд▓реЗрдХрд┐рди рдСрдкрд░реЗрд╢рди рдореЗрдВ, рдПрдХ рдПрдкреАрдЖрдИ рдЬрд┐рд╕реЗ рдкрд╛рдВрдЪ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЙрд╕реЗ рд╣рдЬрд╛рд░реЛрдВ рд╡рд┐рднрд┐рдиреНрди рд╕рдВрдпреЛрдЬрдиреЛрдВ рдХреЗ рд╕рд╛рде рдмреБрд▓рд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдЬрд┐рдирдореЗрдВ рд╕реЗ рдПрдХ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреНрд░реИрд╢ ( рдлрд╝рдЬрд╝рд┐рдВрдЧ ) рд╣реЛ рд╕рдХрддреА рд╣реИред рдХреНрдпрд╛ рд╣реЛрдЧрд╛ рдпрджрд┐ рдЖрдк рдПрдХ рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦ рд╕рдХрддреЗ рд╣реИрдВ рдЬреЛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ рдХреЗ 1000 рд╕рдВрдпреЛрдЬрдиреЛрдВ рдХреЛ рднреЗрдЬрддрд╛ рд╣реИред рдлрд┐рдХреНрд╕рд┐рдВрдЧ, рдХрд┐рд╕ рд╕рдВрдпреЛрдЬрди рдкрд░ рдХреЛрдб рд╕рд╣реА рдЙрддреНрддрд░ рдирд╣реАрдВ рджреЗрддрд╛ рд╣реИ? рд╡рд╣реА рдмрд╛рдд рдЬреЛ рд╣рдо рдореА рдХреЗ рд╕рд╛рде рдХрд░рддреЗ рд╣реИрдВ рдЧреБрдгреЛрдВ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдкрд░реАрдХреНрд╖рдг todike: рдкрд░реАрдХреНрд╖рдг рдЗрдХрд╛рдИ рдореЗрдВ рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ рдХреЗ рд╕рднреА рд╕рдВрднрд╡ рд╕рдВрдпреЛрдЬрдиреЛрдВ рднреЗрдЬрдХрд░ рдЙрд╕рдореЗрдВ рдПрдХ рдмрдЧ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреА рд╕рдВрднрд╛рд╡рдирд╛ рдХреЛ рдмрдврд╝рд╛, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рд╡рд┐рдзрд┐ рд╣реИред addNewProduct(id, name, isDiscount) рдЕрдкрдиреЗ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕рд╣рд╛рдпрдХ рд╕рдВрдпреЛрдЬрдиреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдХреЗ рд╕рд╛рде рдЗрд╕ рдкрджреНрдзрддрд┐ рдХреЛ рдХреЙрд▓ рдХрд░реЗрдВрдЧреЗред (, , ) , рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, (1, "iPhone", false) , (2, "Galaxy", true) рдЖрджрд┐ред рдЖрдк рдЕрдкрдиреЗ рдкрд╕рдВрджреАрджрд╛ рдЯреЗрд╕реНрдЯ рд░рдирд░ (рдореЛрдЪрд╛, рдЬреЗрд╕реНрдЯ) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЧреБрдгреЛрдВ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдкрд░реАрдХреНрд╖рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЖрджрд┐) рдФрд░ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдЬреИрд╕реЗ рдЬреЗрдПрд╕-рд╕рддреНрдпрд╛рдкрд┐рдд рдпрд╛ рдЯреЗрд╕реНрдЯрдЪреЗрдХ (рдЗрд╕рдореЗрдВ рдмрд╣реБрдд рдмреЗрд╣рддрд░ рдкреНрд░рд▓реЗрдЦрди рд╣реИ)ред рдЖрдк рдлрд╛рд╕реНрдЯ-рдЪреЗрдХ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреА рднреА рдХреЛрд╢рд┐рд╢ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЬреЛ рдЕрддрд┐рд░рд┐рдХреНрдд рд╕реБрд╡рд┐рдзрд╛рдПрдБ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ рдФрд░ рд▓реЗрдЦрдХ рдХреЗ рд╕рд╛рде рд╕рдХреНрд░рд┐рдп рд░реВрдк рд╕реЗ рд╣реЛрддрд╛ рд╣реИред

рдЕрдиреНрдпрдерд╛ред рдЖрдк рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рдмрд┐рдирд╛ рд╕реЛрдЪреЗ рд╕рдордЭреЗ рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ рдЪреБрди рд░рд╣реЗ рд╣реИрдВ, рдЬреЛ рдХреЗрд╡рд▓ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдХрд╛рдо рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдХреЛрдб рдирд┐рд╖реНрдкрд╛рджрди рдкрде рдХреЛ рдХрд╡рд░ рдХрд░рддрд╛ рд╣реИред рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдпрд╣ рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреЗ рд╕рд╛рдзрди рдХреЗ рд░реВрдк рдореЗрдВ рдкрд░реАрдХреНрд╖рдг рдХреА рдкреНрд░рднрд╛рд╡рд╢реАрд▓рддрд╛ рдХреЛ рдХрдо рдХрд░рддрд╛ рд╣реИред

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдореЛрдЪрд╛-рдЯреЗрд╕реНрдЯрдЪреЗрдХ рдХреЗ рд╕рд╛рде рдХрдИ рд╕рдВрдпреЛрдЬрдиреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░реЗрдВред

 require('mocha-testcheck').install(); const {expect} = require('chai'); const faker = require('faker'); describe('Product service', () => { describe('Adding new', () => { //this will run 100 times with different random properties check.it('Add new product with random yet valid properties, always successful', gen.int, gen.string, (id, name) => { expect(addNewProduct(id, name).status).to.equal('approved'); }); }) }); 


1.8 рдпрджрд┐ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ, рдХреЗрд╡рд▓ рд▓рдШреБ рдФрд░ рдЗрдирд▓рд╛рдЗрди рд╢реЙрдЯреНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред


рдХреНрдпрд╛ рдХрд░реЗрдВ? рдЬрдм рдЖрдкрдХреЛ рд╕реНрдиреИрдкрд╢реЙрдЯ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ, рддреЛ рд╕рднреА рдЕрддрд┐рд░рд┐рдХреНрдд (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, 3-7 рд▓рд╛рдЗрдиреЛрдВ рдореЗрдВ) рдХреЗ рдмрд┐рдирд╛ рдХреЗрд╡рд▓ рдЫреЛрдЯреЗ рд╕реНрдиреИрдкрд╢реЙрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рдЙрдиреНрд╣реЗрдВ рдкрд░реАрдХреНрд╖рдг рдХреЗ рднрд╛рдЧ рдХреЗ рд░реВрдк рдореЗрдВ рд╢рд╛рдорд┐рд▓ рдХрд░реЗрдВ ( рдЗрдирд▓рд╛рдЗрди рд╕реНрдиреИрдкрд╢реЙрдЯ ), рдФрд░ рдмрд╛рд╣рд░реА рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВред рдЗрд╕ рд╕рд┐рдлрд╛рд░рд┐рд╢ рдХреЗ рдмрд╛рдж рдЖрдкрдХреЗ рдкрд░реАрдХреНрд╖рдг рд╕реНрд╡-рд╕реНрдкрд╖реНрдЯ рдФрд░ рдЕрдзрд┐рдХ рд╡рд┐рд╢реНрд╡рд╕рдиреАрдп рд░рд╣реЗрдВрдЧреЗред

рджреВрд╕рд░реА рдУрд░, "рдХреНрд▓рд╛рд╕рд┐рдХ рд╕реНрдиреИрдкрд╢реЙрдЯ" рдорд╛рд░реНрдЧрджрд░реНрд╢рд┐рдХрд╛рдПрдБ рдФрд░ рдЙрдкрдХрд░рдг рд╣рдореЗрдВ рдмрд╛рд╣рд░реА рдореАрдбрд┐рдпрд╛ рдкрд░ рдмрдбрд╝реА рдлрд╝рд╛рдЗрд▓реЛрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдШрдЯрдХ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдпрд╛ рдЬреЗрдПрд╕рдПрди рдПрдкреАрдЖрдИ рдкрд░рд┐рдгрд╛рдо рдХреЗ рдорд╛рд░реНрдХрдЕрдк) рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдХрд╕рд╛рддреЗ рд╣реИрдВ рдФрд░ рдкрд░реАрдХреНрд╖рдг рдХреЗ рдЪрд▓рдиреЗ рдкрд░ рд╕рд╣реЗрдЬреЗ рдЧрдП рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд╕рд╛рде рдкрд░рд┐рдгрд╛рдореЛрдВ рдХреА рддреБрд▓рдирд╛ рдХрд░рддреЗ рд╣реИрдВред рдпрд╣, рдХрд╣ рд╕рдХрддрд╛ рд╣реИ рдХрд┐, рд╣рдорд╛рд░реЗ рдкрд░реАрдХреНрд╖рдг рдХреЛ 3000 рдорд╛рдиреЛрдВ рд╡рд╛рд▓реА 1000 рдкрдВрдХреНрддрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдЬреЛрдбрд╝ рджреЗрддрд╛ рд╣реИ, рдЬреЛ рдХрд┐ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓реЗрдЦрдХ рдиреЗ рдХрднреА рдирд╣реАрдВ рджреЗрдЦрд╛ рдерд╛ рдЬрд┐рд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЙрд╕рдиреЗ рдЙрдореНрдореАрдж рдирд╣реАрдВ рдХреА рдереАред рдпрд╣ рдмреБрд░рд╛ рдХреНрдпреЛрдВ рд╣реИ? рдХреНрдпреЛрдВрдХрд┐ рдЯреЗрд╕реНрдЯ рдореЗрдВ рдлреЗрд▓ рд╣реЛрдиреЗ рдХреЗ 1000 рдХрд╛рд░рдг рд╣реИрдВред рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдПрдХ рдкрдВрдХреНрддрд┐ рд╕реНрдиреИрдкрд╢реЙрдЯ рдХреЛ рдЕрдорд╛рдиреНрдп рдХрд░ рд╕рдХрддреА рд╣реИ, рдФрд░ рдпрд╣ рдЕрдХреНрд╕рд░ рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдХрд┐рддрдирд╛? рдкреНрд░рддреНрдпреЗрдХ рд╕реНрдерд╛рди рдХреЗ рдмрд╛рдж, рд╕реАрдПрд╕рдПрд╕ рдпрд╛ HTML рдореЗрдВ рдЯрд┐рдкреНрдкрдгреА, рдпрд╛ рдорд╛рдореВрд▓реА рдкрд░рд┐рд╡рд░реНрддрдиред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдкрд░реАрдХреНрд╖рдг рдХрд╛ рдирд╛рдо рдЖрдкрдХреЛ рд╡рд┐рдлрд▓рддрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдирд╣реАрдВ рдмрддрд╛рдПрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдХреЗрд╡рд▓ рдпрд╣ рдЬрд╛рдВрдЪрддрд╛ рд╣реИ рдХрд┐ 1000 рд░реЗрдЦрд╛рдПрдВ рдирд╣реАрдВ рдмрджрд▓реА рд╣реИрдВ, рдФрд░ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓реЗрдЦрдХ рдХреЛ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рд╡рд╛рдВрдЫрд┐рдд рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рд▓реЗрдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░реЛрддреНрд╕рд╛рд╣рд┐рдд рдХрд░рддрд╛ рд╣реИ рдЬрд┐рд╕реЗ рд╡рд╣ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдФрд░ рд╕рддреНрдпрд╛рдкрд┐рдд рдирд╣реАрдВ рдХрд░ рд╕рдХрд╛ред рдпреЗ рд╕рднреА рдПрдХ рдЕрд╕реНрдкрд╖реНрдЯ рдФрд░ рдЬрд▓реНрджрдмрд╛рдЬреА рдореЗрдВ рдХрд┐рдП рдЧрдП рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рдХреНрд╖рдг рд╣реИрдВ, рдЬрд┐рд╕рдореЗрдВ рдПрдХ рд╕реНрдкрд╖реНрдЯ рдХрд╛рд░реНрдп рдирд╣реАрдВ рд╣реИ рдФрд░ рдмрд╣реБрдд рдЕрдзрд┐рдХ рд╣рд╛рд╕рд┐рд▓ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░ рд░рд╣рд╛ рд╣реИред

рдпрд╣ рдзреНрдпрд╛рди рджреЗрдиреЗ рдпреЛрдЧреНрдп рд╣реИ рдХрд┐ рдРрд╕реА рдХрдИ рд╕реНрдерд┐рддрд┐рдпрд╛рдВ рд╣реИрдВ рдЬрд┐рдирдореЗрдВ рд▓рдВрдмреА рдФрд░ рдмрд╛рд╣рд░реА рдЫрд╡рд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╕реНрд╡реАрдХрд╛рд░реНрдп рд╣реИ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЬрдм рдпреЛрдЬрдирд╛ рдХреА рдкреБрд╖реНрдЯрд┐ рд╣реЛрддреА рд╣реИ, рдФрд░ рди рдХрд┐ рдбреЗрдЯрд╛ (рдорд╛рдиреЛрдВ рдХреЛ рд╣рдЯрд╛рдиреЗ рдФрд░ рдЦреЗрддреЛрдВ рдкрд░ рдзреНрдпрд╛рди рдХреЗрдВрджреНрд░рд┐рдд рдХрд░рдирд╛), рдпрд╛ рдЬрдм рдкреНрд░рд╛рдкреНрдд рджрд╕реНрддрд╛рд╡реЗрдЬ рд╢рд╛рдпрдж рд╣реА рдХрднреА рдмрджрд▓рддреЗ рд╣реИрдВред

рдЕрдиреНрдпрдерд╛ред UI рдкрд░реАрдХреНрд╖рдг рд╡рд┐рдлрд▓ред рдХреЛрдб рдареАрдХ рджрд┐рдЦрддрд╛ рд╣реИ, рдЖрджрд░реНрд╢ рдкрд┐рдХреНрд╕реЗрд▓ рд╕реНрдХреНрд░реАрди рдкрд░ рдкреНрд░рджрд░реНрд╢рд┐рдд рд╣реЛрддреЗ рд╣реИрдВ, рддреЛ рдХреНрдпрд╛ рд╣реЛрддрд╛ рд╣реИ? рд╕реНрдиреИрдкрд╢реЙрдЯ рдХреЗ рд╕рд╛рде рдЖрдкрдХреЗ рдкрд░реАрдХреНрд╖рдг рдиреЗ рдореВрд▓ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдФрд░ рдХреЗрд╡рд▓ рдкреНрд░рд╛рдкреНрдд рдПрдХ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдпрд╛ - рдорд╛рд░реНрдХрдЕрдк рдореЗрдВ рдПрдХ рдЕрдВрддрд░рд┐рдХреНрд╖ рд╡рд░реНрдг рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рдерд╛ ...

рдХреЛрдб рдЙрджрд╛рд╣рд░рдг
рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдХреЛрдб рдХреА рдХреБрдЫ рдЕрдЬреНрдЮрд╛рдд 2000 рд▓рд╛рдЗрдиреЛрдВ рдХреЗ рд╕рд╛рде рдПрдХ рдкрд░реАрдХреНрд╖рдг рдХреЛ рдЬреЛрдбрд╝рдирд╛ред

 it('TestJavaScript.com is renderd correctly', () => { //Arrange //Act const receivedPage = renderer .create( <DisplayPage page = "http://www.testjavascript.com" > Test JavaScript < /DisplayPage>) .toJSON(); //Assert expect(receivedPage).toMatchSnapshot(); //We now implicitly maintain a 2000 lines long document //every additional line break or comment - will break this test }); 

рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдЙрдореНрдореАрджреЗрдВ рджрд┐рдЦрд╛рдИ рджреЗ рд░рд╣реА рд╣реИрдВ рдФрд░ рд╕реБрд░реНрдЦрд┐рдпреЛрдВ рдореЗрдВ рд╣реИрдВред

 it('When visiting TestJavaScript.com home page, a menu is displayed', () => { //Arrange //Act receivedPage tree = renderer .create( <DisplayPage page = "http://www.testjavascript.com" > Test JavaScript < /DisplayPage>) .toJSON(); //Assert const menu = receivedPage.content.menu; expect(menu).toMatchInlineSnapshot(` <ul> <li>Home</li> <li> About </li> <li> Contact </li> </ul> `); }); 


1.9 рд╡реИрд╢реНрд╡рд┐рдХ рдкрд░реАрдХреНрд╖рдг рдмреЗрдВрдЪ рдФрд░ рдкреНрд░рд╛рд░рдВрднрд┐рдХ рдбреЗрдЯрд╛ рд╕реЗ рдмрдЪреЗрдВ, рдкреНрд░рддреНрдпреЗрдХ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рдЕрд▓рдЧ рд╕реЗ рдбреЗрдЯрд╛ рдЬреЛрдбрд╝реЗрдВ


рдХреНрдпрд╛ рдХрд░реЗрдВ? рд╕реБрдирд╣рд░реЗ рдирд┐рдпрдо (рдЕрдзреНрдпрд╛рдп 0) рдХреЗ рдЕрдиреБрд╕рд╛рд░, рдкреНрд░рддреНрдпреЗрдХ рдкрд░реАрдХреНрд╖рдг рдХреЛ рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдкрдВрдХреНрддрд┐рдпреЛрдВ рдХреЗ рдЕрдкрдиреЗ рд╕реЗрдЯ рдХреЗ рднреАрддрд░ рдЬреЛрдбрд╝рдирд╛ рдФрд░ рдХрд╛рдо рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП, рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдкрд░реАрдХреНрд╖рдг рдХреЛ рд╕рдордЭрдирд╛ рдЖрд╕рд╛рди рдерд╛ред рд╡рд╛рд╕реНрддрд╡рд┐рдХрддрд╛ рдореЗрдВ, рдкрд░реАрдХреНрд╖рдХ рдЕрдХреНрд╕рд░ рдЗрд╕ рдирд┐рдпрдо рдХрд╛ рдЙрд▓реНрд▓рдВрдШрди рдХрд░рддреЗ рд╣реИрдВ, рдЙрддреНрдкрд╛рджрдХрддрд╛ рдмрдврд╝рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░рд╛рд░рдВрднрд┐рдХ рдбреЗрдЯрд╛ (рдмреАрдЬ) ( рдЬрд┐рд╕реЗ "рдмреЗрдВрдЪ рдмреЗрдВрдЪ" рднреА рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ ) рдХреЗ рд╕рд╛рде рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЛ рднрд░рдиреЗ рд╡рд╛рд▓реЗ рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рдиреЗ рд╕реЗ рдкрд╣рд▓реЗред , (. ┬л ┬╗), . . , , (, ).

. , , , ? , , , .

рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдкрд░реАрдХреНрд╖рдг рд╕реНрд╡рддрдВрддреНрд░ рдирд╣реАрдВ рд╣реИрдВ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ рд╡реИрд╢реНрд╡рд┐рдХ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рд╕реА рдкреНрд░рдХрд╛рд░ рдХреЗ рд╡реИрд╢реНрд╡рд┐рдХ рд╣реБрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред

 before(() => { //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework await DB.AddSeedDataFromJson('seed.json'); }); it("When updating site name, get successful confirmation", async () => { //I know that site name "portal" exists - I saw it in the seed files const siteToUpdate = await SiteService.getSiteByName("Portal"); const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); expect(updateNameResult).to.be(true); }); it("When querying by site name, get the right site", async () => { //I know that site name "portal" exists - I saw it in the seed files const siteToCheck = await SiteService.getSiteByName("Portal"); expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ }); 

рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдЖрдк рдкрд░реАрдХреНрд╖рдг рдХреЗ рднреАрддрд░ рд░рд╣ рд╕рдХрддреЗ рд╣реИрдВ, рдкреНрд░рддреНрдпреЗрдХ рдкрд░реАрдХреНрд╖рдг рдХреЗрд╡рд▓ рдЕрдкрдиреЗ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред

 it("When updating site name, get successful confirmation", async () => { //test is adding a fresh new records and acting on the records only const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); expect(updateNameResult).to.be(true); }); 


1.10 ,


. , - , try-catch-finally , . ( ), .

Chai: expect(method).to.throw ( Jest: expect(method).toThrow() ). , , . , , .

. (, CI-) , .

рдПрдВрдЯреАрдкреИрдЯрд░реНрди рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдгред рдПрдХ рд▓рдВрдмрд╛ рдкрд░реАрдХреНрд╖рдг рдорд╛рдорд▓рд╛ рдЬреЛ рдХреЛрд╢рд┐рд╢-рдХреИрдЪ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдПрдХ рддреНрд░реБрдЯрд┐ рдХреЛ рдкрдХрдбрд╝рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддрд╛ рд╣реИред

 /it("When no product name, it throws error 400", async() => { let errorWeExceptFor = null; try { const result = await addNewProduct({name:'nest'});} catch (error) { expect(error.code).to.equal('InvalidInput'); errorWeExceptFor = error; } expect(errorWeExceptFor).not.to.be.null; //if this assertion fails, the tests results/reports will only show //that some value is null, there won't be a word about a missing Exception }); 

рдЗрд╕реЗ рд╕рд╣реА рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛рдПред рдорд╛рдирд╡-рдкрдардиреАрдп рдХреЛрдб рдЬреЛ рд╕рдордЭрдирд╛ рдЖрд╕рд╛рди рд╣реИ, рд╢рд╛рдпрдж рдХреНрдпреВрдП рд╕реНрдЯрд╛рдл рдпрд╛ рддрдХрдиреАрдХреА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдкреНрд░рдмрдВрдзрдХреЛрдВ рдХреЗ рд▓рд┐рдП рднреАред

 it.only("When no product name, it throws error 400", async() => { expect(addNewProduct)).to.eventually.throw(AppError).with.property('code', "InvalidInput"); }); 


1.11


. :

  • smoke-,
  • IO-less,
  • , , ,
  • , pull request', .

, , , #cold #api #sanity. . , Mocha : mocha тАФ grep 'sanity' .

. , , , , , , , .

. '#cold-test' (Cold=== , - , ).

 //this test is fast (no DB) and we're tagging it correspondingly //now the user/CI can run it frequently describe('Order service', function() { describe('Add new order #cold-test #sanity', function() { it('Scenario - no currency was supplied. Expectation - Use the default currency #sanity', function() { //code logic here }); }); }); 


1.12


. , Node.js . , Node.

TDD . , , , . -- , - . , , . , . , , , , (, ..).

. , .

2:


я╕П2.1 :


. 10 , . . , 10 (, , ), , , ? ?

: 2019- , TDD , , , . , , , . IoT-, Kafka RabbitMQ, , - . , , ? (, , ), , - .

( ) , , (┬л API, , !┬╗ (consumer-driven contracts)). , : , , , .

: TDD - . TDD , . , .

. ROI, Fuzz, , 10 .

. Cindy Sridharan 'Testing Microservices тАФ the sane way'



рдПрдХ рдЙрджрд╛рд╣рд░рдг:



2.2


. , . , . , , ? тАФ . : TDD-, .

┬л┬╗, API, , (, , in-memory ), , , . , , ┬л ┬╗, .

. , , 20 %.

. Express API ( ).



2.3 , API


. , ( ). - , ! тАФ , , . ┬л -22 ┬╗ : , , . (consumer-driven contracts) PACT : , тАж ! PACT тАФ ┬л┬╗, . PACT- тАФ . , API CI, .

. тАФ .

.



2.4


. , Express-. . , , , JS- {req,res}. , (, Sinon ) {req,res}, , . node-mock-http {req,res} . , , HTTP-, res- (. ).

. Express- === .

. , Express-.

 //the middleware we want to test const unitUnderTest = require('./middleware') const httpMocks = require('node-mocks-http'); //Jest syntax, equivalent to describe() & it() in Mocha test('A request without authentication header, should return http status 403', () => { const request = httpMocks.createRequest({ method: 'GET', url: '/user/42', headers: { authentication: '' } }); const response = httpMocks.createResponse(); unitUnderTest(request, response); expect(response.statusCode).toBe(403); }); 


2.5


. . CI- , . (, ), (, ), . Sonarqube (2600+ ) Code Climate (1500+ ). :: Keith Holliday

. , .

. CodeClimate, :



2.6 , Node


. , . ( ) . , - , ? ? , API 50 % ? , Netflix - ( Chaos Engineering ). : , . , Netflix, chaos monkey , , , , - ( Kubernetes kube-monkey , ). , . , , Node- , , v8 1,7 , UX , ? node-chaos (-), , Node.

. , production .

. Node-chaos , Node.js, .



2.7 ,


. ( 0), , , . , (seeds) ( ┬л ┬╗ ) . , (. ┬л ┬╗), , . . , , (, ).

. , , , ? , , , .

. - .

 before(() => { //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework await DB.AddSeedDataFromJson('seed.json'); }); it("When updating site name, get successful confirmation", async () => { //I know that site name "portal" exists - I saw it in the seed files const siteToUpdate = await SiteService.getSiteByName("Portal"); const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); expect(updateNameResult).to.be(true); }); it("When querying by site name, get the right site", async () => { //I know that site name "portal" exists - I saw it in the seed files const siteToCheck = await SiteService.getSiteByName("Portal"); expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ }); 

. , .

 it("When updating site name, get successful confirmation", async () => { //test is adding a fresh new records and acting on the records only const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); expect(updateNameResult).to.be(true); }); 


3:


3.1ред UI


. , , , . , , ( HTML CSS) . , (, , , ), , , .

. 10 , 500 (100 = 1 ) - - .

. .

 test('When users-list is flagged to show only VIP, should display only VIP members', () => { // Arrange const allUsers = [ { id: 1, name: 'Yoni Goldberg', vip: false }, { id: 2, name: 'John Doe', vip: true } ]; // Act const { getAllByTestId } = render(<UsersList users={allUsers} showOnlyVIP={true}/>); // Assert - Extract the data from the UI first const allRenderedUsers = getAllByTestId('user').map(uiElement => uiElement.textContent); const allRealVIPUsers = allUsers.filter((user) => user.vip).map((user) => user.name); expect(allRenderedUsers).toEqual(allRealVIPUsers); //compare data with data, no UI here }); 

. UI .
 test('When flagging to show only VIP, should display only VIP members', () => { // Arrange const allUsers = [ {id: 1, name: 'Yoni Goldberg', vip: false }, {id: 2, name: 'John Doe', vip: true } ]; // Act const { getAllByTestId } = render(<UsersList users={allUsers} showOnlyVIP={true}/>); // Assert - Mix UI & data in assertion expect(getAllByTestId('user')).toEqual('[<li data-testid="user">John Doe</li>]'); }); 


3.2 HTML- ,


. HTML- , . , , CSS-. , 'test-id-submit-button'. . , , .

. , , . тАФ , , Ajax . . , CSS 'thick-border' 'thin-border'

. , .

 // the markup code (part of React component) <b> <Badge pill className="fixed_badge" variant="dark"> <span data-testid="errorsLabel">{value}</span> <!-- note the attribute data-testid --> </Badge> </b> // this example is using react-testing-library test('Whenever no data is passed to metric, show 0 as default', () => { // Arrange const metricValue = undefined; // Act const { getByTestId } = render(<dashboardMetric value={undefined}/>); expect(getByTestId('errorsLabel')).text()).toBe("0"); }); 

. CSS-.

 <!-- the markup code (part of React component) --> <span id="metric" className="d-flex-column">{value}</span> <!-- what if the designer changes the classs? --> // this exammple is using enzyme test('Whenever no data is passed, error metric shows zero', () => { // ... expect(wrapper.find("[className='d-flex-column']").text()).toBe("0"); }); 


3.3


. , , . , , . , тАФ - , (. ┬л ┬╗ ). (, ) , .

, : , . ( ) . , .

. , . ?

. .

 class Calendar extends React.Component { static defaultProps = {showFilters: false} render() { return ( <div> A filters panel with a button to hide/show filters <FiltersPanel showFilter={showFilters} title='Choose Filters'/> </div> ) } } //Examples use React & Enzyme test('Realistic approach: When clicked to show filters, filters are displayed', () => { // Arrange const wrapper = mount(<Calendar showFilters={false} />) // Act wrapper.find('button').simulate('click'); // Assert expect(wrapper.text().includes('Choose Filter')); // This is how the user will approach this element: by text }) 

. .

 test('Shallow/mocked approach: When clicked to show filters, filters are displayed', () => { // Arrange const wrapper = shallow(<Calendar showFilters={false} title='Choose Filter'/>) // Act wrapper.find('filtersPanel').instance().showFilters(); // Tap into the internals, bypass the UI and invoke a method. White-box approach // Assert expect(wrapper.find('Filter').props()).toEqual({title: 'Choose Filter'}); // what if we change the prop name or don't pass anything relevant? }) 


3.4 .


. (, ). (, setTimeOut ) , . (, Cypress cy.request('url') ), API, wait(expect(element)) @testing-library/DOM . , API, , . , , hurry-up the clock . тАФ , , ( ). , , - npm- , , wait-for-expect .

. , . , . .

. E2E API (Cypress).

 // using Cypress cy.get('#show-products').click()// navigate cy.wait('@products')// wait for route to appear // this line will get executed only when the route is ready 

. , DOM- (@testing-library/dom).
 // @testing-library/dom test('movie title appears', async () => { // element is initially not present... // wait for appearance await wait(() => { expect(getByText('the lion king')).toBeInTheDocument() }) // wait for appearance and return the element const movie = await waitForElement(() => getByText('the lion king')) }) 

. .

 test('movie title appears', async () => { // element is initially not present... // custom wait logic (caution: simplistic, no timeout) const interval = setInterval(() => { const found = getByText('the lion king'); if(found){ clearInterval(interval); expect(getByText('the lion king')).toBeInTheDocument(); } }, 100); // wait for appearance and return the element const movie = await waitForElement(() => getByText('the lion king')) }) 


3.5.


. - , . , , . : pingdom , AWS CloudWatch gcp StackDriver , , SLA. , , (, lighthouse , pagespeed ), . тАФ , : , (TTI) . , , , , , DOM, SSL . , CI, 247 CDN.

. , , , , - CDN.

. Lighthouse .



3.6 API


. ( 2), , , ( ). API (, Sinon , Test doubles ), API. . API , ( ). API, . , , API . , : .

. , API 100 , 20 .

. API-.

 // unit under test export default function ProductsList() { const [products, setProducts] = useState(false) const fetchProducts = async() => { const products = await axios.get('api/products') setProducts(products); } useEffect(() => { fetchProducts(); }, []); return products ? <div>{products}</div> : <div data-testid='no-products-message'>No products</div> } // test test('When no products exist, show the appropriate message', () => { // Arrange nock("api") .get(`/products`) .reply(404); // Act const {getByTestId} = render(<ProductsList/>); // Assert expect(getByTestId('no-products-message')).toBeTruthy(); }); 


3.7 ,


. E2E (end-to-end, ) UI (. 3.6). , , . , , - . , тАФ (, ), . - , , UI- Cypress Pupeteer . , : 50 , , . 10 . , , , тАФ . , .

. UI , , ( , UI) .

3.8


. , API , , . (before-all), - . , : . , . - API- . , . (, ), , , . , : , API (. 3.6).

. , 200 , 100 , 20 .

. (before-all), (before-each) (, Cypress).

Cypress .

 let authenticationToken; // happens before ALL tests run before(() => { cy.request('POST', 'http://localhost:3000/login', { username: Cypress.env('username'), password: Cypress.env('password'), }) .its('body') .then((responseFromLogin) => { authenticationToken = responseFromLogin.token; }) }) // happens before EACH test beforeEach(setUser => () { cy.visit('/home', { onBeforeLoad (win) { win.localStorage.setItem('token', JSON.stringify(authenticationToken)) }, }) }) 


3.9 smoke-,


. production- , , . , , , , . smoke- . production, , , . , smoke- , .

. , , production . /Payment.

. Smoke- .

 it('When doing smoke testing over all page, should load them all successfully', () => { // exemplified using Cypress but can be implemented easily // using any E2E suite cy.visit('https://mysite.com/home'); cy.contains('Home'); cy.contains('https://mysite.com/Login'); cy.contains('Login'); cy.contains('https://mysite.com/About'); cy.contains('About'); }) 


3.10


. , . ┬л┬╗ , , , , . , ( ) , , -, , , . ┬л ┬╗, . . , Cucumber JavaScript . StoryBook UI- , (, , , ..) , . , , .

. , .

. cucumber-js.

 // this is how one can describe tests using cucumber: plain language that allows anyone to understand and collaborate Feature: Twitter new tweet I want to tweet something in Twitter @focus Scenario: Tweeting from the home page Given I open Twitter home Given I click on "New tweet" button Given I type "Hello followers!" in the textbox Given I click on "Submit" button Then I see message "Tweet saved" 

. Storybook , .



3.11


. , . , . , . , - . , . , , , . , - . UI ┬л ┬╗. , (, wraith , PhantomCSS), . (, Applitools , Perci.io ) , , ┬л ┬╗ (, ), DOM/CSS, .

. , ( ) , ?

. : , .



. wraith UI.

 тАЛ# Add as many domains as necessary. Key will act as a labelтАЛ domains: english: "http://www.mysite.com"тАЛ тАЛ# Type screen widths below, here are a couple of examplesтАЛ screen_widths: - 600тАЛ - 768тАЛ - 1024тАЛ - 1280тАЛ тАЛ# Type page URL paths below, here are a couple of examplesтАЛ paths: about: path: /about selector: '.about'тАЛ subscribe: selector: '.subscribe'тАЛ path: /subscribe 


4:


4.1 (~80 %),


. тАФ , . , . тАФ (, ), . ? , 10-30 % . 100 % , . . , : Airbus, ; , 50 % . , , 80 % ( Fowler: ┬лin the upper 80s or 90s┬╗ ), , , .

: (CI), ( Jest ) , . , . , ( ) тАФ . , тАФ , , . , .

. . , , . .

.



. ( Jest).



4.2 ,


. , . , , , , . , , - , . PricingCalculator , , , 10 000 тАж , , . , . 80- , . : , , , . , - .

. , , , .

. ? , QA . : , - . , - API .




4.3


. : 100 %, . рдРрд╕рд╛ рдХреИрд╕реЗ? , , , . . - : , , , .

, . JavaScript- Stryker :

  1. ┬л ┬╗. , newOrder.price===0 newOrder.price!=0 . ┬л┬╗ .
  2. , , : , . , , .

, , , .

. , 85- 85 % .

. 100 %, 0 %.

 function addNewOrder(newOrder) { logger.log(`Adding new order ${newOrder}`); DB.save(newOrder); Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`); return {approved: true}; } it("Test addNewOrder, don't use such test names", () => { addNewOrder({asignee: "John@mailer.com",price: 120}); });//Triggers 100% code coverage, but it doesn't check anything 

. Stryker reports, , ().



4.4 -


. ESLint. , eslint-plugin-mocha , ( describe() ), , . eslint-plugin-jest , ( ).

. 90- , , , . , .

. , , .

 describe("Too short description", () => { const userToken = userService.getDefaultToken() // *error:no-setup-in-describe, use hooks (sparingly) instead it("Some description", () => {});//* error: valid-test-description. Must include the word "Should" + at least 5 words }); it.skip("Test name", () => {// *error:no-skipped-tests, error:error:no-global-tests. Put tests only under describe or suite expect("somevalue"); // error:no-assert }); it("Test name", () => {*//error:no-identical-title. Assign unique titles to tests }); 


5: CI


5.1 ,


. тАФ . , . , ( !). , . ( ESLint standard Airbnb ), . , eslint-plugin-chai-expect , . Eslint-plugin-promise ( ). Eslint-plugin-security , DOS-. eslint-plugin-you-dont-need-lodash-underscore , , V8, , Lodash._map(тАж) .

. , , . рдХреНрдпрд╛ рдЪрд▓ рд░рд╣рд╛ рд╣реИ? , , . . , , .

. , . , ESLint production-.



5.2


. CI , , ..? , . рдХреНрдпреЛрдВ? : (1) -> (2) -> (3) . , , .

, , , , - .

CI- ( , CircleCI local CLI ) . , wallaby , ( ) . npm- package.json, , (, , , ). (non-zero exit code) concurrently . тАФ , npm run quality . githook ( husky ).

. , .

. Npm-, , , .

 "scripts": { "inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"", "inspect:lint": "eslint .", "inspect:vulnerabilities": "npm audit", "inspect:license": "license-checker --failOn GPLv2", "inspect:complexity": "plato .", "inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\"" }, "husky": { "hooks": { "precommit": "npm run inspect:all", "prepush": "npm run inspect:all" } } 


5.3 production-


. тАФ CI-. . тАФ Docker-compose . (, ) production-. AWS Local AWS-. , serverless AWS SAM Faas-.

Kubernetes CI-, . , ┬л Kubernetes┬╗ Minikube MicroK8s , , . ┬л Kubernetes┬╗: CI- (, Codefresh ) Kubernetes-, CI- ; .

. .

: CI-, Kubernetes- (Dynamic-environments Kubernetes )

 deploy: stage: deploy image: registry.gitlab.com/gitlab-examples/kubernetes-deploy script: - ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN - kubectl create ns $NAMESPACE - kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL" - mkdir .generated - echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF" - sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml" - kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml - kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml environment: name: test-for-ci 


5.4


. , , . , 500 , , . , CI- ( Jest , AVA Mocha ) , . CI- (!), . , CLI , , .

. тАФ , .

. Mocha parallel Jest Mocha ( JavaScript Test-Runners Benchmark )



5.5


. , . 10 ? CI- npm- license check plagiarism check ( ), , , Stackoveflow .

. , , .

.
 //install license-checker in your CI environment or also locally npm install -g license-checker //ask it to scan all licenses and fail with exit code other than 0 if it found unauthorized license. The CI system should catch this failure and stop the build license-checker --summary --failOn BSD 



5.6


. , Express, . npm audit , snyk ( ). CI .

. . .

: NPM Audit



5.7


. package-lock.json Yarn npm ( ): . npm install npm update , . , тАФ . , package.json ncu .

, . :

  • CI , , npm outdated npm-check-updates (ncu). .
  • , pull request' .

: ? , ( , eslint-scope ). ┬л ┬╗: latest , , (, 1.3.1, тАФ 1.3.8).

. , .

: , , ncu CI-.



5.8 CI-, Node


. , Node . , Node.

  1. . , Jenkins .
  2. Docker.
  3. , . . smoke-, (, , ) .
  4. , , , , , .
  5. , . , -. - ( ).
  6. . .
  7. , , .
  8. (, Docker-).
  9. , . node_modules .

. , .

5.9 : CI-, Node


. , , . Node, CI . , MySQL, Postgres. CI- ┬л┬╗, MySQl, Postgres Node. , - (, ). CI, , .

. - ?

: Travis ( CI) Node.

 language: node_js node_js: - "7" - "6" - "5" - "4" install: - npm install script: - npm run test 

Source: https://habr.com/ru/post/hi466879/


All Articles