
ð2022幎4æã®çºè¡šïŒ5ã€ã®æ°ãããã¹ããã©ã¯ãã£ã¹ãããã«å€ãã®ã³ãŒãäŸã4ã€ã®æ°ããèšèªç¿»èš³ãå«ãæ°ãããšãã£ã·ã§ã³ããªãªãŒã¹ãããŸããã
ðšâð«æ¬¡ã®ã¯ãŒã¯ã·ã§ããïŒã€ã¿ãªã¢ããŽã§ããŒãð®ð¹ã4æ20æ¥ããã±ãããšè©³çްã¯ãã¡ã
ðãã®ã¬ã€ãããã¹ãã¹ãã«ã次ã®ã¬ãã«ã«åŒãäžããããšãã§ããçç±
ð50以äžã®ãã¹ããã©ã¯ãã£ã¹ïŒéåžžã«å
æ¬çã§ç¶²çŸ
ç
ããã¯ãAZã®JavaScriptãšNode.jsã®ä¿¡é Œæ§ã«é¢ããã¬ã€ãã§ããããã¯ããªãã®ããã«åžå ŽãæäŸããªããã°ãªããªãäœåãã®æé«ã®ããã°æçš¿ãæ¬ãšããŒã«ãèŠçŽããŠãã¥ã¬ãŒãããŸã
ð¢äžçŽïŒåºæ¬ã10,000ãã€ã«è¶
ããŸã
åºæ¬ãè¶
ããŠãæ¬çªç°å¢ã§ã®ãã¹ãããã¥ãŒããŒã·ã§ã³ãã¹ããããããã£ããŒã¹ã®ãã¹ãããã®ä»å€ãã®æŠç¥çããã³å°éçãªããŒã«ãªã©ã®é«åºŠãªãããã¯ã«é²ãæ
ã«é£ã³èŸŒã¿ãŸãããããã®ã¬ã€ãã®ãã¹ãŠã®åèªãèªããšããã¹ãã¹ãã«ã¯å¹³åãã¯ããã«è¶
ããå¯èœæ§ããããŸã
ðãã«ã¹ã¿ãã¯ïŒããã³ããããã¯ãšã³ããCIããã®ä»
ãŸããããããã¢ããªã±ãŒã·ã§ã³å±€ã®åºç€ãšãªããŠããã¿ã¹ãªãã¹ãææ³ãçè§£ããããšããå§ããŸããæ¬¡ã«ãéžæããé åïŒããã³ããšã³ã/ UIãããã¯ãšã³ããCIããŸãã¯ããããã¹ãŠïŒã詳ãã調ã¹ãŸãã
ãšããŽãŒã«ãããŒã°èæ¬ã®äœå
翻蚳-ããªãèªèº«ã®èšèªã§èªã
Table of Contents
ä»ã®ãã¹ãŠãåºæ¿ããåäžã®ã¢ããã€ã¹ïŒ1ã€ã®ç¹å¥ãªç®æ¡æžãïŒ
åºç€-ã¯ãªãŒã³ãã¹ãã®æ§ç¯ïŒ12匟ïŒ
ããã¯ãšã³ããšãã€ã¯ããµãŒãã¹ã®ãã¹ããå¹ççã«äœæããïŒ13ç®æ¡æžãïŒ
ã³ã³ããŒãã³ããã¹ããšE2Eãã¹ããå«ãWebUIã®ãã¹ãã®äœæïŒ11ç®æ¡æžãïŒ
èŠåå¡ãç£èŠãã-ãã¹ãåè³ªã®æž¬å®ïŒ4ã€ã®åŒŸäžžïŒ
JSã®äžçã«ãããCIã®ã¬ã€ãã©ã€ã³ïŒ9ç®æ¡æžãïŒ
ã»ã¯ã·ã§ã³0ïžâ£ïŒé»éåŸ
âªïž0é»éåŸïŒç¡é§ã®ãªããã¹ãã®ããã®èšèš
â
宿œïŒ
ãã¹ãã³ãŒãã¯æ¬çªã³ãŒãã§ã¯ãããŸãã-çããéåžžã«ã·ã³ãã«ã§ããã©ããã§ãæäœããããããã«èšèšããŠãã ããããã¹ããèŠãŠãããã«æå³ãçè§£ããå¿
èŠããããŸãã
ã»ããç§ãã¡ã®å¿ã¯ãã§ã«ç§ãã¡ã®äž»ãªä»äºã§ãããããã¯ã·ã§ã³ã³ãŒãã«å°å¿µããŠããŸããããã«è€éã«ããããã®ããããã¹ããŒã¹ãã¯ãããŸãããããã«å¥ã®susã·ã¹ãã ã貧匱ãªè³ã«æŒã蟌ãããšãããšãããŒã ã®é床ãäœäžãããã¹ããè¡ãçç±ã«åããŸããå®éã«ã¯ãããã¯å€ãã®ããŒã ããã¹ããæŸæ£ããå Žæã§ãã
ãã¹ãã¯ä»ã®äœãã®ããã®æ©äŒã§ã-å°ããªæè³ã§å€§ããªäŸ¡å€ãæäŸãããã¬ã³ããªãŒãªã¢ã·ã¹ã¿ã³ããå¯æçžŠå£«ãç§åŠã«ãããšãç§ãã¡ã«ã¯2ã€ã®è³ã·ã¹ãã ããããŸããã·ã¹ãã 1ã¯ç©ºã®éè·¯ã§è»ãé転ãããªã©ã®æ¥œãªæŽ»åã«äœ¿çšãããã·ã¹ãã 2ã¯æ°åŠã®æ¹çšåŒãè§£ããªã©ã®è€éã§æèçãªæäœãç®çãšããŠããŸããã·ã¹ãã 1ã®ãã¹ããèšèšããŸãããã¹ãã³ãŒããèŠããšã2XïŒ17Ã24ïŒãè§£ãã®ã§ã¯ãªããHTMLããã¥ã¡ã³ãã倿Žããã®ãšåããããç°¡åã«æããã¯ãã§ãã
ããã¯ãè²»çšå¯Ÿå¹æãé«ããåªããROIãæäŸãããéžæçã«ãã§ãªãŒãããã³ã°ããææ³ãããŒã«ãããã³ãã¹ãã¿ãŒã²ããã«ãã£ãŠå®çŸã§ããŸããå¿
èŠãªã ããã¹ãããæ©æã«ä¿ã€ããã«åªããŸããå Žåã«ãã£ãŠã¯ãããã€ãã®ãã¹ããäžæ¢ããä¿¡é Œæ§ãææ·æ§ãšåçŽããšåŒãæãã«ãã䟡å€ãããããŸãã

以äžã®ã¢ããã€ã¹ã®ã»ãšãã©ã¯ããã®ååã®æŽŸçç©ã§ãã
å§ããæºåã¯ã§ããŸãããïŒ
ã»ã¯ã·ã§ã³1ïŒãã¹ãã®æ§é
âªïž1.1åãã¹ãåã«3ã€ã®éšåãå«ãã
â
宿œïŒãã¹ãã¬ããŒãã§ã¯ãçŸåšã®ã¢ããªã±ãŒã·ã§ã³ãªããžã§ã³ããã³ãŒãã«å¿
ããã粟éããŠããªã人ã
ïŒãã¹ã¿ãŒããããã€ããŠããDevOpsãšã³ãžãã¢ãããã³2幎åŸã®å°æ¥ïŒã®èŠä»¶ãæºãããŠãããã©ããã確èªããå¿
èŠããããŸããããã¯ããã¹ããèŠä»¶ã¬ãã«ã§èšè¿°ãããæ¬¡ã®3ã€ã®éšåãå«ãŸããŠããå Žåã«æãããéæã§ããŸãã
ïŒ1ïŒäœããã¹ããããŠããŸããïŒããšãã°ãProductsService.addNewProductã¡ãœãã
ïŒ2ïŒã©ã®ãããªç¶æ³ãšã·ããªãªã§ïŒããšãã°ãã¡ãœããã«äŸ¡æ Œãæž¡ãããããšã¯ãããŸãã
ïŒ3ïŒæåŸ
ãããçµæã¯äœã§ããïŒããšãã°ãæ°è£œåã¯æ¿èªãããŠããŸãã
â ãã以å€ã®å ŽåïŒå±éã倱æããã°ããã§ãã補åã®è¿œå ããšããååã®ãã¹ãã倱æããŸãããããã¯ãæ£ç¢ºã«äœã誀åäœããŠããã®ããæããŠãããŸããïŒ
ðæ³šïŒåç®æ¡æžãã«ã¯ã³ãŒãäŸããããå Žåã«ãã£ãŠã¯ç»åã®ã€ã©ã¹ãããããŸããã¯ãªãã¯ããŠå±é
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸïŒ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');
});
});
});
ðæ£ããè¡ãäŸïŒ3ã€ã®éšåãæ§æãããã¹ãå

©ã¯ã¬ãžããïŒç¶ããèªã
1.RoyOsherove-ãŠããããã¹ãã®åœååºæº
âªïž1.2AAAãã¿ãŒã³ã«ããæ§é ãã¹ã
â
宿œïŒã¢ã¬ã³ãžãã¢ã¯ããã¢ãµãŒã·ã§ã³ïŒAAAïŒã®3ã€ã®ã»ã¯ã·ã§ã³ã§ãã¹ããæ§æããŸãããã®æ§é ã«åŸãããšã§ãèªè
ããã¹ãèšç»ã®çè§£ã«é è³CPUãè²»ãããªãããšãä¿èšŒãããŸãã
1çªç®ã®A-é
眮ïŒãã¹ããã·ãã¥ã¬ãŒãããããšãç®çãšããã·ããªãªã«ã·ã¹ãã ãå°å
¥ããããã®ãã¹ãŠã®ã»ããã¢ããã³ãŒããããã«ã¯ããã¹ãäžã®ãŠãããã³ã³ã¹ãã©ã¯ã¿ãŒã®ã€ã³ã¹ã¿ã³ã¹åãDBã¬ã³ãŒãã®è¿œå ããªããžã§ã¯ãã®ã¢ãã¯/ã¹ã¿ããããã³ãã®ä»ã®æºåã³ãŒããå«ãŸããå ŽåããããŸãã
2çªç®ã®A-è¡çºïŒãã¹ãäžã®ãŠããããå®è¡ããŸããéåžžã1è¡ã®ã³ãŒã
3çªç®ã®A-ã¢ãµãŒãïŒåä¿¡ããå€ãæåŸ
å€ãæºãããŠããããšã確èªããŸããéåžžã1è¡ã®ã³ãŒã
â ãã以å€ã®å ŽåïŒã¡ã€ã³ã³ãŒããçè§£ããã®ã«äœæéãè²»ããã ãã§ãªãã1æ¥ã®æãåçŽãªéšåïŒãã¹ãïŒã§ããã¯ãã ã£ãããšãè³ã䌞ã°ããŸã
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸïŒAAAãã¿ãŒã³ã§æ§é åããããã¹ã

describe("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");
});
});
ðã¢ã³ããã¿ãŒã³ã®äŸïŒåé¢ãªãã1ã€ã®ãã«ã¯ãè§£éãé£ãã
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ã¹ã¿ã€ã«ã®ã¢ãµãŒã·ã§ã³ã䜿çšãã
â
宿œïŒãã¹ãã宣èšåã§ã³ãŒãã£ã³ã°ããããšã§ãèªè
ã¯è³ãšCPUã®ãµã€ã¯ã«ã1ã€ãè²»ããããšãªããå³åº§ã«æã«å
¥ããããšãã§ããŸããæ¡ä»¶ä»ãããžãã¯ãè©°ã蟌ãŸããåœä»€åã³ãŒããæžããšããªãŒããŒã¯ããå€ãã®ãã¬ã€ã³CPUãµã€ã¯ã«ãå®è¡ããããšãäœåãªããããŸãããã®å Žåãã«ã¹ã¿ã ã³ãŒãã䜿çšãã
expect
ãã©ããã«ãããããã人éã®ãããªèšèªã宣èšåBDDã¹ã¿ã€ã«ã§æåŸ
å€ãã³ãŒãã£ã³ã°ããŸãã
should
ChaiïŒJestã«ç®çã®ã¢ãµãŒã·ã§ã³ãå«ãŸããŠããããåçŸæ§ãé«ãå Žåã¯ã
Jestãããã£ãŒïŒJestïŒãæ¡åŒµãããã
ã«ã¹ã¿ã Chaiãã©ã°ã€ã³ãäœæããããšãæ€èšããŠãã ããã
â ãã以å€ã®å ŽåïŒããŒã ã¯ãã¹ãã®èšè¿°ãæžãããè¿·æãªãã¹ãã.skipïŒïŒã§è£
食ããŸãã
â ã³ãŒãäŸ

ðã¢ã³ããã¿ãŒã³ã®äŸïŒèªè
ã¯ããã¹ãã¹ããŒãªãŒãååŸããããã ãã«ãããã»ã©çããªããåœä»€åã®ã³ãŒãããã£ãšèªãå¿
èŠããããŸãã
test("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 });
let 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ãã©ãã¯ããã¯ã¹ãã¹ãã«åºå·ããïŒãããªãã¯ã¡ãœããã®ã¿ããã¹ããã
â
宿œïŒå
éšããã¹ããããšãã»ãšãã©äœãããã«å€§ããªãªãŒããŒããããçºçããŸããã³ãŒã/APIãæ£ããçµæãæäŸããå Žåãå
éšã§ã©ã®ããã«æ©èœãããããã¹ãããããã«æ¬¡ã®3æéãæ¬åœã«æè³ããŠããããããã®è匱ãªãã¹ããç¶æããå¿
èŠããããŸããïŒãããªãã¯åäœããã§ãã¯ããããšãã¯ãã€ã§ãããã©ã€ããŒãå®è£
ãæé»çã«ãã¹ããããç¹å®ã®åé¡ïŒããšãã°ãééã£ãåºåïŒãããå Žåã«ã®ã¿ãã¹ãã倱æããŸãããã®ã¢ãããŒãã¯ããšãåŒã°ã
behavioral testing
ãŸããäžæ¹ãå
éšããã¹ãããå¿
èŠãããå ŽåïŒãã¯ã€ãããã¯ã¹ã¢ãããŒãïŒ-ã³ã³ããŒãã³ãã®çµæã®èšç»ããæ¬è³ªçãªè©³çްã«çŠç¹ãç§»ããçµæã¯è¯å¥œã§ããããã€ããŒãªã³ãŒããªãã¡ã¯ã¿ãŒã®ããã«ãã¹ãã倱æããå¯èœæ§ããããŸã-ããã«ãããã¡ã³ããã³ã¹ãåçã«å¢å ããŸãè² æ
â ãã以å€ã®å ŽåïŒãã¹ãã¯ããªãªã«ããæ³£ããå°å¹Žã®ããã«åäœããŸãïŒåœéœæ§ã®å«ã³ãå«ã³ãŸãïŒããšãã°ããã©ã€ããŒã倿°åã倿Žãããããããã¹ãã¯å€±æããŸãïŒãåœç¶ã®ããšãªããã人ã
ã¯ããã«CIéç¥ãç¡èŠãå§ãããã€ã®æ¥ããæ¬åœã®ãã°ãç¡èŠãããããã«ãªããŸãâŠ
â ã³ãŒãäŸ
ðã¢ã³ããã¿ãŒã³ã®äŸïŒãã¹ãã±ãŒã¹ã¯ãæ£åœãªçç±ããªãå
éšããã¹ãããŠããŸã

class ProductService {
//this method is only used internally
//Change this name will make the tests fail
calculateVATAdd(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;
return 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é©åãªãã¹ãããã«ãéžæããŠãã ããïŒã¹ã¿ããšã¹ãã€ãæ¯æããŠã¢ãã¯ãé¿ããŠãã ãã
â
宿œïŒãã¹ãããã«ã¯ã¢ããªã±ãŒã·ã§ã³ã®å
éšã«çµåãããŠãããããå¿
èŠãªæªã§ãããããã€ãã¯èšãç¥ããªã䟡å€ãæäŸããŸãïŒãã¹ãããã«ã«ã€ããŠã®ãªãã€ã³ããŒãããã§èªãã§ãã ããïŒã¢ãã¯vsã¹ã¿ãvsã¹ãã€ïŒã
ãã¹ãããã«ã䜿çšããåã«ãéåžžã«ç°¡åãªè³ªåãããŠãã ãããèŠä»¶ããã¥ã¡ã³ãã«è¡šç€ºãããããŸãã¯è¡šç€ºãããå¯èœæ§ã®ããæ©èœããã¹ãããããã«äœ¿çšããŸããïŒãããã®å Žåãããã¯ãã¯ã€ãããã¯ã¹ãã¹ãã®åãã§ãã
ããšãã°ãæ¯æããµãŒãã¹ãããŠã³ããŠãããšãã«ã¢ããªãé©åã«åäœããããšããã¹ãããå Žåã¯ãæ¯æããµãŒãã¹ãã¹ã¿ããããå¿çãªããã®æ»ããããªã¬ãŒããŠããã¹ã察象ã®ãŠããããæ£ããå€ãè¿ãããšã確èªããŸããããã«ãããç¹å®ã®ã·ããªãªã§ã®ã¢ããªã±ãŒã·ã§ã³ã®åäœ/å¿ç/çµæããã§ãã¯ãããŸããã¹ãã€ã䜿çšããŠããã®ãµãŒãã¹ãããŠã³ãããšãã«é»åã¡ãŒã«ãéä¿¡ãããããšã衚æããããšãã§ããŸããããããèŠä»¶ããã¥ã¡ã³ãã«è¡šç€ºãããå¯èœæ§ã®ããåäœãã§ãã¯ã§ãïŒãæ¯æããä¿åã§ããªãã£ãå Žåã¯é»åã¡ãŒã«ãéä¿¡ããŠãã ãããïŒãå察ã«ãPaymentãµãŒãã¹ãã¢ãã¯ããŠãé©åãªJavaScriptã¿ã€ãã§åŒã³åºãããããšã確èªãããšããã¹ãã¯ãã¢ããªã±ãŒã·ã§ã³ã®æ©èœãšã¯é¢ä¿ããªããé »ç¹ã«å€æŽãããå¯èœæ§ã®ããå
éšçãªãã®ã«çŠç¹ãåœãŠãããŸãã
â ãã以å€ã®å ŽåïŒã³ãŒãã®ãªãã¡ã¯ã¿ãªã³ã°ã§ã¯ãã³ãŒãå
ã®ãã¹ãŠã®ã¢ãã¯ãæ€çŽ¢ããããã«å¿ããŠæŽæ°ããå¿
èŠããããŸãããã¹ãã¯èŠªåãªåéã§ã¯ãªãè² æ
ã«ãªããŸã
â ã³ãŒãäŸ
ðã¢ã³ããã¿ãŒã³ã®äŸïŒã¢ãã¯ã¯å
éšã«çŠç¹ãåœãŠãŠããŸã

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);
dataAccessMock.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)
expect(spy.calledOnce).to.be.true;
});
ðã©ã€ããããªã§ããããã¹ãŠã®ãã©ã¯ãã£ã¹ãåŠã³ããã§ããïŒ
âªïž1.6ãfooãããªãã§ãçŸå®çãªå
¥åããŒã¿ã䜿çšããŠãã ãã
â
宿œïŒå€ãã®å Žåãæ¬çªç°å¢ã®ãã°ã¯ãéåžžã«å
·äœçã§é©ãã¹ãå
¥åã®äžã§æããã«ãªããŸãããã¹ãå
¥åãçŸå®çã§ããã»ã©ããã°ãæ©æã«çºèŠã§ããå¯èœæ§ãé«ããªããŸããChanceãFakerãªã©ã®å°çšã©ã€ãã©ãªã䜿çšããŠãæ¬çªããŒã¿ã®å€æ§æ§ãšåœ¢åŒã«äŒŒãç䌌å®ããŒã¿ãçæããŸããããšãã°ããã®ãããªã©ã€ãã©ãªã¯ãçŸå®çãªé»è©±çªå·ããŠãŒã¶ãŒåãã¯ã¬ãžããã«ãŒããäŒç€Ÿåãããã«ã¯ãloremipsumãããã¹ããçæã§ããŸãããŸããåœé è
ã®ããŒã¿ãã©ã³ãã åããŠãã¹ã察象ã®ãŠããããæ¡åŒµããããå®çšŒåç°å¢ããå®éã®ããŒã¿ãã€ã³ããŒãããããããã¹ããïŒãŠããããã¹ãã«å ããŠã代æ¿ãšããŠã§ã¯ãªãïŒäœæããããšãã§ããŸãããããæ¬¡ã®ã¬ãã«ã«åŒãäžãããã§ããïŒæ¬¡ã®ç®æ¡æžãïŒããããã£ããŒã¹ã®ãã¹ãïŒãåç
§ããŠãã ããã
â ãã以å€ã®å ŽåïŒãFooããªã©ã®åæå
¥åã䜿çšãããšããã¹ãŠã®éçºãã¹ãã§èª€ã£ãŠç·è²ã衚瀺ãããŸãããããã«ãŒãã@ 3e2ddsfããªã©ã®åä»ãªæååãæž¡ããšãæ¬çªç°å¢ãèµ€è²ã«å€ããå¯èœæ§ããããŸãã##'1fdsfdsãfds432AAAAã
â ã³ãŒãäŸ
ðã¢ã³ããã¿ãŒã³ã®äŸïŒéçŸå®çãªããŒã¿ãåå ã§åæ Œãããã¹ãã¹ã€ãŒã

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).toBe(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ããããã£ããŒã¹ã®ãã¹ãã䜿çšããŠå€ãã®å
¥åã®çµã¿åããããã¹ãããŸã
â
宿œïŒéåžžããã¹ãããšã«ããã€ãã®å
¥åãµã³ãã«ãéžæããŸããå
¥å圢åŒãå®éã®ããŒã¿ã«äŒŒãŠããå Žåã§ãïŒç®æ¡æžã'Do n't foo'ãåç
§ïŒãããã€ãã®å
¥åã®çµã¿åããïŒmethodïŒ''ãtrueã1ïŒãmethodïŒ "string"ãfalseã0ïŒïŒã®ã¿ãåãäžããŸãã ããã ããæ¬çªç°å¢ã§ã¯ã5ã€ã®ãã©ã¡ãŒã¿ãŒã§åŒã³åºãããAPIãæ°åã®ç°ãªãé åã§åŒã³åºãããšãã§ãããã®ãã¡ã®1ã€ã§ããã»ã¹ãããŠã³ããå¯èœæ§ããããŸãïŒãã¡ãºãã¹ããåç
§ïŒãïŒãããŸããŸãªå
¥åã®1000ã®é åãèªåçã«éä¿¡ããã³ãŒããæ£ããå¿çãè¿ããªãå
¥åããã£ããããåäžã®ãã¹ããäœæã§ãããšãããã©ãã§ãããããããããã£ããŒã¹ã®ãã¹ãã¯ããŸãã«ãããè¡ãææ³ã§ããå¯èœãªãã¹ãŠã®å
¥åã®çµã¿åããããã¹ã察象ã®ãŠãããã«éä¿¡ããããšã§ããã°ãèŠã€ããå¯èœæ§ãé«ãŸããŸããããšãã°ãã¡ãœããïŒaddNewProductïŒidãnameãisDiscountïŒïŒãäžãããããšããµããŒãã©ã€ãã©ãªã¯ãïŒ1ãâ iPhoneâãfalseïŒãïŒ2ãâ GalaxyïŒã®ãããªïŒnumberãstringãbooleanïŒã®å€ãã®çµã¿åããã§ãã®ã¡ãœãããåŒã³åºããŸãã "ã çå®ïŒãjs-verifyãtestcheckïŒã¯ããã«åªããããã¥ã¡ã³ãïŒãªã©ã®ã©ã€ãã©ãªã䜿çšããŠããæ°ã«å
¥ãã®ãã¹ãã©ã³ããŒïŒMochaãJestãªã©ïŒã䜿çšããŠããããã£ããŒã¹ã®ãã¹ããå®è¡ã§ããŸããæŽæ°ïŒNicolas Dubienã¯ã以äžã®ã³ã¡ã³ãã§æ¬¡ã®ããã«ææ¡ããŠããŸããããã€ãã®è¿œå æ©èœãæäŸããç©æ¥µçã«ç¶æãããŠããããã«èŠãã
ãã§ãã¯ã¢ãŠããã¡ã¹ããã§ãã¯
â ãã以å€ã®å ŽåïŒç¡æèã®ãã¡ã«ãé©åã«æ©èœããã³ãŒããã¹ã®ã¿ãã«ããŒãããã¹ãå
¥åãéžæããŸããæ®å¿µãªãããããã¯ãã°ãæããã«ããããã®ææ®µãšããŠã®ãã¹ãã®å¹çãäœäžãããŸã
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸïŒãé«éãã§ãã¯ãã䜿çšããŠå€ãã®å
¥åé åããã¹ããã

import fc from "fast-check";
describe("Product service", () => {
describe("Adding new", () => {
//this will run 100 times with different random properties
it("Add new product with random yet valid properties, always successful", () =>
fc.assert(
fc.property(fc.integer(), fc.string(), (id, name) => {
expect(addNewProduct(id, name).status).toEqual("approved");
})
));
});
});
âªïž1.8å¿
èŠã«å¿ããŠãçãã€ã³ã©ã€ã³ã¹ãããã·ã§ããã®ã¿ã䜿çšããŠãã ãã
â
宿œïŒã¹ãããã·ã§ãããã¹ããå¿
èŠãªå Žåã¯ããã¹ãïŒã€ã³ã©ã€ã³ã¹ãããã·ã§ããïŒã®äžéšãšããŠå«ãŸããå€éšãã¡ã€ã«ã«ã¯å«ãŸããªããçãçŠç¹ãçµã£ãã¹ãããã·ã§ããïŒã€ãŸãã3ã7è¡ïŒã®ã¿ã䜿çšããŸãããã®ã¬ã€ãã©ã€ã³ãå®ãããšã§ããã¹ããèªæã§è匱æ§ãå°ãªããªãããšãä¿èšŒãããŸãã
äžæ¹ããã¯ã©ã·ãã¯ã¹ãããã·ã§ãããã®ãã¥ãŒããªã¢ã«ãšããŒã«ã§ã¯ã倧ããªãã¡ã€ã«ïŒã³ã³ããŒãã³ãã¬ã³ããªã³ã°ããŒã¯ã¢ãããAPI JSONçµæãªã©ïŒãå€éšã¡ãã£ã¢ã«ä¿åãããã¹ããå®è¡ãããã³ã«ãåä¿¡ããçµæãšä¿åããããŒãžã§ã³ãæ¯èŒããããšããå§ãããŸããããã¯ãããšãã°ããã¹ãã©ã€ã¿ãŒãèªã¿åã£ããæšè«ãããããããšã®ãªã3000ã®ããŒã¿å€ãæã€1000è¡ã«ãã¹ããæé»çã«çµåããå¯èœæ§ããããŸãããªããããééã£ãŠããã®ã§ããïŒããããããšã§ããã¹ãã倱æããçç±ã¯1000ãããŸããã¹ãããã·ã§ãããç¡å¹ã«ãªãã«ã¯ã1è¡ã倿Žããã ãã§ååã§ãããããã¯é »ç¹ã«çºçããå¯èœæ§ããããŸããã©ã®ãããã®é »åºŠã§ïŒãã¹ãŠã®ã¹ããŒã¹ãã³ã¡ã³ãããŸãã¯CSS/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
const receivedPage = 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>
`);
});
âªïžã³ãŒããã³ããŒããŸãããå¿
èŠãªãã®ã ãã§ã
â
è¡ãïŒãã¹ãçµæã«åœ±é¿ãäžããå¿
èŠãªè©³çްããã¹ãŠå«ããŸããããã以äžã¯å«ããŸãããäŸãšããŠã100è¡ã®å
¥åJSONãå æ°åè§£ããå¿
èŠããããã¹ããèããŠã¿ãŸããã-ããããã¹ãŠã®ãã¹ãã«è²Œãä»ããã®ã¯é¢åã§ãããããå€éšã§transferFactory.getJSONïŒïŒã«æœåºãããšããã¹ãããããŸãã«ãªããŸã-ããŒã¿ããªããšããã¹ãçµæãåå ãšé¢é£ä»ããã®ã¯å°é£ã§ãïŒããªã400ã¹ããŒã¿ã¹ãè¿ãããšã«ãªã£ãŠããã®ã§ããïŒãïŒããã®ãã¿ãŒã³ãããã¹ããªãŒã²ã¹ãããšåä»ããå€å
žçãªæ¬ã®xãŠããããã¿ãŒã³-ç®ã«èŠããªãäœãããã¹ãçµæã«åœ±é¿ãäžããŸãããæ£ç¢ºã«ã¯äœãããããŸãããç¹°ãè¿ãå¯èœãªé·ãéšåãå€åŽã«æœåºããã©ã®ç¹å®ã®è©³çްããã¹ãã«éèŠã§ããããæç€ºçã«èšåããããšã§ãããè¯ãçµæãåŸãããšãã§ããŸããäžèšã®äŸã䜿çšãããšããã¹ãã¯éèŠãªãã®ã匷調ãããã©ã¡ãŒã¿ãŒãæž¡ãããšãã§ããŸãïŒtransferFactory.getJSONïŒ{senderïŒundefined}ïŒããã®äŸã§ã¯ã
â ãã以å€ã®å ŽåïŒ 500ã®JSONè¡ãã³ããŒãããšããã¹ããä¿å®ã§ããªããªããèªã¿åãäžèœã«ãªããŸãããã¹ãŠãå€ã«ç§»åãããšãçè§£ãã«ããæŒ ç¶ãšãããã¹ãã§çµãããŸã
â ã³ãŒãäŸ
ðã¢ã³ããã¿ãŒã³ã®äŸïŒãã¹ãŠã®åå ãå€éšã«ããã巚倧ãªJSONå
ã«é ããŠããããããã¹ãã®å€±æã¯äžæç¢ºã§ã

test("When no credit, then the transfer is declined", async() => {
// Arrange
const transferRequest = testHelpers.factorMoneyTransfer() //get back 200 lines of JSON;
const transferServiceUnderTest = new TransferService();
// Act
const transferResponse = await transferServiceUnderTest.transfer(transferRequest);
// Assert
expect(transferResponse.status).toBe(409);// But why do we expect failure: All seems perfectly valid in the test ð€
});
ðæ£ããè¡ãäŸïŒãã¹ãã¯ããã¹ãçµæã®åå ãäœã§ãããã匷調ããŸã
test("When no credit, then the transfer is declined ", async() => {
// Arrange
const transferRequest = testHelpers.factorMoneyTransfer({userCredit:100, transferAmount:200}) //obviously there is lack of credit
const transferServiceUnderTest = new TransferService({disallowOvercharge:true});
// Act
const transferResponse = await transferServiceUnderTest.transfer(transferRequest);
// Assert
expect(transferResponse.status).toBe(409); // Obviously if the user has no credit it should fail
});
âªïž1.10ãšã©ãŒããã£ããããªãã§ãã ãããããããæåŸ
ããŠãã ãã
â
宿œïŒäžéšã®å
¥åããšã©ãŒãããªã¬ãŒããããšã衚æããããšãããšãtry-catch-finallyã䜿çšããã®ãæ£ããããã«èŠããcatchå¥ãå
¥åãããããšã衚æããŸããçµæã¯ãåçŽãªãã¹ãã®æå³ãšçµæã®æåŸ
ãé ãåä»ã§åé·ãªãã¹ãã±ãŒã¹ïŒä»¥äžã®äŸïŒã§ãã
ããæŽç·Žãããä»£æ¿ææ®µã¯ã1è¡ã®å°çšChaiã¢ãµãŒã·ã§ã³ã䜿çšããããšã§ãïŒexpectïŒmethodïŒ.to.throwïŒãŸãã¯Jestã§ã¯expectïŒmethodïŒ.toThrowïŒïŒïŒãäŸå€ã«ãšã©ãŒã¿ã€ãã瀺ãããããã£ãå«ãŸããŠããããšã確èªããå¿
èŠããããŸããããããªããšãäžè¬çãªãšã©ãŒãçºçããå Žåãã¢ããªã±ãŒã·ã§ã³ã¯ãŠãŒã¶ãŒã«æåŸ
å€ãã®ã¡ãã»ãŒãžã衚瀺ããã®ã§ã¯ãªããå€ãã®ããšãå®è¡ã§ããªããªããŸãã
â ãã以å€ã®å ŽåïŒãã¹ãã¬ããŒãïŒCIã¬ããŒããªã©ïŒããäœãæªãã£ãã®ããæšæž¬ããã®ã¯å°é£ã§ã
â ã³ãŒãäŸ
ðã¢ã³ããã¿ãŒã³ã®äŸïŒtry-catchã§ãšã©ãŒã®ååšã衚æããããšããé·ããã¹ãã±ãŒã¹

it("When no product name, it throws error 400", async () => {
let errorWeExceptFor = null;
try {
const result = await addNewProduct({});
} 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
});
ðæ£ããè¡ãäŸïŒQAãæè¡PMã§ãç°¡åã«çè§£ã§ããã人éãèªãã圢åŒã®æåŸ
it("When no product name, it throws error 400", async () => {
await expect(addNewProduct({}))
.to.eventually.throw(AppError)
.with.property("code", "InvalidInput");
});
âªïž1.11ãã¹ãã«ã¿ã°ãä»ãã
â
宿œïŒããŸããŸãªã·ããªãªã§ããŸããŸãªãã¹ããå®è¡ããå¿
èŠããããŸããã¯ã€ãã¯ã¹ã¢ãŒã¯ãIOã¬ã¹ãéçºè
ããã¡ã€ã«ãä¿åãŸãã¯ã³ããããããšãã«ãã¹ããå®è¡ããå¿
èŠããããŸããéåžžãæ°ãããã«ãªã¯ãšã¹ããéä¿¡ããããšãå®å
šãªãšã³ãããŒãšã³ãã®ãã¹ããå®è¡ãããŸãã #cold #api #sanityãªã©ã®ããŒã¯ãŒãã§ãã¹ãã«ã¿ã°ãä»ããããšã§å®çŸã§ããããããã¹ãããŒãã¹ã䜿çšããŠgrepãå®è¡ããç®çã®ãµãã»ãããåŒã³åºãããšãã§ããŸããããšãã°ãããã¯ãMochaã䜿çšããŠå¥å
šæ§ãã¹ãã°ã«ãŒãã®ã¿ãåŒã³åºãæ¹æ³ã§ããmochaâ grep'sanity'
â ãã以å€ã®å ŽåïŒæ°åã®DBã¯ãšãªãå®è¡ãããã¹ããå«ããã¹ãŠã®ãã¹ãã®å®è¡ã¯ãéçºè
ãå°ããªå€æŽãå ãããã³ã«éåžžã«é
ããªããéçºè
ããã¹ããå®è¡ã§ããªãããã«ããŸãã
â ã³ãŒãäŸ
ðæ£ããå®è¡äŸïŒãã¹ãã«ãïŒcold-testãã®ã¿ã°ãä»ãããšããã¹ãã©ã³ããŒã¯é«éãã¹ãã®ã¿ãå®è¡ã§ããŸãïŒIOãå®è¡ãããéçºè
ãå
¥åããŠãããšãã§ãé »ç¹ã«å®è¡ã§ããã³ãŒã«ã===é«éãã¹ãïŒ

//this test is fast (no DB) and we're tagging it correspondigly
//now the user/CI can run it frequently
describe("Order service", function() {
describe("Add new order #cold-test #sanity", function() {
test("Scenario - no currency was supplied. Expectation - Use the default currency #sanity", function() {
//code logic here
});
});
});
âªïž1.12å°ãªããšã2ã€ã®ã¬ãã«ã§ãã¹ããåé¡ãã
â
宿œïŒãã¹ãã¹ã€ãŒãã«æ§é ãé©çšããŠãäžå®æã®èšªåè
ãèŠä»¶ïŒãã¹ããæé©ãªããã¥ã¡ã³ãïŒãšãã¹ããããŠããããŸããŸãªã·ããªãªãç°¡åã«çè§£ã§ããããã«ããŸããããã®äžè¬çãªæ¹æ³ã¯ããã¹ãã®äžã«å°ãªããšã2ã€ã®ãdescribeããããã¯ãé
眮ããããšã§ãã1ã€ç®ã¯ãã¹ã察象ã®ãŠãããã®ååçšã§ã2ã€ç®ã¯ã·ããªãªãã«ã¹ã¿ã ã«ããŽãªãªã©ã®è¿œå ã¬ãã«ã®åé¡çšã§ãïŒã³ãŒãäŸãšå°å·ãåç
§ïŒäžã®ç»é¢ïŒãããããããšã§ããã¹ãã¬ããŒãã倧å¹
ã«æ¹åãããŸããèªè
ã¯ããã¹ãã«ããŽãªãç°¡åã«æšæž¬ããç®çã®ã»ã¯ã·ã§ã³ãæãäžããŠã倱æãããã¹ããçžäºã«é¢é£ä»ããããšãã§ããŸããããã«ãéçºè
ãå€ãã®ãã¹ããå«ãã¹ã€ãŒãã®ã³ãŒããããã²ãŒãããã®ãã¯ããã«ç°¡åã«ãªããŸãããã¹ãã¹ã€ãŒãã«ã¯ã次ã®ããã«èããããšãã§ããè€æ°ã®ä»£æ¿æ§é ããããŸããäžãããããšããšRITE
â ãã以å€ã®å ŽåïŒãã¹ãã®ãã©ããã§é·ããªã¹ããå«ãã¬ããŒããèŠãå Žåãèªè
ã¯é·ãããã¹ãããã£ãšèªãã§äž»èŠãªã·ããªãªãçµè«ä»ãã倱æãããã¹ãã®å
±éæ§ãé¢é£ä»ããå¿
èŠããããŸããæ¬¡ã®å ŽåãèããŠã¿ãŸãããã7/100ãã¹ãã倱æããå Žåããã©ãããªã¹ããèŠãã«ã¯ã倱æãããã¹ãã®ããã¹ããèªãã§ãããããäºãã«ã©ã®ããã«é¢é£ããŠãããã確èªããå¿
èŠããããŸãããã ããéå±€ã¬ããŒãã§ã¯ãããããã¹ãŠãåããããŒãŸãã¯ã«ããŽãªã«å±ããŠããå¯èœæ§ããããèªè
ã¯æ ¹æ¬çãªé害ã®åå ãäœã§ããããå°ãªããšãã©ãã«ãããããã°ããæšæž¬ããŸãã
â ã³ãŒãäŸ
ðæ£ããæ¹æ³ã®äŸïŒãã¹ã察象ã®ãŠãããã®ååãšã·ããªãªã䜿çšããŠã¹ã€ãŒããæ§ç¯ãããšã以äžã«ç€ºã䟿å©ãªã¬ããŒãã衚瀺ãããŸãã

// Unit under test
describe("Transfer service", () => {
//Scenario
describe("When no credit", () => {
//Expectation
test("Then the response status should decline", () => {});
//Expectation
test("Then it should send email to admin", () => {});
});
});

ðã¢ã³ããã¿ãŒã³ã®äŸïŒãã¹ãã®ãã©ãããªã¹ãã¯ãèªè
ããŠãŒã¶ãŒã¹ããŒãªãŒãèå¥ãã倱æãããã¹ããçžäºã«é¢é£ä»ããããšãå°é£ã«ããŸã

test("Then the response status should decline", () => {});
test("Then it should send email", () => {});
test("Then there should not be a new transfer record", () => {});

âªïž1.13ãã®ä»ã®äžè¬çãªè¯å¥œãªè©Šéšè¡ç
â
宿œïŒãã®æçš¿ã¯ãããŒãJSã«é¢é£ããããŸãã¯å°ãªããšãããŒãJSã§äŸç€ºã§ãããã¹ãã¢ããã€ã¹ã«çŠç¹ãåœãŠãŠããŸãããã ãããã®ç®æ¡æžãã§ã¯ãããç¥ãããŠããããŒãã«é¢é£ããªããã³ããããã€ãã°ã«ãŒãåããŠããŸãã
TDDã®ååãåŠã³ãå®è·µããŸããTDDã®ååâã¯å€ãã®äººã«ãšã£ãŠéåžžã«äŸ¡å€ããããŸãããèªåã®ã¹ã¿ã€ã«ã«åããªããŠãæããããšã¯ãããŸãããããªãã ãã§ã¯ãããŸãããã³ãŒãã®åã«èµ€ç·ãªãã¡ã¯ã¿ãŒã¹ã¿ã€ã«ã§ãã¹ããäœæããããšãæ€èšãããã°ãèŠã€ãããšãã«åãã¹ãã1ã€ã ããã§ãã¯ããããšã確èªããŸããä¿®æ£ããåã«ãå°æ¥ãã®ãã°ãæ€åºãããã¹ããäœæããå°ãªããšãåãã¹ãã倱æãããŸããç·è²ã«å€ããåã«ããã¹ããæºããè¿
éã§åçŽãªã³ãŒããèšè¿°ããŠã¢ãžã¥ãŒã«ãéå§ããŸããæ¬¡ã«ãåŸã
ã«ãªãã¡ã¯ã¿ãªã³ã°ããŠè£œåã°ã¬ãŒãã¬ãã«ã«ããç°å¢ïŒãã¹ãOSãªã©ïŒãžã®äŸåãåé¿ããŸãã
â ããã§ãªããã°ïŒããªãã¯äœå幎ãã®ééããããç¥æµã®çç ãèŠéãã§ããã
ã»ã¯ã·ã§ã³2ïžâ£ïŒããã¯ãšã³ããã¹ã
âªïž2.1ãã¹ãããŒããã©ãªãªãå
å®ãããïŒåäœãã¹ããšãã©ããããè¶
ããŠèŠãŠãã ãã
â
宿œïŒãã¹ããã©ãããã¯10幎以äžåã®ãã®ã§ããã3ã€ã®ãã¹ãã¿ã€ããææ¡ããã»ãšãã©ã®éçºè
ã®ãã¹ãæŠç¥ã«åœ±é¿ãäžãããåªããé¢é£ã¢ãã«ã§ããåæã«ãäžæ¡ã以äžã®å
æ²¢ã®ããæ°ãããã¹ãææ³ãåºçŸãããã¹ããã©ãããã®é°ã«é ããŠããŸããæè¿10幎éã«èŠããããã¹ãŠã®åçãªå€åïŒãã€ã¯ããµãŒãã¹ãã¯ã©ãŠãããµãŒããŒã¬ã¹ïŒãèãããšã1ã€ã®éåžžã«å€ãã¢ãã«ããã¹ãŠã®ã¿ã€ãã®ã¢ããªã±ãŒã·ã§ã³ã«é©åããå¯èœæ§ãããããŸããïŒãã¹ãã®äžçã§ã¯ãæ°ãããã¹ãææ³ãæè¿ããããšãæ€èšãã¹ãã§ã¯ãããŸãããïŒ
誀解ããªãã§ãã ããã2019幎ã®ãã¹ããã©ããããTDDãããã³åäœãã¹ãã¯äŸç¶ãšããŠåŒ·åãªææ³ã§ãããããããå€ãã®ã¢ããªã±ãŒã·ã§ã³ã«æé©ã§ããä»ã®ã¢ãã«ãšåãããã«ããã®æçšæ§ã«ãããããããããã¯æã
ééã£ãŠããã«éããããŸãããããšãã°ãKafka / RabbitMQã®ãããªã¡ãã»ãŒãžãã¹ã«å€ãã®ã€ãã³ããåã蟌ã¿ãããŒã¿ãŠã§ã¢ããŠã¹ã«æµã蟌ã¿ãæçµçã«åæUIã«ãã£ãŠã¯ãšãªãããIoTã¢ããªã±ãŒã·ã§ã³ã«ã€ããŠèããŠã¿ãŸããçµ±åäžå¿ã§ããžãã¯ãã»ãšãã©ãªãã¢ããªã±ãŒã·ã§ã³ã®åäœãã¹ãã®äœæã«ããã¹ãäºç®ã®50ïŒ
ãå®éã«è²»ããå¿
èŠããããŸããïŒã¢ããªã±ãŒã·ã§ã³ã¿ã€ãïŒããããæå·ãAlexaã¹ãã«ïŒã®å€æ§æ§ãå¢ãã«ã€ããŠããã¹ããã©ããããæé©ã§ã¯ãªãã·ããªãªãèŠã€ããå¯èœæ§ãé«ããªããŸãã
ãã¹ãããŒããã©ãªãªãå
å®ãããããå€ãã®ãã¹ãã¿ã€ãïŒæ¬¡ã®ç®æ¡æžãã¯ããã€ãã®ã¢ã€ãã¢ãææ¡ããŸãïŒããã¹ããã©ãããã®ãããªãã€ã³ãã¢ãã«ã«ç²Ÿéãããšãã§ããããã¹ãã¿ã€ããçŽé¢ããŠããå®éã®åé¡ã«äžèŽãããŸãïŒããããç§ãã¡ã®APIå£ããŠããå Žåã¯ãæ¶è²»è
äž»å°ã®å¥çŽãã¹ããäœæããŸãããïŒ'ïŒããªã¹ã¯åæã«åºã¥ããŠããŒããã©ãªãªãæ§ç¯ããæè³å®¶ã®ããã«ãã¹ãã倿§åããŸãâåé¡ãçºçããå¯èœæ§ã®ããå Žæãè©äŸ¡ãããããã®æœåšçãªãªã¹ã¯ã軜æžããããã®ããã€ãã®äºé²çãšäžèŽãããŸã
泚æã®èšèïŒãœãããŠã§ã¢ã®äžçã§ã®TDDã®è°è«ã¯ãå
žåçãªèª€ã£ãäºåæ³ã®é¡ãåããã©ãã§ãããã䜿çšããããã«èª¬æãã人ãããã°ããããæªéã ãšèãã人ãããŸãã絶察çã«è©±ã人ã¯èª°ã§ãééã£ãŠããŸãïŒ]
â ãã以å€ã®å ŽåïŒãã¡ãºããªã³ãããã¥ãŒããŒã·ã§ã³ãªã©ãé©ãã¹ãROIãåããããŒã«ãèŠéããŠããŸããŸãã10åã§äŸ¡å€ãåŸãããŸãã
â ã³ãŒãäŸ
ðæ£ããæ¹æ³ã®äŸïŒCindy Sridharanã¯ã圌女ã®ãã°ãããæçš¿ãTesting Microservices âåãæ¹æ³ãã§è±å¯ãªãã¹ãããŒããã©ãªãªãææ¡ããŠããŸãã

âºïžäŸïŒYouTubeïŒããŠããããã¹ããè¶
ããŠïŒ5ã€ã®Shiny Node.JSãã¹ãã¿ã€ãïŒ2018ïŒãïŒYoniGoldbergïŒ

âªïž2.2ã³ã³ããŒãã³ããã¹ãã¯ããªãã®æåã®åé¡ãããããŸãã
â
宿œïŒååäœãã¹ãã¯ã¢ããªã±ãŒã·ã§ã³ã®ããäžéšãã«ããŒããå
šäœãã«ããŒããã®ã«è²»çšãããããŸããããšã³ãããŒãšã³ãã®ãã¹ãã¯ç°¡åã«å€ãã®é åãã«ããŒããŸãããäžå®å®ã§æéãããããŸãããã©ã³ã¹ã®åããã¢ãããŒããé©çšããŠãåäœãã¹ãããã倧ãããããšã³ãããŒãšã³ãã®ãã¹ããããå°ããïŒã³ã³ããŒãã³ããã¹ãã¯ããã¹ãã®äžçã§æãããŠããªãæã§ãããããã¯ã劥åœãªããã©ãŒãã³ã¹ãšTDDãã¿ãŒã³ãé©çšããå¯èœæ§+çŸå®çã§åªããã«ãã¬ããžãšãããäž¡æ¹ã®äžçããæé«ã®ãã®ãæäŸããŸãã
ã³ã³ããŒãã³ããã¹ãã¯ãã€ã¯ããµãŒãã¹ã®ããŠããããã«çŠç¹ãåœãŠãAPIã«å¯ŸããŠæ©èœãããã€ã¯ããµãŒãã¹èªäœã«å±ãããã®ïŒããšãã°ãå®éã®DBããŸãã¯å°ãªããšããã®DBã®ã¡ã¢ãªå
ããŒãžã§ã³ïŒãã¢ãã¯ããŸããããå€éšã®ãã®ãã¹ã¿ãããŸãä»ã®ãã€ã¯ããµãŒãã¹ãžã®åŒã³åºãã®ããã«ãããããããšã§ããããã€ããå
容ããã¹ãããã¢ããªã«å€åŽããå
åŽã«ã¢ãããŒããã劥åœãªæéå
ã«å€§ããªèªä¿¡ãåŸãããšãã§ããŸãã
ã³ã³ããŒãã³ããã¹ããæ£ããæ¹æ³ã§æžãããšã«å°å¿µããå®å
šãªã¬ã€ãããããŸã
â ãã以å€ã®å ŽåïŒã·ã¹ãã ã«ãã¬ããžã20ïŒ
ãããªãããšã確èªããããã«ãåäœãã¹ãã®äœæã«é·ãæ¥æ°ãè²»ããå¯èœæ§ããããŸã
â ã³ãŒãäŸ
ðæ£ããå®è¡äŸïŒã¹ãŒããŒãã¹ãã«ãããExpress APIã«ã€ã³ããã»ã¹ã§ã¢ãããŒãã§ããŸãïŒé«éã§å€ãã®ã¬ã€ã€ãŒãã«ããŒããŸãïŒ

![[ã¹ãŒããŒãã¹ã]ïŒhttps://www.npmjs.com/package/supertestïŒã«ãããExpress APIã®ã€ã³ããã»ã¹ã«ã¢ãããŒãã§ããŸãïŒé«éã§å€ãã®ã¬ã€ã€ãŒãã«ããŒããŸãïŒ ä»£æ¿ããã¹ã](https://raw.githubusercontent.com/goldbergyoni/javascript-testing-best-practices/master/assets/bp-13-component-test-yoni-goldberg.png)
âªïž2.3ã³ã³ãã©ã¯ããã¹ãã䜿çšããŠãæ°ãããªãªãŒã¹ãAPIãå£ããªãããšã確èªããŸã
â
è¡ãïŒãããã£ãŠããã€ã¯ããµãŒãã¹ã«ã¯è€æ°ã®ã¯ã©ã€ã¢ã³ãããããäºææ§ã®çç±ãããµãŒãã¹ã®è€æ°ã®ããŒãžã§ã³ãå®è¡ããŸãïŒå
šå¡ãæºè¶³ãããŸãïŒã次ã«ãããã€ãã®ãã£ãŒã«ãã倿ŽããŠãããŒã ïŒããšãããšããã®ãã£ãŒã«ãã«äŸåããŠããéèŠãªã¯ã©ã€ã¢ã³ããæã£ãŠããŸããããã¯çµ±åã®äžçã®ãã£ãã22ã§ãããµãŒããŒåŽãè€æ°ã®ã¯ã©ã€ã¢ã³ãã®æåŸ
ããã¹ãŠèæ
®ããããšã¯éåžžã«å°é£ã§ãâäžæ¹ããµãŒããŒããªãªãŒã¹æ¥ãå¶åŸ¡ãããããã¯ã©ã€ã¢ã³ãã¯ãã¹ããå®è¡ã§ããŸãããå¥çŽã®åé¡ã軜æžã§ããããŸããŸãªææ³ããããŸããåçŽãªãã®ãããã°ãæ©èœãè±å¯ã§ãããæ¥ãªåŠç¿æ²ç·ãå¿
èŠãªãã®ããããŸããã·ã³ãã«ã§æšå¥šãããã¢ãããŒãã§ã¯ãAPIãããã€ããŒã¯APIã¿ã€ãã³ã°ïŒJSDocãTypeScriptãªã©ïŒã䜿çšããŠnpmããã±ãŒãžãå
¬éããŸããæ¬¡ã«ãæ¶è²»è
ã¯ãã®ã©ã€ãã©ãªããã§ããããŠãã³ãŒãã€ã³ã¿ã€ã ã€ã³ããªã»ã³ã¹ãšæ€èšŒã®æ©æµãåããããšãã§ããŸãã䜿çšããããã®ããæã®èŸŒãã ã¢ãããŒãéåžžã«ç Žå£çãªã¢ãããŒãã§ãã®ããã»ã¹ã圢åŒåããããã«çãŸããPACTâãµãŒããŒãããèªäœã®ãã¹ãèšç»ãå®çŸ©ããã®ã§ã¯ãªããã¯ã©ã€ã¢ã³ããâŠãµãŒããŒã®ãã¹ããå®çŸ©ããŸãïŒPACTã¯ã¯ã©ã€ã¢ã³ãã®æåŸ
ãèšé²ããå
±æã®å ŽæããããŒã«ãŒãã«é
眮ã§ããããããµãŒããŒã¯æåŸ
ãåŒãåºããPACTã©ã€ãã©ãªã䜿çšããŠãã¹ãŠã®ãã«ãã§å®è¡ããå£ããå¥çŽïŒã¯ã©ã€ã¢ã³ãã®æåŸ
ãæºããããŠããªãïŒãæ€åºã§ããŸããããããããšã§ããµãŒããŒãšã¯ã©ã€ã¢ã³ãã®APIã®äžäžèŽã¯ãã¹ãŠããã«ã/ CIã®æ©ã段éã§æ€åºããããã©ã¹ãã¬ãŒã·ã§ã³ã倧å¹
ã«è»œæžã§ããå¯èœæ§ããããŸãã
â ãã以å€ã®å ŽåïŒä»£æ¿æ¡ã¯ãæåãã¹ããŸãã¯å±éã®æãã䜿ãæãããŠããŸã
â ã³ãŒãäŸ
ðãããæ£ããè¡ãäŸïŒ


âªïž2.4ããã«ãŠã§ã¢ãåé¢ããŠãã¹ããã
â
宿œïŒããã«ãŠã§ã¢ãã¹ãã¯ã·ã¹ãã ã®ããäžéšã衚ããã©ã€ãExpressãµãŒããŒãå¿
èŠãšãããããå€ãã®å Žåããã«ãŠã§ã¢ãã¹ããåé¿ããŸããäž¡æ¹ã®çç±ãééã£ãŠããŸãâããã«ãŠã§ã¢ã¯å°ããã§ããããã¹ãŠãŸãã¯ã»ãšãã©ã®ãªã¯ãšã¹ãã«åœ±é¿ãäžãã{reqãres}JSãªããžã§ã¯ããååŸããçŽç²é¢æ°ãšããŠç°¡åã«ãã¹ãã§ããŸããããã«ãŠã§ã¢é¢æ°ããã¹ãããã«ã¯ãããã«ãŠã§ã¢é¢æ°ãåŒã³åºããŠã{reqãres}ãªããžã§ã¯ããšã®çžäºäœçšãã¹ãã€ãïŒããšãã°Sinonã䜿çšïŒã颿°ãæ£ããã¢ã¯ã·ã§ã³ãå®è¡ããããšã確èªããå¿
èŠããããŸããã©ã€ãã©ãªnode-mock-httpã¯ãããããã«é²ãã{reqãres}ãªããžã§ã¯ãããããã®åäœãã¹ãã€ãããšãšãã«å æ°åè§£ããŸããããšãã°ãresãªããžã§ã¯ãã«èšå®ãããhttpã¹ããŒã¿ã¹ãæåŸ
å€ãšäžèŽãããã©ãããã¢ãµãŒãã§ããŸãïŒä»¥äžã®äŸãåç
§ïŒ
â ãã以å€ã®å ŽåïŒ Expressããã«ãŠã§ã¢ã®ãã°===ãã¹ãŠãŸãã¯ã»ãšãã©ã®ãªã¯ãšã¹ãã®ãã°
â ã³ãŒãäŸ
ðæ£ããæ¹æ³ã®äŸïŒãããã¯ãŒã¯åŒã³åºããçºè¡ããããExpressãã·ã³å
šäœããŠã§ã€ã¯ã¢ãããããããã«ãããã«ãŠã§ã¢ãåé¢ããŠãã¹ããã

//the middleware we want to test
const unitUnderTest = require("./middleware");
const httpMocks = require("node-mocks-http");
//Jest syntax, equivelant 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ãã«ãã«è¿œå ããŠãã³ãŒãã®èããèŠã€ãã£ããšãã«äžæ¢ããããšãã§ããŸãããã¬ãŒã³ãªã³ãã£ã³ã°ã«å¯Ÿããäž»ãªã»ãŒã«ã¹ãã€ã³ãã¯ãè€æ°ã®ãã¡ã€ã«ã®ã³ã³ããã¹ãã§åè³ªãæ€æ»ãïŒéè€ã®æ€åºãªã©ïŒãé«åºŠãªåæãå®è¡ãïŒã³ãŒãã®è€éããªã©ïŒãã³ãŒãã®åé¡ã®å±¥æŽãšé²è¡ç¶æ³ã远跡ã§ããããšã§ãã䜿çšã§ããããŒã«ã®2ã€ã®äŸã¯ãSonarQubeïŒ4,900以äžã®æïŒãšCode ClimateïŒ2,000以äžã®æïŒã§ãã
ã¯ã¬ãžããïŒããŒã¹ããªãã€
â ãã以å€ã®å ŽåïŒã³ãŒãã®å質ãäœããšããã°ãšããã©ãŒãã³ã¹ãåžžã«åé¡ã«ãªããå
æ²¢ã®ããæ°ããã©ã€ãã©ãªãæå
端ã®ââæ©èœã§ã¯ä¿®æ£ã§ããŸããã
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸïŒè€éãªã¡ãœãããèå¥ã§ããåçšããŒã«ã§ããCodeClimateïŒ


âªïž2.6ããŒãé¢é£ã®æ··ä¹±ã«å¯Ÿããæºåã確èªããŸã
â
宿œïŒå¥åŠãªããšã«ãã»ãšãã©ã®ãœãããŠã§ã¢ãã¹ãã¯ããžãã¯ãšããŒã¿ã®ã¿ã察象ãšããŠããŸãããçºçããïŒãããŠè»œæžããã®ãéåžžã«é£ããïŒææªã®äºæ
ã®ããã€ãã¯ã€ã³ãã©ã¹ãã©ã¯ãã£ã®åé¡ã§ããããšãã°ãããã»ã¹ã¡ã¢ãªãéè² è·ã«ãªã£ããšãããµãŒããŒ/ããã»ã¹ã忢ãããšãã«äœãèµ·ãããããã¹ãããããšããããŸããããŸãã¯APIã50ïŒ
é
ããªã£ããšãã«ç£èŠã·ã¹ãã ãèªèããŸããïŒãããã®ã¿ã€ãã®æªãããšããã¹ãããŠè»œæžããããã«ââã«ãªã¹ãšã³ãžãã¢ãªã³ã°ã¯Netflixã«ãã£ãŠçãŸããŸãããããã¯ãæ··æ²ãšããåé¡ã«å¯Ÿããã¢ããªã®åŸ©å
åããã¹ãããããã®èªèããã¬ãŒã ã¯ãŒã¯ãããŒã«ãæäŸããããšãç®çãšããŠããŸããããšãã°ãæåãªããŒã«ã®1ã€ã§ããã«ãªã¹ã¢ã³ããŒã¯ããµãŒããŒãã©ã³ãã ã«åŒ·å¶çµäºããŠããµãŒãã¹ãåäžã®ãµãŒããŒã«äŸåããã«ãŠãŒã¶ãŒã«ãµãŒãã¹ãæäŸã§ããããã«ããŸãïŒKubernetesããŒãžã§ã³ã®kube-monkeyããããŸãïŒãããã¯ããããæ®ºããŸãïŒããããã®ããŒã«ã¯ãã¹ãŠãã¹ãã£ã³ã°/ãã©ãããã©ãŒã ã¬ãã«ã§æ©èœããŸãããããŒãããã»ã¹ããã£ãããããªããšã©ãŒãæªåŠçã®ãããã¹æåŠãæå€§èš±å®¹å€1.7GBã§éè² è·ã®v8ã¡ã¢ãªã«ã©ã®ããã«å¯ŸåŠãããã確èªãããªã©ãçŽç²ãªããŒãã«ãªã¹ããã¹ãããŠçæãããå Žåã¯ã©ããªããŸããïŒã€ãã³ãã«ãŒããé »ç¹ã«ãããã¯ãããŠããUXã¯æºè¶³ã®ãããã®ã§ããïŒç§ãæžããããã«å¯ŸåŠããããã«ãããŒãé¢é£ã®ããããçš®é¡ã®æ··æ²ãšãââãè¡çºãæäŸãã
ããŒãã«ãªã¹ïŒã¢ã«ãã¡ïŒ
â ããã§ãªããã°ïŒããã§éããããšã¯ã§ããŸãããããŒãã£ãŒã®æ³åã¯å®¹èµŠãªãããªãã®äœåã«åœ±é¿ãäžããŸã
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸ::Node-chaosã¯ãããããçš®é¡ã®Node.jsãããããçæããå¯èœæ§ããããããã¢ããªãã«ãªã¹ã«å¯ŸããŠã©ã®çšåºŠå埩åããããããã¹ãã§ããŸãã

âªïž2.7ã°ããŒãã«ãªãã¹ããã£ã¯ã¹ãã£ãšã·ãŒããé¿ãããã¹ãããšã«ããŒã¿ã远å ããŸã
â
宿œïŒé»éåŸïŒç®æ¡æžã0ïŒã«åŸã£ãŠãåãã¹ãã¯ãçµåãé²ãããã¹ããããŒã«ã€ããŠç°¡åã«æšè«ã§ããããã«ãç¬èªã®DBè¡ã®ã»ããã远å ããŠåŠçããå¿
èŠããããŸããå®éã«ã¯ãããã©ãŒãã³ã¹ãåäžãããããã«ããã¹ããå®è¡ããåã«DBã«ããŒã¿ãã·ãŒããããã¹ã¿ãŒïŒããã¹ããã£ã¯ã¹ãã£ããšãåŒã°ããŸãïŒã¯ãããã«éåããããšããããããŸããããã©ãŒãã³ã¹ã¯ç¢ºãã«æå¹ãªæžå¿µäºé
ã§ããã軜æžããããšã¯ã§ããŸããïŒãã³ã³ããŒãã³ãã®ãã¹ããã®ç®æ¡æžããåç
§ïŒããã¹ãã®è€éãã¯éåžžã«èŠçãªæ²ãã¿ã§ãããã»ãšãã©ã®å Žåãä»ã®èæ
®äºé
ãå·Šå³ããã¯ãã§ããå®éã«ã¯ãåãã¹ãã±ãŒã¹ã«å¿
èŠãªDBã¬ã³ãŒããæç€ºçã«è¿œå ãããããã®ã¬ã³ãŒãã®ã¿ã«äœçšããããã«ããŸããããã©ãŒãã³ã¹ãéå€§ãªæžå¿µäºé
ã«ãªãå Žåâãã©ã³ã¹ã®åãã劥åã¯ãããŒã¿ã倿Žããªãå¯äžã®ãã¹ãã¹ã€ãŒãïŒã¯ãšãªãªã©ïŒãã·ãŒããããšãã圢ã§çºçããå¯èœæ§ããããŸãã
â ãã以å€ã®å ŽåïŒå€±æãããã¹ãã¯ã»ãšãã©ãªããå±éã¯äžæ¢ãããŸããããŒã ã¯è²Žéãªæéãè²»ããäºå®ã§ãããã°ã¯ãããŸããïŒèª¿ã¹ãŠã¿ãŸããããããã2ã€ã®ãã¹ããåãã·ãŒãããŒã¿ãå€ç°ãããŠããããã§ã
â ã³ãŒãäŸ
ðã¢ã³ããã¿ãŒã³ã®äŸïŒãã¹ãã¯ç¬ç«ããŠããããã°ããŒãã«DBããŒã¿ããã£ãŒãããããã«ã°ããŒãã«ããã¯ã«äŸåããŠããŸã

before(async () => {
//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);
});
âªïž2.8æç¢ºãªããŒã¿ã¯ãªãŒã³ã¢ããæŠç¥ãéžæããŸãïŒçµå±ã®ãšããïŒæšå¥šïŒãŸãã¯ããããã®åŸ
â
è¡ãïŒãã¹ããããŒã¿ããŒã¹ãã¯ãªãŒã³ã¢ããããã¿ã€ãã³ã°ã«ãã£ãŠããã¹ãã®èšè¿°æ¹æ³ã決ãŸããŸããæãå®è¡å¯èœãª2ã€ã®ãªãã·ã§ã³ã¯ããã¹ãŠã®ãã¹ãåŸã®ã¯ãªãŒãã³ã°ãšããã¹ãŠã®ãã¹ãåŸã®ã¯ãªãŒãã³ã°ã§ããåŸè
ã®ãªãã·ã§ã³ãéžæãããšããã¹ãŠã®ãã¹ãã®åŸã«ã¯ãªãŒãã³ã°ããããšã§ãããŒãã«ãã¯ãªãŒã³ã«ãªããéçºè
ã«ãšã£ãŠäŸ¿å©ãªãã¹ãç¹å
žãæ§ç¯ãããŸãããã¹ãã®éå§æã«ä»ã®ã¬ã³ãŒãã¯ååšããŸãããã©ã®ããŒã¿ãã¯ãšãªãããŠãããã確èªã§ããã¢ãµãŒã·ã§ã³äžã«è¡ãã«ãŠã³ãããããªãå ŽåããããŸããããã«ã¯éå€§ãªæ¬ ç¹ããããŸãããã«ãããã»ã¹ã¢ãŒãã§å®è¡ãããšããã¹ããçžäºã«å¹²æžããå¯èœæ§ããããŸããprocess-1ã¯ããŒãã«ãããŒãžããŸããããã®ç¬éãprocess-2ã¯ããŒã¿ãç
§äŒããŠå€±æããŸãïŒDBãprocess-1ã«ãã£ãŠçªç¶åé€ãããããïŒãããã«ã倱æãããã¹ãã®ãã©ãã«ã·ã¥ãŒãã£ã³ã°ã¯å°é£ã§ããDBã«ã¢ã¯ã»ã¹ããŠããã¬ã³ãŒãã¯è¡šç€ºãããŸããã
2çªç®ã®ãªãã·ã§ã³ã¯ããã¹ãŠã®ãã¹ããã¡ã€ã«ãçµäºããåŸïŒãŸãã¯æ¯æ¥ïŒïŒã«ã¯ãªãŒã³ã¢ããããããšã§ãããã®ã¢ãããŒãã¯ãæ¢åã®ã¬ã³ãŒããæã€åãDBããã¹ãŠã®ãã¹ããšããã»ã¹ã«å¯Ÿå¿ããããšãæå³ããŸãããäºãã®ã€ãŸå
ãèžãŸãªãããã«ããã«ã¯ããã¹ãã¯è¿œå ããç¹å®ã®ã¬ã³ãŒãã远å ããŠåŠçããå¿
èŠããããŸããããã€ãã®ã¬ã³ãŒãã远å ãããããšã確èªããå¿
èŠããããŸããïŒä»ã«ãæ°åã®ã¬ã³ãŒãããããæç€ºçã«è¿œå ãããã¬ã³ãŒããç
§äŒãããšããŸããã¬ã³ãŒããåé€ãããããšã確èªããå¿
èŠããããŸããïŒç©ºã®ããŒãã«ãæ³å®ããããšã¯ã§ããŸããããã®ç¹å®ã®ã¬ã³ãŒããååšããªãããšã確èªããŠãã ããããã®ææ³ã¯ãããã€ãã®åŒ·åãªå©ç¹ããããããŸããéçºè
ãäœãèµ·ãã£ãã®ããçè§£ãããå Žåããã«ãããã»ã¹ã¢ãŒãã§ãã€ãã£ãã«æ©èœããŸããããŒã¿ã¯ããã«ãããåé€ãããŸããããŸããDBã¯ã¬ã³ãŒãã§ãã£ã±ãã§ããã人çºçã«ç©ºã§ã¯ãªãããããã°ãèŠã€ããå¯èœæ§ãé«ããªããŸããããã§å®å
šãªæ¯èŒè¡šãåç
§ããŠãã ããã
â ãã以å€ã®å ŽåïŒã¬ã³ãŒããåé¢ãããã¯ãªãŒã³ã¢ãããããããæŠç¥ããªãå Žå-ãã¹ãã¯äºãã«è¶³ãèžã¿å
¥ããŸãããã©ã³ã¶ã¯ã·ã§ã³ã®äœ¿çšã¯ãªã¬ãŒã·ã§ãã«DBã§ã®ã¿æ©èœããå
éšãã©ã³ã¶ã¯ã·ã§ã³ããããšè€éã«ãªãå¯èœæ§ããããŸã
â ã³ãŒãäŸ
ðãã¹ãŠã®ãã¹ãåŸã®ã¯ãªãŒãã³ã°ããã¹ãŠã®å®è¡åŸã«å¿
ãããå¿
èŠã§ã¯ãããŸããããã¹ãã®å®è¡äžã«ååŸããããŒã¿ãå€ãã»ã©ãæ¬çªç°å¢ã®ç¹å
žã«äŒŒãŠããŸãã
// After-all clean up (recommended)
// global-teardown.js
module.exports = async () => {
// ...
if (Math.ceil(Math.random() * 10) === 10) {
await new OrderRepository().cleanup();
}
};
âªïž2.9HTTPã€ã³ã¿ãŒã»ãã¿ãŒã䜿çšããŠã³ã³ããŒãã³ããã¯ãŒã«ãããåé¢ãã
â
è¡ãïŒçºä¿¡HTTPãªã¯ãšã¹ããã€ã³ã¿ãŒã»ããããå¿
èŠãªå¿çãæäŸããŠãã³ã©ãã¬ãŒã¿ãŒã®HTTP APIããããããªãããã«ããããšã§ããã¹ã察象ã®ã³ã³ããŒãã³ããåé¢ããŸããNockã¯ãå€éšãµãŒãã¹ã®åäœãå®çŸ©ããããã®äŸ¿å©ãªæ§æãæäŸããããããã®ããã·ã§ã³ã«æé©ãªããŒã«ã§ããåé¢ã¯ããã€ãºãããã©ãŒãã³ã¹ã®äœäžãé²ãããã«å¿
é ã§ãããäž»ã«ããŸããŸãªã·ããªãªãå¿çãã·ãã¥ã¬ãŒãããããã«å¿
èŠã§ã-åªãããã©ã€ãã·ãã¥ã¬ãŒã¿ãŒã¯ãæŸãã éã空ãæãããšã§ã¯ãªããå®å
šãªåµãæ··ä¹±ãããããããšã§ããããã¯ãä»ã®äžçãå·»ã蟌ãããšãªããåžžã«åäžã®ã³ã³ããŒãã³ãã«çŠç¹ãåœãŠãå¿
èŠããããã€ã¯ããµãŒãã¹ã¢ãŒããã¯ãã£ã§åŒ·åãããŠããŸãããã¹ãããã«ïŒã¢ããã³ã°ïŒã䜿çšããŠå€éšãµãŒãã¹ã®åäœãã·ãã¥ã¬ãŒãããããšã¯å¯èœã§ãããå±éãããã³ãŒãã«è§Šããã«ãããã¯ãŒã¯ã¬ãã«ã§åäœããŠããã¹ããçŽç²ãªãã©ãã¯ããã¯ã¹ã«ä¿ã€ããšãæãŸããã§ãã
â ãã以å€ã®å ŽåïŒäžéšã®ãµãŒãã¹ã¯ãéåžžã¯Dockerã䜿çšããŠãåŒã³åºãå
ãããŒã«ã«ã«ãããã€ã§ããåœã®ããŒãžã§ã³ãæäŸããŸããããã«ãããã»ããã¢ãããç°¡åã«ãªããããã©ãŒãã³ã¹ãåäžããŸãããããŸããŸãªå¿çã®ã·ãã¥ã¬ãŒã·ã§ã³ã«ã¯åœ¹ç«ã¡ãŸãããäžéšã®ãµãŒãã¹ã¯ããµã³ãããã¯ã¹ãç°å¢ãæäŸãããããå®éã®ãµãŒãã¹ã¯ãããããŸãããã³ã¹ããå¯äœçšã¯çºçããŸãã-ããã«ããããµãŒãããŒãã£ãµãŒãã¹ã®ã»ããã¢ããã®ãã€ãºãåæžãããŸãããã·ããªãªã®ã·ãã¥ã¬ãŒã·ã§ã³ãã§ããªããªããŸã
â ã³ãŒãäŸ
ðå€éšã³ã³ããŒãã³ããžã®ãããã¯ãŒã¯åŒã³åºãã鲿¢ããããšã§ãã·ããªãªãã·ãã¥ã¬ãŒããããã€ãºãæå°éã«æããããšãã§ããŸã
// Intercept requests for 3rd party APIs and return a predefined response
beforeEach(() => {
nock('http://localhost/user/').get(`/1`).reply(200, {
id: 1,
name: 'John',
});
});```
</details>
## ⪠ïž2.10 Test the response schema, mostly when there are auto-generated fields
:white_check_mark: **Do:** When it is impossible to assert for specific data, check for mandatory field existence and types. Sometimes, the response contains important fields with dynamic data that can't be predicted when writing the test, like dates and incrementing numbers. If the API contract promises that these fields won't be null and hold the right types, it's imperative to test it. Most assertion libraries support checking types. If the response is small, check the return data and type together within the same assertion (see code example). One more option is to verify the entire response against an OpenAPI doc (Swagger). Most test runners have community extensions that validate API responses against their documentation.
<br/>
â **Otherwise:** Although the code/API caller relies on some field with dynamic data (e.g., ID, date), it will not come in return and break the contract
<br/>
<details><summary>â <b>Code Examples</b></summary>
<br/>
### :clap: Asserting that fields with dynamic value exist and have the right type
```javascript
test('When adding a new valid order, Then should get back approval with 200 response', async () => {
// ...
//Assert
expect(receivedAPIResponse).toMatchObject({
status: 200,
data: {
id: expect.any(Number), // Any number satisfies this test
mode: 'approved',
},
});
});
âªïž2.12çµ±åã®ã³ãŒããŒã±ãŒã¹ãšã«ãªã¹ã確èªãã
â
宿œïŒçµ±åã確èªãããšãã¯ã幞ããªéãšæ²ããéãè¶
ããŠãã ããããšã©ãŒå¿çïŒHTTP 500ãšã©ãŒãªã©ïŒã ãã§ãªããå¿çãé
ãã¿ã€ã ã¢ãŠããããªã©ã®ãããã¯ãŒã¯ã¬ãã«ã®ç°åžžã確èªããŠãã ãããããã¯ãã³ãŒããå埩åããããã¿ã€ã ã¢ãŠãåŸã«æ£ãããã¹ããšããªã©ã®ããŸããŸãªãããã¯ãŒã¯ã·ããªãªãåŠçã§ããè匱ãªç«¶åç¶æ
ããªããå詊è¡çšã®åè·¯ãã¬ãŒã«ãŒãå«ãŸããŠããããšã蚌æããŸããè©å€ã®è¯ãã€ã³ã¿ãŒã»ãã¿ãŒããŒã«ã¯ããšãã©ã倱æããå€å¿ãªãµãŒãã¹ã®ãããªããŸããŸãªãããã¯ãŒã¯åäœãç°¡åã«ã·ãã¥ã¬ãŒãã§ããŸããããã©ã«ãã®HTTPã¯ã©ã€ã¢ã³ãã¿ã€ã ã¢ãŠãå€ãã·ãã¥ã¬ãŒããããå¿çæéãããé·ãå Žåã§ãèªèã§ããåŸ
æ©ããã«ããã«ã¿ã€ã ã¢ãŠãäŸå€ãã¹ããŒããŸã
â ãã以å€ã®å ŽåïŒãã¹ãŠã®ãã¹ãã«åæ ŒããŸãããµãŒãããŒãã£ãäŸå€çãªå¿çãéä¿¡ãããšãã«ã¯ã©ãã·ã¥ãããããšã©ãŒãæ£ããå ±åããªãã®ã¯æ¬çªç°å¢ã®ã¿ã§ãã
â ã³ãŒãäŸ
ðãããã¯ãŒã¯é害æã«ãåè·¯ãã¬ãŒã«ãŒã1æ¥ãç¯çŽã§ããããšãä¿èšŒããŸã
test('When users service replies with 503 once and retry mechanism is applied, then an order is added successfully', async () => {
//Arrange
nock.removeInterceptor(userServiceNock.interceptors[0])
nock('http://localhost/user/')
.get('/1')
.reply(503, undefined, { 'Retry-After': 100 });
nock('http://localhost/user/')
.get('/1')
.reply(200);
const orderToAdd = {
userId: 1,
productId: 2,
mode: 'approved',
};
//Act
const response = await axiosAPIClient.post('/order', orderToAdd);
//Assert
expect(response.status).toBe(200);
});
âªïž2.135ã€ã®æœåšçãªçµæããã¹ãããŸã
â
宿œïŒãã¹ããèšç»ãããšãã¯ã5ã€ã®å
žåçãªãããŒã®åºåãã«ããŒããããšãæ€èšããŠãã ããããã¹ããäœããã®ã¢ã¯ã·ã§ã³ïŒAPIåŒã³åºããªã©ïŒãããªã¬ãŒããŠãããšããåå¿ãçºçããäœãæå³ã®ããããšãçºçãããã¹ããå¿
èŠã«ãªããŸããç§ãã¡ã¯ç©äºãã©ã®ããã«æ©èœããããæ°ã«ããªãããšã«æ³šæããŠãã ãããç§ãã¡ã®çŠç¹ã¯ãçµæãã€ãŸãå€éšããç®ç«ã¡ããŠãŒã¶ãŒã«åœ±é¿ãäžããå¯èœæ§ã®ãããã®ã«ãããŸãããããã®çµæ/åå¿ã¯ã5ã€ã®ã«ããŽãªã«åé¡ã§ããŸãã
â¢å¿çâãã¹ãã¯ïŒAPIãªã©ãä»ããŠïŒã¢ã¯ã·ã§ã³ãåŒã³åºããå¿çãååŸããŸããå¿çããŒã¿ã®æ£ç¢ºæ§ãã¹ããŒããããã³HTTPã¹ããŒã¿ã¹ã®ãã§ãã¯ã«é¢ä¿ããããã«ãªããŸãã
â¢æ°ããç¶æ
-ã¢ã¯ã·ã§ã³ãåŒã³åºããåŸãå
¬çã«ã¢ã¯ã»ã¹å¯èœãªããŒã¿ã®äžéšã倿ŽãããŠããå¯èœæ§ããããŸã
â¢å€éšåŒã³åºã-ã¢ã¯ã·ã§ã³ãåŒã³åºããåŸãã¢ããªã¯HTTPãŸãã¯ãã®ä»ã®ãã©ã³ã¹ããŒããä»ããŠå€éšã³ã³ããŒãã³ããåŒã³åºãå ŽåããããŸããããšãã°ãSMSãéä¿¡ããããé»åã¡ãŒã«ãéä¿¡ããããã¯ã¬ãžããã«ãŒãã«è«æ±ãããããããã®é»è©±
â¢ã¡ãã»ãŒãžãã¥ãŒ-ãããŒã®çµæã¯ããã¥ãŒå
ã®ã¡ãã»ãŒãžã«ãªãå¯èœæ§ããããŸã
â¢å¯èŠ³æž¬æ§âãšã©ãŒãé¡èãªããžãã¹ã€ãã³ããªã©ãç£èŠããå¿
èŠã®ãããã®ãããã€ããããŸãããã©ã³ã¶ã¯ã·ã§ã³ã倱æããå Žåãæ£ããå¿çã ãã§ãªããæ£ãããšã©ãŒåŠçãšé©åãªãã®ã³ã°/ã¡ããªãã¯ãæåŸ
ãããŸãããã®æ
å ±ã¯ãéåžžã«éèŠãªãŠãŒã¶ãŒïŒã€ãŸããæ¬çªSRE /管çè
ïŒã«çŽæ¥éä¿¡ãããŸãã
Section 3ïžâ£: Frontend Testing
âª ïž 3.1 Separate UI from functionality
â
Do: When focusing on testing component logic, UI details become a noise that should be extracted, so your tests can focus on pure data. Practically, extract the desired data from the markup in an abstract way that is not too coupled to the graphic implementation, assert only on pure data (vs HTML/CSS graphic details) and disable animations that slow down. You might get tempted to avoid rendering and test only the back part of the UI (e.g. services, actions, store) but this will result in fictional tests that don't resemble the reality and won't reveal cases where the right data doesn't even arrive in the UI
â Otherwise: The pure calculated data of your test might be ready in 10ms, but then the whole test will last 500ms (100 tests = 1 min) due to some fancy and irrelevant animation
â Code Examples
ð Doing It Right Example: Separating out the UI details

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
});
ð Anti-Pattern Example: Assertion mix UI details and data
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-test-id="user">John Doe</li>]');
});
âª ïž 3.2 Query HTML elements based on attributes that are unlikely to change
â
Do: Query HTML elements based on attributes that are likely to survive graphic changes unlike CSS selectors and like form labels. If the designated element doesn't have such attributes, create a dedicated test attribute like 'test-id-submit-button'. Going this route not only ensures that your functional/logic tests never break because of look & feel changes but also it becomes clear to the entire team that this element and attribute are utilized by tests and shouldn't get removed
â Otherwise: You want to test the login functionality that spans many components, logic and services, everything is set up perfectly - stubs, spies, Ajax calls are isolated. All seems perfect. Then the test fails because the designer changed the div CSS class from 'thick-border' to 'thin-border'
â Code Examples
ð Doing It Right Example: Querying an element using a dedicated attribute for testing

// the markup code (part of React component)
<h3>
<Badge pill className="fixed_badge" variant="dark">
<span data-test-id="errorsLabel">{value}</span>
<!-- note the attribute data-test-id -->
</Badge>
</h3>
// 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");
});
ð Anti-Pattern Example: Relying on CSS attributes
<!-- 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 Whenever possible, test with a realistic and fully rendered component
â
Do: Whenever reasonably sized, test your component from outside like your users do, fully render the UI, act on it and assert that the rendered UI behaves as expected. Avoid all sort of mocking, partial and shallow rendering - this approach might result in untrapped bugs due to lack of details and harden the maintenance as the tests mess with the internals (see bullet 'Favour blackbox testing'). If one of the child components is significantly slowing down (e.g. animation) or complicating the setup - consider explicitly replacing it with a fake
With all that said, a word of caution is in order: this technique works for small/medium components that pack a reasonable size of child components. Fully rendering a component with too many children will make it hard to reason about test failures (root cause analysis) and might get too slow. In such cases, write only a few tests against that fat parent component and more tests against its children
â Otherwise: When poking into a component's internal by invoking its private methods, and checking the inner state - you would have to refactor all tests when refactoring the components implementation. Do you really have a capacity for this level of maintenance?
â Code Examples
ð Doing It Right Example: Working realistically with a fully rendered component

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
});
ð Anti-Pattern Example: Mocking the reality with shallow rendering
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 Don't sleep, use frameworks built-in support for async events. Also try to speed things up
â
Do: In many cases, the unit under test completion time is just unknown (e.g. animation suspends element appearance) - in that case, avoid sleeping (e.g. setTimeOut) and prefer more deterministic methods that most platforms provide. Some libraries allows awaiting on operations (e.g. Cypress cy.request('url')), other provide API for waiting like @testing-library/dom method wait(expect(element)). Sometimes a more elegant way is to stub the slow resource, like API for example, and then once the response moment becomes deterministic the component can be explicitly re-rendered. When depending upon some external component that sleeps, it might turn useful to hurry-up the clock. Sleeping is a pattern to avoid because it forces your test to be slow or risky (when waiting for a too short period). Whenever sleeping and polling is inevitable and there's no support from the testing framework, some npm libraries like wait-for-expect can help with a semi-deterministic solution
â Otherwise: When sleeping for a long time, tests will be an order of magnitude slower. When trying to sleep for small numbers, test will fail when the unit under test didn't respond in a timely fashion. So it boils down to a trade-off between flakiness and bad performance
â Code Examples
ð Doing It Right Example: E2E API that resolves only when the async operations is done (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
ð Doing It Right Example: Testing library that waits for DOM elements
// @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"));
});
ð Anti-Pattern Example: custom sleep code
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 Watch how the content is served over the network

â
Do: Apply some active monitor that ensures the page load under real network is optimized - this includes any UX concern like slow page load or un-minified bundle. The inspection tools market is no short: basic tools like pingdom, AWS CloudWatch, gcp StackDriver can be easily configured to watch whether the server is alive and response under a reasonable SLA. This only scratches the surface of what might get wrong, hence it's preferable to opt for tools that specialize in frontend (e.g. lighthouse, pagespeed) and perform richer analysis. The focus should be on symptoms, metrics that directly affect the UX, like page load time, meaningful paint, time until the page gets interactive (TTI). On top of that, one may also watch for technical causes like ensuring the content is compressed, time to the first byte, optimize images, ensuring reasonable DOM size, SSL and many others. It's advisable to have these rich monitors both during development, as part of the CI and most important - 24x7 over the production's servers/CDN
â Otherwise: It must be disappointing to realize that after such great care for crafting a UI, 100% functional tests passing and sophisticated bundling - the UX is horrible and slow due to CDN misconfiguration
â Code Examples
ð Doing It Right Example: Lighthouse page load inspection report

âª ïž 3.6 Stub flaky and slow resources like backend APIs
â
Do: When coding your mainstream tests (not E2E tests), avoid involving any resource that is beyond your responsibility and control like backend API and use stubs instead (i.e. test double). Practically, instead of real network calls to APIs, use some test double library (like Sinon, Test doubles, etc) for stubbing the API response. The main benefit is preventing flakiness - testing or staging APIs by definition are not highly stable and from time to time will fail your tests although YOUR component behaves just fine (production env was not meant for testing and it usually throttles requests). Doing this will allow simulating various API behavior that should drive your component behavior as when no data was found or the case when API throws an error. Last but not least, network calls will greatly slow down the tests
â Otherwise: The average test runs no longer than few ms, a typical API call last 100ms>, this makes each test ~20x slower
â Code Examples
ð Doing It Right Example: Stubbing or intercepting API calls

// 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-test-id="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 Have very few end-to-end tests that spans the whole system
â
Do: Although E2E (end-to-end) usually means UI-only testing with a real browser (See bullet 3.6), for other they mean tests that stretch the entire system including the real backend. The latter type of tests is highly valuable as they cover integration bugs between frontend and backend that might happen due to a wrong understanding of the exchange schema. They are also an efficient method to discover backend-to-backend integration issues (e.g. Microservice A sends the wrong message to Microservice B) and even to detect deployment failures - there are no backend frameworks for E2E testing that are as friendly and mature as UI frameworks like Cypress and Puppeteer. The downside of such tests is the high cost of configuring an environment with so many components, and mostly their brittleness - given 50 microservices, even if one fails then the entire E2E just failed. For that reason, we should use this technique sparingly and probably have 1-10 of those and no more. That said, even a small number of E2E tests are likely to catch the type of issues they are targeted for - deployment & integration faults. It's advisable to run those over a production-like staging environment
â Otherwise: UI might invest much in testing its functionality only to realizes very late that the backend returned payload (the data schema the UI has to work with) is very different than expected
âª ïž 3.8 Speed-up E2E tests by reusing login credentials
â
Do:å®éã®ããã¯ãšã³ããå«ã¿ãAPIåŒã³åºãã«æå¹ãªãŠãŒã¶ãŒããŒã¯ã³ã«äŸåããE2Eãã¹ãã§ã¯ããŠãŒã¶ãŒãäœæããããã¹ãŠã®ãªã¯ãšã¹ãã«ãã°ã€ã³ããã¬ãã«ã«ãã¹ããåé¢ããããšã¯å ±ãããŸããã代ããã«ããã¹ãã®å®è¡ãéå§ãããåã«1åã ããã°ã€ã³ãïŒã€ãŸãããã¹ãŠã®ããã¯ã®åã«ïŒãããŒã¯ã³ãããŒã«ã«ã¹ãã¬ãŒãžã«ä¿åããŠããªã¯ãšã¹ãéã§åå©çšããŸããããã¯ãã³ã¢ãã¹ãã®ååã®1ã€ã«éåããŠããããã§ããã€ãŸãããªãœãŒã¹ãçµåããã«ãã¹ããèªåŸçã«ç¶æããŸããããã¯æå¹ãªæžå¿µäºé
ã§ãããE2Eãã¹ãã§ã¯ããã©ãŒãã³ã¹ãéèŠãªæžå¿µäºé
ã§ãããåã
ã®ãã¹ããéå§ããåã«1ã3åã®APIãªã¯ãšã¹ããäœæãããšãå®è¡æéãã²ã©ããªãå¯èœæ§ããããŸããè³æ Œæ
å ±ãåå©çšããããšã¯ããã¹ããåããŠãŒã¶ãŒã¬ã³ãŒãã«å¯ŸããŠå®è¡ããå¿
èŠãããããšãæå³ããããã§ã¯ãããŸãã-ãŠãŒã¶ãŒã¬ã³ãŒãã«äŸåããŠããå ŽåïŒäŸïŒãã¹ããŠãŒã¶ãŒã®æ¯æãå±¥æŽïŒãããããã¹ãã®äžéšãšããŠãããã®ã¬ã³ãŒããçæããä»ã®ãã¹ããšãã®ååšãå
±æããªãããã«ããŠãã ããããŸããããã¯ãšã³ãã¯åœé ãããå¯èœæ§ãããããšã«ã泚æããŠãã ããããã¹ããããã³ããšã³ãã«çŠç¹ãåãããŠããå Žåã¯ãããã³ããšã³ããåé¢ããŠããã¯ãšã³ãAPIãã¹ã¿ãåããæ¹ãããå ŽåããããŸãïŒãåç
§ïŒãç®æ¡æžã3.6ïŒã
â ãã以å€ã®å ŽåïŒ 200ã®ãã¹ãã±ãŒã¹ãäžããããlogin = 100ms=20ç§ãšä»®å®ããŠäœåºŠããã°ã€ã³ããå Žåã®ã¿
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸïŒæ¯åã§ã¯ãªãããã¹ãŠã®åã«ãã°ã€ã³ãã

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ãµã€ãããããç§»åããã ãã®E2Eã¹ã¢ãŒã¯ãã¹ãã1ã€è¡ã
â
宿œïŒæ¬çªç£èŠãšéçºæã®å¥å
šæ§ãã§ãã¯ã®ããã«ããµã€ãã®ãã¹ãŠ/ã»ãšãã©ã®ããŒãžã«ã¢ã¯ã»ã¹ãã誰ãå£ããªãããšã確èªããåäžã®E2Eãã¹ããå®è¡ããŸãããã®ã¿ã€ãã®ãã¹ãã¯ãäœæãšä¿å®ãéåžžã«ç°¡åã§ãããããæè³åççãé«ããªããŸãããæ©èœããããã¯ãŒã¯ãããã³å±éã®åé¡ãå«ãããããçš®é¡ã®éå®³ãæ€åºã§ããŸããä»ã®ã¹ã¿ã€ã«ã®ç
ãšå¥å
šæ§ãã§ãã¯ã¯ãããã»ã©ä¿¡é Œæ§ãé«ããç¶²çŸ
çã§ã¯ãããŸãããäžéšã®éçšããŒã ã¯ãããŒã ããŒãžïŒæ¬çªç°å¢ïŒã«pingãéä¿¡ããã ãã§ããããããã±ãŒãžã³ã°ããã©ãŠã¶ãŒã®åé¡ãçºèŠããªãå€ãã®çµ±åãã¹ããå®è¡ããéçºè
ã§ããèšããŸã§ããªããã¹ã¢ãŒã¯ãã¹ãã¯æ©èœãã¹ãã«åã£ãŠä»£ãããã®ã§ã¯ãªããè¿
éãªç
æ¢ç¥åšãšããŠæ©èœããããšãç®çãšããŠããŸãã
â ãã以å€ã®å ŽåïŒãã¹ãŠãå®ç§ã«èŠããå¯èœæ§ãããããã¹ãŠã®ãã¹ãã«åæ Œããæ¬çªãã«ã¹ãã§ãã¯ãéœæ§ã§ãããPaymentã³ã³ããŒãã³ãã«ããã±ãŒãžã³ã°ã®åé¡ãããã/Paymentã«ãŒãã®ã¿ãã¬ã³ããªã³ã°ãããŠããŸãã
â ã³ãŒãäŸ
ðæ£ããè¡ãäŸïŒãã¹ãŠã®ããŒãžãç§»åããç

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.visit("https://mysite.com/Login");
cy.contains("Login");
cy.visit("https://mysite.com/About");
cy.contains("About");
});
âªïž3.10ã©ã€ãã®å
±åããã¥ã¡ã³ããšããŠãã¹ããå
¬éããŸã
â
宿œïŒã¢ããªã®ä¿¡é Œæ§ãé«ããããšã«å ããŠããã¹ãã¯ããŒãã«ã«å¥ã®é
åçãªæ©äŒããããããŸã-ã©ã€ãã¢ããªã®ããã¥ã¡ã³ããšããŠæ©èœããŸãããã¹ãã¯æ¬è³ªçã«æè¡çã§ã¯ãªã補å/UXèšèªã§è©±ããããé©åãªããŒã«ã䜿çšãããšããã¹ãŠã®ãã¢ïŒéçºè
ãšãã®é¡§å®¢ïŒã倧å¹
ã«èª¿æŽããã³ãã¥ãã±ãŒã·ã§ã³ã¢ãŒãã£ãã¡ã¯ããšããŠæ©èœã§ããŸããããšãã°ãäžéšã®ãã¬ãŒã ã¯ãŒã¯ã§ã¯ã人éãèªããèšèªã䜿çšããŠãããŒãšæåŸ
ïŒã€ãŸãããã¹ãèšç»ïŒã衚çŸã§ããããã補åãããŒãžã£ãŒãå«ããã¹ãŠã®å©å®³é¢ä¿è
ããã©ã€ãèŠä»¶ããã¥ã¡ã³ãã«ãªã£ãã°ããã®ãã¹ããèªã¿ãæ¿èªããå
±åäœæ¥ãè¡ãããšãã§ããŸãããã®ææ³ã¯ã顧客ãåãå
¥ãåºæºãå¹³æãªèšèã§å®çŸ©ã§ããããããåãå
¥ããã¹ãããšãåŒã°ããŸããããã¯BDDïŒããã€ãã¢é§åãã¹ãïŒã§ããã®æãçŽç²ãªåœ¢ã§ããããå¯èœã«ãã人æ°ã®ãããã¬ãŒã ã¯ãŒã¯ã®1ã€ã¯ãJavaScriptãã¬ãŒããŒãæã€Cucumberã§ãã以äžã®äŸãåç
§ããŠãã ããããã1ã€ã®é¡äŒŒããŠãããç°ãªãæ©äŒã§ãã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èªåããŒã«ã§èŠèŠçãªåé¡ãæ€åºãã
â
è¡ãïŒ Setup automated tools to capture UI screenshots when changes are presented and detect visual issues like content overlapping or breaking. This ensures that not only the right data is prepared but also the user can conveniently see it. This technique is not widely adopted, our testing mindset leans toward functional tests but it's the visuals what the user experience and with so many device types it's very easy to overlook some nasty UI bug. Some free tools can provide the basics - generate and save screenshots for the inspection of human eyes. While this approach might be sufficient for small apps, it's flawed as any other manual testing that demands human labor anytime something changes. On the other hand, it's quite challenging to detect UI issues automatically due to the lack of clear definition - this is where the field of 'Visual Regression' chime in and solve this puzzle by comparing old UI with the latest changes and detect differences. Some OSS/free tools can provide some of this functionality (e.g. wraith, PhantomCSS but might charge significant setup time. The commercial line of tools (e.g. Applitools, Percy.io) takes is a step further by smoothing the installation and packing advanced features like management UI, alerting, smart capturing by eliminating 'visual noise' (e.g. ads, animations) and even root cause analysis of the DOM/CSS changes that led to the issue
â Otherwise: How good is a content page that display great content (100% tests passed), loads instantly but half of the content area is hidden?
â Code Examples
ð Anti-Pattern Example: A typical visual regression - right content that is served badly

ð Doing It Right Example: Configuring wraith to capture and compare UI snapshots

â# 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
ð Doing It Right Example: Using Applitools to get snapshot comparison and other advanced features

import * as todoPage from "../page-objects/todo-page";
describe("visual validation", () => {
before(() => todoPage.navigate());
beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" }));
afterEach(() => cy.eyesClose());
it("should look good", () => {
cy.eyesCheckWindow("empty todo list");
todoPage.addTodo("Clean room");
todoPage.addTodo("Learn javascript");
cy.eyesCheckWindow("two todos");
todoPage.toggleTodo(0);
cy.eyesCheckWindow("mark as completed");
});
});
Section 4ïžâ£: Measuring Test Effectiveness
âª ïž 4.1 Get enough coverage for being confident, ~80% seems to be the lucky number
â
Do: The purpose of testing is to get enough confidence for moving fast, obviously the more code is tested the more confident the team can be. Coverage is a measure of how many code lines (and branches, statements, etc) are being reached by the tests. So how much is enough? 10â30% is obviously too low to get any sense about the build correctness, on the other side 100% is very expensive and might shift your focus from the critical paths to the exotic corners of the code. The long answer is that it depends on many factors like the type of applicationâââif youâre building the next generation of Airbus A380 than 100% is a must, for a cartoon pictures website 50% might be too much. Although most of the testing enthusiasts claim that the right coverage threshold is contextual, most of them also mention the number 80% as a thumb of a rule (Fowler: âin the upper 80s or 90sâ) that presumably should satisfy most of the applications.
Implementation tips: You may want to configure your continuous integration (CI) to have a coverage threshold (Jest link) and stop a build that doesnât stand to this standard (itâs also possible to configure threshold per component, see code example below). On top of this, consider detecting build coverage decrease (when a newly committed code has less coverage)âââthis will push developers raising or at least preserving the amount of tested code. All that said, coverage is only one measure, a quantitative based one, that is not enough to tell the robustness of your testing. And it can also be fooled as illustrated in the next bullets
â Otherwise: Confidence and numbers go hand in hand, without really knowing that you tested most of the systemâââthere will also be some fear and fear will slow you down
â Code Examples
ð Example: A typical coverage report

ð Doing It Right Example: Setting up coverage per component (using Jest)


âª ïž 4.2 Inspect coverage reports to detect untested areas and other oddities
â
Do: Some issues sneak just under the radar and are really hard to find using traditional tools. These are not really bugs but more of surprising application behavior that might have a severe impact. For example, often some code areas are never or rarely being invokedâââyou thought that the âPricingCalculatorâ class is always setting the product price but it turns out it is actually never invoked although we have 10000 products in DB and many sales⊠Code coverage reports help you realize whether the application behaves the way you believe it does. Other than that, it can also highlight which types of code is not testedâââbeing informed that 80% of the code is tested doesnât tell whether the critical parts are covered. Generating reports is easyâââjust run your app in production or during testing with coverage tracking and then see colorful reports that highlight how frequent each code area is invoked. If you take your time to glimpse into this dataâââyou might find some gotchas
â Otherwise: If you donât know which parts of your code are left un-tested, you donât know where the issues might come from
â Code Examples
ð Anti-Pattern Example: Whatâs wrong with this coverage report?
Based on a real-world scenario where we tracked our application usage in QA and find out interesting login patterns (Hint: the amount of login failures is non-proportional, something is clearly wrong. Finally it turned out that some frontend bug keeps hitting the backend login API)

âª ïž 4.3 Measure logical coverage using mutation testing
â
Do: The Traditional Coverage metric often lies: It may show you 100% code coverage, but none of your functions, even not one, return the right response. How come? it simply measures over which lines of code the test visited, but it doesnât check if the tests actually tested anythingâââasserted for the right response. Like someone whoâs traveling for business and showing his passport stampsâââthis doesnât prove any work done, only that he visited few airports and hotels.
Mutation-based testing is here to help by measuring the amount of code that was actually TESTED not just VISITED. Stryker is a JavaScript library for mutation testing and the implementation is really neat:
(1) it intentionally changes the code and âplants bugsâ. For example the code newOrder.price===0 becomes newOrder.price!=0. This âbugsâ are called mutations
(2) it runs the tests, if all succeed then we have a problemâââthe tests didnât serve their purpose of discovering bugs, the mutations are so-called survived. If the tests failed, then great, the mutations were killed.
Knowing that all or most of the mutations were killed gives much higher confidence than traditional coverage and the setup time is similar
â Otherwise: Youâll be fooled to believe that 85% coverage means your test will detect bugs in 85% of your code
â Code Examples
ð Anti-Pattern Example: 100% coverage, 0% testing

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({ assignee: "John@mailer.com", price: 120 });
}); //Triggers 100% code coverage, but it doesn't check anything
ð Doing It Right Example: Stryker reports, a tool for mutation testing, detects and counts the amount of code that is not tested (Mutations)

⪠ïž4.4 Preventing test code issues with Test linters
â
Do: A set of ESLint plugins were built specifically for inspecting the tests code patterns and discover issues. For example, eslint-plugin-mocha will warn when a test is written at the global level (not a son of a describe() statement) or when tests are skipped which might lead to a false belief that all tests are passing. Similarly, eslint-plugin-jest can, for example, warn when a test has no assertions at all (not checking anything)
â Otherwise: Seeing 90% code coverage and 100% green tests will make your face wear a big smile only until you realize that many tests arenât asserting for anything and many test suites were just skipped. Hopefully, you didnât deploy anything based on this false observation
â Code Examples
ð Anti-Pattern Example: A test case full of errors, luckily all are caught by Linters
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
});
Section 5ïžâ£: CI and Other Quality Measures
âª ïž 5.1 Enrich your linters and abort builds that have linting issues
â
Do: Linters are a free lunch, with 5 min setup you get for free an auto-pilot guarding your code and catching significant issue as you type. Gone are the days where linting was about cosmetics (no semi-colons!). Nowadays, Linters can catch severe issues like errors that are not thrown correctly and losing information. On top of your basic set of rules (like ESLint standard or Airbnb style), consider including some specializing Linters like eslint-plugin-chai-expect that can discover tests without assertions, eslint-plugin-promise can discover promises with no resolve (your code will never continue), eslint-plugin-security which can discover eager regex expressions that might get used for DOS attacks, and eslint-plugin-you-dont-need-lodash-underscore is capable of alarming when the code uses utility library methods that are part of the V8 core methods like Lodash._map(âŠ)
â Otherwise: Consider a rainy day where your production keeps crashing but the logs donât display the error stack trace. What happened? Your code mistakenly threw a non-error object and the stack trace was lost, a good reason for banging your head against a brick wall. A 5 min linter setup could detect this TYPO and save your day
â Code Examples
ð Anti-Pattern Example: The wrong Error object is thrown mistakenly, no stack-trace will appear for this error. Luckily, ESLint catches the next production bug

âª ïž 5.2 Shorten the feedback loop with local developer-CI
â
Do: Using a CI with shiny quality inspections like testing, linting, vulnerabilities check, etc? Help developers run this pipeline also locally to solicit instant feedback and shorten the feedback loop. Why? an efficient testing process constitutes many and iterative loops: (1) try-outs -> (2) feedback -> (3) refactor. The faster the feedback is, the more improvement iterations a developer can perform per-module and perfect the results. On the flip, when the feedback is late to come fewer improvement iterations could be packed into a single day, the team might already move forward to another topic/task/module and might not be up for refining that module.
Practically, some CI vendors (Example: CircleCI local CLI) allow running the pipeline locally. Some commercial tools like wallaby provide highly-valuable & testing insights as a developer prototype (no affiliation). Alternatively, you may just add npm script to package.json that runs all the quality commands (e.g. test, lint, vulnerabilities)âââuse tools like concurrently for parallelization and non-zero exit code if one of the tools failed. Now the developer should just invoke one commandâââe.g. ânpm run qualityââââto get instant feedback. Consider also aborting a commit if the quality check failed using a githook (husky can help)
â Otherwise: When the quality results arrive the day after the code, testing doesnât become a fluent part of development rather an after the fact formal artifact
â Code Examples
ð Doing It Right Example: npm scripts that perform code quality inspection, all are run in parallel on demand or when a developer is trying to push new code
"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 Perform e2e testing over a true production-mirror
â
Do: End to end (e2e) testing are the main challenge of every CI pipelineâââcreating an identical ephemeral production mirror on the fly with all the related cloud services can be tedious and expensive. Finding the best compromise is your game: Docker-compose allows crafting isolated dockerized environment with identical containers using a single plain text file but the backing technology (e.g. networking, deployment model) is different from real-world productions. You may combine it with âAWS Localâ to work with a stub of the real AWS services. If you went serverless multiple frameworks like serverless and AWS SAM allows the local invocation of FaaS code.
The huge Kubernetes ecosystem is yet to formalize a standard convenient tool for local and CI-mirroring though many new tools are launched frequently. One approach is running a âminimized-Kubernetesâ using tools like Minikube and MicroK8s which resemble the real thing only come with less overhead. Another approach is testing over a remote âreal-Kubernetesâ, some CI providers (e.g. Codefresh) has native integration with Kubernetes environment and make it easy to run the CI pipeline over the real thing, others allow custom scripting against a remote Kubernetes.
â Otherwise: Using different technologies for production and testing demands maintaining two deployment models and keeps the developers and the ops team separated
â Code Examples
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
ç°å¢ïŒ
ååïŒtest-for-ci
âªïž5.4ãã¹ãå®è¡ã®äžŠåå
â
宿œïŒæ£ããè¡ãããå Žåããã¹ãã¯24æé幎äžç¡äŒã®å人ã§ãããã»ãŒç¬æã«ãã£ãŒãããã¯ãæäŸããŸããå®éã«ã¯ã1ã€ã®ã¹ã¬ããã§500åã®CPUããŠã³ããŠããããã¹ããå®è¡ãããšãæéãããããããå¯èœæ§ããããŸãã幞ããªããšã«ãææ°ã®ãã¹ãã©ã³ããŒãšCIãã©ãããã©ãŒã ïŒJestãAVAãMochaæ¡åŒµæ©èœãªã©ïŒã¯ããã¹ããè€æ°ã®ããã»ã¹ã«äžŠååãããã£ãŒãããã¯æéã倧å¹
ã«æ¹åããããšãã§ããŸããäžéšã®CIãã³ããŒã¯ãã³ã³ãããŒéã§ãã¹ãã䞊ååããããšãããïŒïŒïŒããã£ãŒãããã¯ã«ãŒããããã«ççž®ããŸããããŒã«ã«ã§è€æ°ã®ããã»ã¹ã䜿çšããå Žåã§ããè€æ°ã®ãã·ã³ã䜿çšããã¯ã©ãŠãCLIã䜿çšããå Žåã§ããéèŠã䞊ååããŠããããããç°ãªãããã»ã¹ã§å®è¡ãããå¯èœæ§ãããããããã¹ããèªåŸçã«ç¶æããŸãã
â ãã以å€ã®å ŽåïŒæ¬¡ã®æ©èœããã§ã«ã³ãŒãã£ã³ã°ããŠãããããæ°ããã³ãŒããããã·ã¥ããŠãã1æéåŸã«ãã¹ãçµæãååŸããããšã¯ããã¹ãã®é¢é£æ§ãäœãããããã®åªããã¬ã·ãã§ãã
â ã³ãŒãäŸ
ðæ£ããå®è¡äŸïŒäžŠååããã¹ãããããšã§ãMocha parallelïŒJestã¯åŸæ¥ã®Mochaãç°¡åã«äžåããŸãïŒã¯ã¬ãžããïŒJavaScript Test-Runners BenchmarkïŒ

âªïž5.5ã©ã€ã»ã³ã¹ãšççšãã§ãã¯ã䜿çšããŠæ³çãªåé¡ãåé¿ãã
â
宿œïŒã©ã€ã»ã³ã¹ãšççšã®åé¡ã¯ãããããä»ã®ãšããããªãã®äž»ãªé¢å¿äºã§ã¯ãããŸãããã10å以å
ã«ãã®ããã¯ã¹ã«ããã§ãã¯ãå
¥ããŠã¿ãŸãããïŒã©ã€ã»ã³ã¹ãã§ãã¯ãççšãã§ãã¯ïŒç¡æãã©ã³ã§åçšïŒãªã©ã®npmããã±ãŒãžã®æã¯ãCIãã€ãã©ã€ã³ã«ç°¡åã«çµã¿èŸŒãããšãã§ããå¶éä»ãã©ã€ã»ã³ã¹ã®äŸåé¢ä¿ããStack Overflowããã³ããŒããŠè²Œãä»ããããäžéšã®èäœæš©ã䟵害ããŠãããšæãããã³ãŒããªã©ã®æ²ãã¿ãæ€æ»ã§ããŸãã
â ãã以å€ã®å ŽåïŒæå³ããã«ãéçºè
ãäžé©åãªã©ã€ã»ã³ã¹ã®ããã±ãŒãžã䜿çšããããåçšã³ãŒããã³ããŒããŠè²Œãä»ãããããŠãæ³çãªåé¡ã«ééããå¯èœæ§ããããŸã
â ã³ãŒãäŸ
ðãããæ£ããè¡ãäŸïŒ
//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ç£æ»çµæ

âªïž5.7äŸåé¢ä¿ã®æŽæ°ãèªååãã
â
DoïŒ Yarnãšnpmã®package-lock.jsonã®ææ°ã®å°å
¥ã«ãããæ·±å»ãªèª²é¡ãçºçããŸããïŒå°çãžã®éã¯åæã§èè£
ãããŠããŸãïŒâããã©ã«ãã§ã¯ãããã±ãŒãžã¯æŽæ°ãããªããªããŸããã'npminstall'ãš'npmupdate'ã䜿çšããŠå€ãã®æ°ãããããã€ã¡ã³ããå®è¡ããŠããããŒã ã§ãããæ°ããæŽæ°ãååŸããŸãããããã¯ãããããæšæºä»¥äžã®äŸåããã±ãŒãžããŒãžã§ã³ã«ã€ãªããããææªã®å Žåè匱ãªã³ãŒãã«ã€ãªãããŸããããŒã ã¯çŸåšãéçºè
ã®åæãšã¡ã¢ãªã«äŸåããŠãpackage.jsonãæåã§æŽæ°ããããncuãªã©ã®ããŒã«ãæåã§äœ¿çšãããããŠããŸããããä¿¡é Œæ§ã®é«ãæ¹æ³ã¯ãæãä¿¡é Œæ§ã®é«ãäŸåé¢ä¿ããŒãžã§ã³ãååŸããããã»ã¹ãèªååããããšã§ãããç¹å¹è¬ã®è§£æ±ºçã¯ãããŸãããã2ã€ã®å¯èœãªèªååã®éããããŸãã
(1) CI can fail builds that have obsolete dependenciesâââusing tools like ânpm outdatedâ or ânpm-check-updates (ncu)â . Doing so will enforce developers to update dependencies.
(2) Use commercial tools that scan the code and automatically send pull requests with updated dependencies. One interesting question remaining is what should be the dependency update policyâââupdating on every patch generates too many overhead, updating right when a major is released might point to an unstable version (many packages found vulnerable on the very first days after being released, see the eslint-scope incident).
An efficient update policy may allow some âvesting periodââââlet the code lag behind the @latest for some time and versions before considering the local copy as obsolete (e.g. local version is 1.3.1 and repository version is 1.3.8)
â Otherwise: Your production will run packages that have been explicitly tagged by their author as risky
â Code Examples
ð Example: ncu can be used manually or within a CI pipeline to detect to which extent the code lag behind the latest versions

âª ïž 5.8 Other, non-Node related, CI tips
â
Do: This post is focused on testing advice that is related to, or at least can be exemplified with Node JS. This bullet, however, groups few non-Node related tips that are well-known
- Use a declarative syntax. This is the only option for most vendors but older versions of Jenkins allows using code or UI
- Opt for a vendor that has native Docker support
- Fail early, run your fastest tests first. Create a âSmoke testingâ step/milestone that groups multiple fast inspections (e.g. linting, unit tests) and provide snappy feedback to the code committer
- Make it easy to skim-through all build artifacts including test reports, coverage reports, mutation reports, logs, etc
- Create multiple pipelines/jobs for each event, reuse steps between them. For example, configure a job for feature branch commits and a different one for master PR. Let each reuse logic using shared steps (most vendors provide some mechanism for code reuse)
- Never embed secrets in a job declaration, grab them from a secret store or from the jobâs configuration
- Explicitly bump version in a release build or at least ensure the developer did so
- Build only once and perform all the inspections over the single build artifact (e.g. Docker image)
- Test in an ephemeral environment that doesnât drift state between builds. Caching node_modules might be the only exception
â Otherwise: Youâll miss years of wisdom
âª ïž 5.9 Build matrix: Run the same CI steps using multiple Node versions
â
Do: Quality checking is about serendipity, the more ground you cover the luckier you get in detecting issues early. When developing reusable packages or running a multi-customer production with various configuration and Node versions, the CI must run the pipeline of tests over all the permutations of configurations. For example, assuming we use MySQL for some customers and Postgres for othersâââsome CI vendors support a feature called âMatrixâ which allow running the suit of testing against all permutations of MySQL, Postgres and multiple Node version like 8, 9 and 10. This is done using configuration only without any additional effort (assuming you have testing or any other quality checks). Other CIs who doesnât support Matrix might have extensions or tweaks to allow that
â Otherwise: So after doing all that hard work of writing testing are we going to let bugs sneak in only because of configuration issues?
â Code Examples
ð Example: Using Travis (CI vendor) build definition to run the same test over multiple Node versions
language: node_js
node_js:
- "7"
- "6"
- "5"
- "4"
install:
- npm install
script:
- npm run test
Team
Yoni Goldberg

Role: Writer
æŠèŠïŒç§ã¯ç¬ç«ããã³ã³ãµã«ã¿ã³ãã§ããããã©ãŒãã¥ã³500äŒæ¥ãã¬ã¬ãŒãžã®æ°èäŒæ¥ãšååããŠJSããã³Node.jsã¢ããªã±ãŒã·ã§ã³ã磚ããŠããŸããç§ãé
äºããããã¹ãã®èžè¡ãç¿åŸããããšãç®æããŠããä»ã®ã©ã®ãããã¯ããããç§ã¯Node.jsã®ãã¹ããã©ã¯ãã£ã¹ã®èè
ã§ããããŸã
ðãªã³ã©ã€ã³ã³ãŒã¹ïŒãã®ã¬ã€ããæ°ã«å
¥ãããã¹ãã¹ãã«ãæå€§éã«çºæ®ãããã§ããïŒNode.jsãšJavaScriptãAããZãŸã§ãã¹ãããç§ã®å
æ¬çãªã³ãŒã¹ã«ã¢ã¯ã»ã¹ããããšãæ€èšããŠãã ãã
åŸãïŒ
圹å²ïŒæè¡ã¬ãã¥ãŒæ
åœè
ããã³ã¢ããã€ã¶ãŒ
ãã¹ãŠã®ããã¹ããæ¹èšãæ¹åããªã³ããããªãã·ã¥ããããã«æ³šæãæããŸãã
æŠèŠïŒãã«ã¹ã¿ãã¯Webãšã³ãžãã¢ãNode.jsãGraphQLæå¥œå®¶
圹å²ïŒã³ã³ã»ããããã¶ã€ã³ãåªããã¢ããã€ã¹
æŠèŠïŒç¥èè±å¯ãªããã³ããšã³ãéçºè
ãCSSãšãã¹ããŒããçµµæåããªãŒã¯
圹å²ïŒãã®ãããžã§ã¯ãã®å®è¡ãç¶æããã»ãã¥ãªãã£é¢é£ã®ãã©ã¯ãã£ã¹ã確èªããŸã
æŠèŠïŒ Node.jsãããžã§ã¯ããšWebã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ã«åãçµãã®ã倧奜ãã§ãã
å¯çš¿è
âš
ãã®ãªããžããªã«è²¢ç®ããŠããããããã®çŽ æŽããã人ã
ã«æè¬ããŸãïŒ