๐ Chapter 8: ๋ฉ๋ชจ์ด์ ์ด์ ํจํด
๐ ๋จ์ ํ ์คํธโ
TDD๋ฅผ ์ค์ฒ ์ค์ธ ์นํ์ด๋ memoizedRestaurantApi
ํผ์ฌ๋๋ฅผ ๊ตฌํํ๊ธฐ ์ํด ๊ฐ์ฅ ๋จผ์ ํ ์ผ์ ๋จ์ ํ
์คํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ด๋ค. ์นํ์ ์ฌ๋ฟ์ด UI๋ฅผ ์์ฃผ ๊ณ ์น์ง ์๋๋ก ์ฌ๋ฟ์ด ์ฌ์ฉํ ํจ์์ ๊ฐ์ getRestaurantsNearConference
ํจ์๋ฅผ ์ ์คํฉํธ๊ฐ ๊ฐ๋ฏธ๋ ์๋ํํฐ API์ ํ์ถํ๊ธฐ๋ก ํ๋ค.
์์ ์๋ํํฐ API๋ฅผ ํ์ฅํ์ฌ getRestaurantsNearConference
๋ฉ์๋๋ฅผ ์ถ๊ฐํ ์ฝ๋์ ๋จ์ํ
์คํธ์ ๋ง์ฐฌ๊ฐ์ง๋ก, API๊ฐ ์ค์ ๋ก ๋ฐํํ ๊ฐ์ฒด์ ํ์
์ ์ ๊ฒฝ ์ธ ํ์๊ฐ ์๋ค. ๋ฐ๋ผ์ ํ๋ผ๋ฏธ์ค๋ฅผ ์ด ๋น๋๊ธฐ ์ฝ๋ ํ
์คํธ์ ์์ง๊ตฌ๋ ํ ์์ ๋๋ฌธ์ ์ฝ๋งค์ด์ง ์์๋ ๋๋ค.
describe('memoizedRestaurantApi', () => {
'use strict';
var api;
var service;
var returnedFromService;
beforeEach(() => {
api = ThirdParty.restaurantApi();
service = Conference.memoizedRestaurantApi(api);
returnedFromService = {};
});
describe('getRestaurantsNearConference(cuisine)', () => {
it('๊ธฐ๋ ์ธ์๋ฅผ ๋๊ฒจ api์ getRestaurantsNearConference๋ฅผ ์คํ', () => {
var cuisine = '๋ถ์';
spyOn(api, 'getRestaurantsNearConference');
service.getRestaurantsNearConference(cuisine);
var args = api.getRestaurantsNearConference.calls.argsFor(0);
expect(args[0]).toEqual(cuisine);
});
it('์๋ํํฐ API์ ๋ฐํ๊ฐ์ ๋ฐํํ๋ค', () => {
spyOn(api, 'getRestaurantsNearConference').and.returnValue(returnedFromService);
var value = service.getRestaurantsNearConference('Asian Fusion');
expect(value).toBe(returnedFromService);
});
it('๊ฐ์ ์๋ฆฌ๋ฅผ ์ฌ๋ฌ ๋ฒ ์์ฒญํด๋ api๋ ํ ๋ฒ๋ง ์์ฒญํ๋ค', () => {
var cuisine = '๋ถ์';
spyOn(api, 'getRestaurantsNearConference').and.returnValue(returnedFromService);
var iterations = 5;
for (var i = 0; i < iterations; i++) {
var value = service.getRestaurantsNearConference(cuisine);
}
expect(api.getRestaurantsNearConference.calls.count()).toBe(1);
});
it('๊ฐ์ ์๋ฆฌ๋ฅผ ์ฌ๋ฌ ๋ฒ ์์ฒญํด๋ ๊ฐ์ ๊ฐ์ผ๋ก ๊ท๊ฒฐ๋๋ค', () => {
var cuisine = 'ํ์ ์';
spyOn(api, 'getRestaurantsNearConference').and.returnValue(returnedFromService);
var iterations = 5;
for (var i = 0; i < iterations; i++) {
var value = service.getRestaurantsNearConference(cuisine);
expect(value).toBe(returnedFromService);
}
});
});
});
์ด์ด์ ๊ตฌํ๋ถ๋ฅผ ์์ฑํ๋ค.
var Conference = Conference || {};
Conference.memoizedRestaurantApi = function(thirdPartyApi) {
'use strict';
var api = thirdPartyApi;
var cache = {};
return {
getRestaurantsNearConference: function(cuisine) {
// ํค์๋ cuisine์ ํด๋นํ๋ ํค๊ฐ ์บ์์ ์๋์ง ์ฐพ์๋ณด๊ณ , ์์ผ๋ฉด ์บ์๋ ํ๋ผ๋ฏธ์ค๋ฅผ ์ฆ์ ๋ฐํํ๋ค.
if (cache.hasOwnProperty(cuisine)) {
return cache[cuisine];
}
// ์บ์์ ์์ผ๋ฉด ์๋ํํฐ API์ ์์ฒญ ํ ์ ๋ฌ๋ฐ์ ํ๋ผ๋ฏธ์ค๋ฅผ
// cache[cuisine] = returnedPromise;๋ก ์บ์์ ์ถ๊ฐํ ๋ค ํธ์ถ๋ถ์ ํ๋ผ๋ฏธ์ค๋ฅผ ๋๊ฒจ์ค๋ค.
var returnedPromise = api.getRestaurantsNearConference(cuisine);
cache[cuisine] = returnedPromise;
return returnedPromise;
}
}
}
๋ฉ๋ชจ์ด์ ์ด์
๊ธฐ๋ฅ ๋๋ถ์ getRestaurantsNearConference
ํจ์๋ API ํธ์ถ ํ์๋ฅผ ์ค์ผ ์ ์๋ค. ์๋ํํฐ restaurantApi
๋ฅผ ์ด์ฉํ ์ง์ ์ ๋ฌ๋ memoizedRestaurantApi
๋ง ์ถ๊ฐํ๋ฉด ๋๋๊น ์๋ํ๋ ์ฌ๋ฟ์ด ์นํ์๊ฒ ํ ๊ฐ์ง ๋ ์ ์ํ๋ค.
์ฌ๋ฟ: ์นํ, ๋ค๊ฐ
getRestaurantsNearConference
๋ฅผ ๋ฃ์ผ๋ฌ๊ณ ํ์ฅํ๋ฏ์ด ๋ฉ๋ชจ์ด์ ์ด์ ๊ธฐ๋ฅ์ ๊ฐ์ถ ์ ์คํฉํธ๋กrestaurantApi
๋ฅผ ํ์ฅํ ๋ฐฉ๋ฒ์ ์์๊น?
์นํ: ์ด, ๊ฐ๋ฅํด.memoizedRestaurantApi
์์ด ๋ค๋ฅธ ๋ฐ์๋ ํ์ํ๋ฉด ์ผ๋ง๋ ์ง ์ธ ์ ์๋ ๋ฒ์ฉmemoization
์ ์คํฉํธ๋ฅผ ๋ง๋ค ์ ์์ ๊ฑฐ์ผ.
๐ AOP๋ก ๋ฉ๋ชจ์ด์ ์ด์ ์ถ๊ฐํ๊ธฐโ
๐ ๋ฉ๋ชจ์ด์ ์ด์ ์ ์คํฉํธ ์์ฑํ๊ธฐโ
์นํ์ ๋ค์ ๋ชฉํ๋ restaurantApi
์ฒ๋ผ ๋ค๋ฅธ ์ฝ๋์์๋ ๋ฉ๋ชจ์ด์ ์ด์
๋์ ๋ณด๊ฒ๋ ๋ฉ๋ชจ์ด์ ์ด์
์ฝ๋๋ฅผ ์ ์์ memoizedRestaurantApi
์์ ์ถ์ถํ์ฌ ์ ์คํฉํธ๋ก ์ฎ๊ธฐ๋ ๊ฒ์ด๋ค.
๋จ์ ํ
์คํธ๋ฅผ ๋จผ์ ์์ฑํ๋ค. returnValueCache
๋ advice
ํจ์ ํ๋๋ฅผ ์ ์ํ ๋ชจ๋๋ก ๊ตฌํ๋๋ค. beforeEach
๋ธ๋ก์ ๋ค์ ๋ฌธ์ ์คํํ๋ฉด testFunction
์ ์ด๋๋ฐ์ด์ค๋ก ์ฅ์ํ๋ค.
Aop.around('testFunction', Aspects.returnValueCache().advice, testObject);
describe('returnValueCache', () => {
'use strict';
var testObject;
var testValue;
var args;
var spyReference;
var testFunctionExecutionCount;
beforeEach(() => {
// ํ
์คํธํ ๋๋ง๋ค ์ฐ์ ์คํ ํ์๋ฅผ ์ด๊ธฐํํ๋ค.
testFunctionExecutionCount = 0;
testValue = {};
testObject = {
testFunction: function(arg) {
return testValue;
}
};
spyOn(testObject, 'testFunction').and.callThrough();
// ์ ์คํฉํธ๊ฐ ์ ์ฉ๋ ์ดํ์๋
// ์คํ์ด๋ฅผ ์ง์ ์ฐธ์กฐํ ์ ์์ผ๋ฏ๋ก ํ์ฌ ์ฐธ์กฐ๊ฐ์ ๋ณด๊ดํด๋๋ค.
spyReference = testObject.testFunction;
// testObject.testFunction์ returnValueCache ์ ์คํฉํธ๋ก ์ฅ์ํ๋ค.
Aop.around('testFunction', Aspects.returnValueCache().advice, testObject);
args = [{ key: 'value' }, 'someValue'];
});
describe('advice(targetInfo)', () => {
it('์ฒซ ๋ฒ์งธ ์คํ ์ ์ฅ์๋ ํจ์์ ๋ฐํ๊ฐ์ ๋ฐํํ๋ค', () => {
var value = testObject.tetFunction.apply(testObject, args);
expect(value).toBe(testValue);
});
it('์ฌ๋ฌ ๋ฒ ์คํ ์ ์ฅ์๋ ํจ์์ ๋ฐํ๊ฐ์ ๋ฐํํ๋ค', () => {
var iterations = 3;
for (var i = 0; i < iterations; i++) {
var value = testObject.testFunction.apply(testObject, args);
expect(value).toBe(testValue);
}
});
it('๊ฐ์ ํค๊ฐ์ผ๋ก ์ฌ๋ฌ ๋ฒ ์คํํด๋ ์ฅ์๋ ํจ์๋ง ์คํํ๋ค', () => {
var iterations = 3;
for (var i = 0; i < iterations; i++) {
var value = testObject.testFunction.apply(testObject, args);
expect(value).toBe(testValue);
}
expect(spyReference.calls.count()).toBe(1);
});
it('๊ณ ์ ํ ๊ฐ ํค๊ฐ๋ง๋ค ๊ผญ ํ ๋ฒ์ฉ ์ฅ์๋ ํจ์๋ฅผ ์คํํ๋ค', () => {
var keyValues = ['value1', 'value2', 'value3'];
keyValues.forEach(function iterator(arg) {
var value = testObject.testFunction(arg);
});
// ์์ฒญ์ ๊ฐ๊ฐ ๋ค์ ์คํํ๋ค. ๊ฒฐ๊ณผ๋ ์บ์์์ ๊ฐ์ ธ์ค๋ฏ๋ก ์ฅ์๋ ํจ์๋ฅผ ์คํํ์ง ์๋๋ค.
keyValues.forEach(function iterator(arg) {
var value = testObject.testFunction(arg);
});
// ์ฅ์๋ ํจ์๋ ๊ณ ์ณ๊ฐ ํ๋๋น ๊ผญ ํ ๋ฒ์ฉ ์คํ๋์ด์ผ ํ๋ค.
expect(spyReference.calls.count()).toBe(keyValues.length);
});
// ์บ์ ํค๊ฐ ์ ํํ ๊ณ์ฐ๋์๋์ง ํ์ธํ๋ ์ถ๊ฐ ํ
์คํธ ๋ฑ๋ฑ...
});
});
testObject.testFunction
์ ์คํ์ด๋ฅผ ์ฌ๊ณ ์ ์ด ํจ์์ ์ ์คํฉํธ๋ฅผ ์ ์ฉํ๋ค. ์คํ์ด๊ฐ ์คํ๋๋์ง ํ์ธํ๋ ค๊ณ ํ๊ธฐ ์ ๊น์ง ๋ง์ฌ ์์กฐ๋กญ๊ฒ ํ๋ฌ๊ฐ ๊ฒ์ด๋ค.
testObject.testFunction
์ ๋ ์คํ์ด๋ ์ด ํจ์๋ฅผ ์ ์คํฉํธ๋ก ์ฅ์ํ๋ ์๊ฐ ์ข
์ ์ ๊ฐ์ถ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ๋ค์ ๊ธฐ๋์์์ calls
๋ ์ด์ testObject.testFunction
ํ๋กํผํฐ๊ฐ ์๋์ด์ ์คํจํ๋ค.
expect(testObject.testFunction.calls.count()).toBe(1);
ํจ์์ ์ ์คํฉํธ๋ฅผ ์ ์ฉํ๊ธฐ ์ , ๋ณธํจ์์ ์ฐธ์กฐ๊ฐ์ spyReference
์ ๋ณด๊ดํ์ฌ ํด๊ฒฐํ๋ค.
expect(spyReference.calls.count()).toBe(1);
๋ค์์ returnValueCache
๊ตฌํ๋ถ์ด๋ค.
var Aspects = Aspects || {};
Aspects.returnValueCache = function() {
'use strict';
var cache = {};
return {
advice: function(targetInfo) {
// ํจ์์ ๋๊ธด ์ธ์๋ฅผ ์บ์ ํค๋ก ์ด์ฉํ๋ค.
// (๊ฐ์ฒด ์ฐธ์กฐ๊ฐ ๋น๊ต๊ฐ ์๋, ๋ฌธ์์ด ๋น๊ต๋ฅผ ํ๊ธฐ ์ํด ๋ฌธ์์ด๋ก ๋ฐ๊พผ๋ค).
var cacheKey = JSON.stringify(targetInfo.args);
if (cache.hasOwnProperty(cacheKey)) {
return cache[cacheKey];
}
// ์ฅ์๋ ํจ์๋ฅผ ๊ฐ์ ธ์ ์คํํ ๋ค ๊ทธ ๋ฐํ๊ฐ์ ์บ์์ ์ ์ฅํ๋ค.
var returnValue = Aop.next(targetInfo);
cache[cacheKey] = returnValue;
return returnValue;
}
};
};
๐ returnValueCache ์ ์คํฉํธ๋ฅผ restaurantApi์ ์ ์ฉํ๊ธฐโ
๋ง์ง๋ง์ผ๋ก restaurantApi
๋ฉ์๋๋ฅผ restaurantApi
์ ์คํฉํธ๋ก ์ฅ์ํ๋ค. restaurantApi
ํจ์์ returnValueCache
์ ์คํฉํธ๋ฅผ ์ง์ ์ ์ฉํ๋ ๊ฒ ์ผ๋ฐ์ ์ธ ํด๊ฒฐ์ฑ
์ด๋ค. ๊ทธ๋์ผ getRestaurantsNearConference
๋ ๊ธฐ์ตํ๋ฉด์ getRestaurantsWithinRadius
๋ฅผ ์ฐ๋ ๋ค๋ฅธ ํจ์๊น์ง ์๋์ผ๋ก ๋ฉ๋ชจ์ด์ ์ด์
ํํ์ ๋๋ฆด ์ ์๋ค.
๋ค์ ์๋ getRestaurantsWithinRadius
ํจ์๋ฅผ ๊ธฐ์ตํ๊ฒ๋ ThirdPartyRestaurantApiAspects.js
๋ฅผ ์์ ํ ์ฝ๋๋ค.
// getRestaurantsWithinRadius์ ๋ฉ๋ชจ์ด์ ์ด์
ํจํด ์ ์ฉ
Aop.around(
// ๋ฐํ๊ฐ์ ์์ ํด์ผ ํ ํจ์
'restaurantApi',
// ๋ฐํ๊ฐ์ ์์ ํ๋ ํจ์
function addMemoizationToGetRestaurantsWithinRadius(targetInfo) {
// ThirdParty.restaurantApi()๊ฐ ๋ฐํํ ์๋ณธ API
var api = Aop.next.call(this, targetInfo);
// getRestaurantsWithinRadius ํจ์๋ฅผ ์ฅ์ํ์ฌ ๋ฉ๋ชจ์ด์ ์ด์
์ถ๊ฐ
Aop.around('getRestaurantsWithinRadius',
Aspects.returnValueCache().advice, api);
// ๊ณ ์น API๋ฅผ ๋ฐํํ๋ค.
return api;
},
ThirdParty
);
// ThirdParty.restaurantApi()์ getRestaurantsNearConference ๋ฉค๋ฒ ์ถ๊ฐ
Aop.around(
// ๋ฐํ๊ฐ์ ์์ ํด์ผ ํ ํจ์
'restaurantApi',
// ๋ฐํ๊ฐ์ ์์ ํ๋ ํจ์
function addGetRestaurantsNearConference(targetInfo) {
'use strict';
// ThirdParty.restaurantApi()๊ฐ ๋ฐํํ ์๋ณธ API
var api = Aop.next.call(this, targetInfo);
// API์ ์ถ๊ฐํ ํจ์
function getRestaurantsNearConference(cuisine) {
return api.getRestaurantsWithinRadius(
'์ธ์ฐ ๋จ๊ตฌ ์ ์ ๋ก 20๋ฒ๊ธธ 988', 2.0, cuisine
);
}
// ์์ผ๋ฉฐ ์ด ํจ์๋ฅผ ์ถ๊ฐํ๋ค.
api.getRestaurantsNearConference =
api.getRestaurantsNearConference || getRestaurantsNearConference;
// ๊ณ ์น API๋ฅผ ๋ฐํํ๋ค.
return api;
},
// ๋ฐํ๊ฐ์ ์์ ํด์ผ ํ ํจ์์ ์ด๋ฆ๊ณต๊ฐ
ThirdParty
);
๐ ์ ๋ฆฌํ๊ธฐโ
๋ฉ๋ชจ์ด์ ์ด์ ํจํด ๊ตฌํ ์ ๋ค์ ๋ ๊ฐ์ง๋ฅผ ๋จ์ ํ ์คํธ๋ก ๊ผญ ํ์ธํ๋ผ.
- ๋ฐํ๊ฐ์ ๊ธฐ์ตํ ํจ์ ๋๋ ์์์ ์ด๋ค ํค๋ฅผ ๊ธฐ์ค์ผ๋ก ๋งจ ์ฒ์ ํธ์ถํ ๋ ํ ๋ฒ๋ง ์ ๊ทผํ๋ค.
- ๊ทธ ์ดํ๋ก ๊ฐ์ ํค๋ฅผ ์ธ์๋ก ๊ธฐ์ตํ ํจ์๋ฅผ ๋ค์ ํธ์ถํ๋ฉด ๋งจ ์ฒ์ ํธ์ถํ์ ๋์ ๊ฐ์ ๊ฐ์ ๋ฐํํ๋ค.