喬丹弄清楚為什麼他的間諜沒有從事間諜活動
示例代碼在這裡
上一篇文章我瀏覽了鏈接檢查器的所有代碼以嘗試將其產品化。我希望它可以投入生產,並且包括單元測試。有些測試並沒有按照我的預期進行。這篇文章介紹了我在深入研究並讓它們發揮作用時學到的一些東西。
將函數分離到自己的文件或模塊中
我遇到的最大問題是我的間諜。監視時,您必須監視模塊,然後監視函數,例如 sinon.spy(moduleName, 'function/methodName')
.我最初在同一個文件中有很多函數,這導致了一些問題。
例如,我的 checkLinks()
函數調用domainCheck()
.因為這兩個函數都在同一個文件中並且我需要一個模塊,所以我只是做了 import * as findDeadLinksFunctions from './../findDeadLinks;
獲取一個模塊,然後使用 sinon.spy(findDeadLinksFunctions, 'domainCheck');
進行間諜活動 .間諜永遠不會被召喚。原因是它的行為幾乎就像在監視不同的東西。
解決方案是將這個和許多其他功能導出到他們自己的文件中。我將不相互調用的函數放入 helpers.ts
文件然後像這樣窺探:
import * as helpers from './../helpers';
...
it('should call domainChecK()', async () => {
const originalLinkObject: helpers.ILinkObject = {
link: 'https://javascriptwebscrapingguy.com/jordan-takes-advantage-of-multithreaded-i-o-in-nodejs/',
status: null,
locationOfLink: 'https://javascriptwebscrapingguy.com'
};
const originalLinks = [];
const domain = 'https://javascriptwebscrapingguy.com';
const desiredIOThreads = 4;
nock('https://javascriptwebscrapingguy.com').get('/jordan-takes-advantage-of-multithreaded-i-o-in-nodejs/').reply(200, '<button>click me</button>');
domainCheckSpy = sinon.spy(helpers, 'domainCheck');
await checkLinkFunction.checkLink(originalLinkObject, originalLinks, domain, desiredIOThreads);
expect(domainCheckSpy.callCount).to.equal(1);
});
需要注意的是,我仍然可以直接從 helpers.ts
導入域檢查 在實際的 checkLink()
內 功能,如下圖。所以只要它在自己的模塊中(或在這種情況下充當模塊的文件),它就可以很好地工作。
import { domainCheck, ILinkObject, getLinks } from './helpers';
...
if (newDomain) {
if (html && domainCheck(linkObject.link, domain, newDomain)) {
newLinks = await getLinks(html, domain, linkObject.link, false);
}
}
恢復存根與恢復間諜
出於某種原因,我不得不在 afterEach
中恢復我的存根 .本來,我會做這樣的事情:
domainCheckSpy = sinon.spy(helpers, 'domainCheck');
getLinksStub = sinon.stub(helpers, 'getLinks');
// some test stuff
domainCheckSpy.restore();
getLinksStub.restore();
這對間諜很有用。如果我嘗試使用存根來執行此操作,則無論在 getLinks
何處,該功能都將永遠無法恢復 被使用它會返回 undefined 就像這個存根導致它做的那樣。
如果我在 afterEach
內完成 它沒有問題。我最終在下面這樣做了。我有條件,因為不是每個函數都使用 spy 或 stub。
describe('checkLink()', () => {
let domainCheckStub;
let domainCheckSpy;
let getLinksSpy;
let getLinksStub;
let checkLinkSpy;
afterEach(() => {
if (domainCheckStub) {
domainCheckStub.restore();
}
if (domainCheckSpy) {
domainCheckSpy.restore();
}
if (getLinksSpy) {
getLinksSpy.restore();
}
if (getLinksStub) {
getLinksStub.restore();
}
if (checkLinkSpy) {
checkLinkSpy.restore();
}
});
...
測試遞歸函數
checkLink()
調用自己。有時很多。我想要一種方法來測試它是否經常或盡可能少地調用自己。在我的測試中,我使用 import * as checkLinkFunction from "../checkLink";
導入了它 並將其稱為 promises.push(checkLink(linkToCheck, links, domain, desiredIOThreads));
.當我預計它會調用自己 3 次,其中包括 2 次遞歸調用時,它只是在最初的時候調用了自己。
這篇 stackoverflow 帖子非常有幫助。我只需要從自身導入該函數作為它自己的模塊並以這種方式遞歸調用它,然後它就完美地工作了。
import * as checkLinkFunction from './checkLink';
...
// Have to call the imported function so tests work: https://stackoverflow.com/a/51604652/2287595
promises.push(checkLinkFunction.checkLink(linkToCheck, links, domain, desiredIOThreads));
設置測試發現了一個大錯誤
這真是太棒了。我的代碼中有一個我不知道發生的大錯誤。該代碼似乎正在工作,我可能永遠不會發現這個錯誤。我使用的發現錯誤的測試是 findDeadLinks.spec.ts
中的這個 .
it('should return the number of bad links (if one 404 and one 200, one bad link)', async () => {
const returnLinks: helpers.ILinkObject[] = [
{ link: 'https://heyAnotherBuddy.com', status: null, locationOfLink: 'https://javascriptwebscrapingguy.com' },
{ link: 'https://heyBuddy.com', status: null, locationOfLink: 'https://javascriptwebscrapingguy.com' }
];
getLinksStub = sinon.stub(helpers, 'getLinks').returns(Promise.resolve(returnLinks));
nock(domainToSend).get('/').reply(200);
nock("https://heyBuddy.com").get('/').reply(200);
nock("https://heyAnotherBuddy.com").get('/').reply(400);
const links = await findDeadLinks(domainToSend, desiredIOThreadsToSend);
expect(links.length).to.equal(1);
});
我的數組中有兩個鏈接,我希望它會像我在那裡顯示的那樣返回。它返回的鏈接應該只有一個,因為我們只返回了錯誤鏈接,並且只有一個狀態為 400,但它返回了 0 個錯誤鏈接。
這是罪魁禍首:
let linkToReplaceIndex = links.findIndex(linkObject => linkObject.link === linkObject.link);
links[linkToReplaceIndex] = linkObject;
看到問題了嗎?我沒有。很長時間沒有。我一直在搞砸這個試圖弄清楚發生了什麼。如果您仔細觀察,您會發現問題所在。 linkObject => linkObject.link === linkObject.link
.我正在檢查它自己,所以它每次都會在索引 0 處返回 true。 總是 替換索引 0 處的鏈接。
就我而言,我有 heyAnotherBuddy.com
首先是 heyBuddy.com
在第二個位置。它將通過第一次迭代並且工作得很好。然後在第二次迭代中,它將替換 heyAnotherBuddy.com
與heyBuddy.com
狀態為200。
讓我很難找到的最重要的事情是 heyBuddy.com
的狀態正在更新。它從未在索引 0 處,但不知何故它的狀態得到了更新。我傳遞給我的 checkLink
的鏈接 函數仍然被引用到原始鏈接數組中的那個。更新其狀態會在鏈接數組中自動更新它。所以,我只是撕掉了 linkToReplaceIndex
一塊,一切都很完美。
結論
我學到了更多關於測試的知識。我抓到了一個大蟲子。而且……我有一個非常不純的功能。 checkLink
肯定會影響其功能之外的事物。我不喜歡這個。這是我必須考慮更多並找到更好的方法的事情。
總的來說,美好的一天。做了很多好事。
Jordan 弄清楚為什麼他的間諜沒有進行間諜活動的帖子首先出現在 JavaScript Web Scraping Guy 上。