[Front-end๐Ÿฆ] #32 TDD + JavaScript ๋ชจ๋“ˆํ™”

๋˜์ƒยท2021๋…„ 12์›” 13์ผ
0

front-end

๋ชฉ๋ก ๋ณด๊ธฐ
46/58
post-thumbnail

1. TDD

1. ๊ฐ์ฒด ์ƒ์„ฑ ํŒจํ„ด

  1. ๋ชจ๋“ˆ ํŒจํ„ด : ํด๋กœ์ €๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ’์„ ์€๋‹‰.
  • ์บก์Šํ™”๋ฅผ ์™œํ•˜๋Š”๋ฐ?????? ์— ๋Œ€ํ•œ ์˜๋ฌธ์ด ๊ณ„์† ์žˆ์—ˆ๋Š”๋ฐ, ์˜ˆ์‹œ๋ฅผ ๋“œ๋Š๋ผ๊ณ  ๋ณ€์ˆ˜๋ฅผ ํ•˜๋‚˜๋งŒ ๋„ฃ๊ณ  get set ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋‹ค ๊ตฌํ˜„ํ•ด์„œ ๊ทธ๋ƒฅ ๊ฐ์ฒด๋ž‘ ์ฐจ์ด๊ฐ€ ์—†์–ด๋ณด์˜€๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ํ•ด๋‹น ๊ฐ์ฒด์— ํ•„์š”ํ•œ ๋ณ€์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ด๊ณ , ์–ด๋–ค ๊ฒƒ์€ ์€๋‹‰ํ•˜๊ณ  ์–ด๋–ค ๊ฒƒ์€ ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅํ•˜๊ณ  ์–ด๋–ค ๋ณ€์ˆ˜๋Š” ์ฝ๊ธฐ ์“ฐ๊ธฐ๊ฐ€ ๋‹ค ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค๋ฉด..? ์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ์ˆ˜ ๋ฐ–์—!
// 1. ๋ชจ๋“ˆ ํŒจํ„ด
// age๋Š” ์ง์ ‘ ์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅ. ์ˆจ๊ฒจ์ง„ ๊ฐ’์ž„.
// ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๋„ ์•„๋‹˜.
function person() {
    let age = 123;
    return {
        getAge: function () { return age },
        setAge: function (data) { age = data }
    }
}
  1. ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž… ํŒจํ„ด : ์ƒ์„ฑ์ž ํ•จ์ˆ˜ ์‚ฌ์šฉ. ๊ฐ’์ด ์€๋‹‰๋˜์ง„ ์•Š์Œ.
// ์ธ์Šคํ„ด์Šค์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— age๋Š” ์ˆจ๊ฒจ์ง„ ๊ฐ’์ด ์•„๋‹˜.
function PersonType() {
    this.age = 135;
}
PersonType.prototype.getage = function () {
    return this.age
}
const instancePerson = new PersonType();
  1. ๊ทธ๋ƒฅ ๊ฐ์ฒด ์ƒ์„ฑ
// 3. ๊ทธ๋ƒฅ Object
// ๊ฐ์ฒด๋ฅผ ๋”ฑ ํ•˜๋‚˜๋งŒ ๋งŒ๋“ค์–ด์„œ ์“ฐ๋Š” ๊ฒƒ์„ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์ด๋ผ๊ณ  ํ•œ๋‹ค.
let person2 = { age: 35 };
  1. ๋ชจ๋“ˆ + ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž… ํŒจํ„ด
// ์ธ์Šคํ„ด์Šค๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ƒ์„ฑํ•˜๋ฉด์„œ ๊ฐ’์„ ์€๋‹‰.
// IIFE ํŒจํ„ด : ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ด์•ผ ์ƒ์„ฑ์ž ํ•จ์ˆ˜๊ฐ€ return ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ฆ‰์‹œ ์‹คํ–‰ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ๋‹ค!!!!
const Person3 = (function PersonType2() {
  // closure ๊ณต๊ฐ„.
  // ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ age ๊ฐ’์„ ๊ณต์œ ํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด ์›์‹œ๊ฐ’์ด ์•„๋‹ˆ๋ผ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.
    let age = { data: 142 }; 
  // ํ•จ์ˆ˜ ๋‚ด๋ถ€ ๊ณต๊ฐ„์—์„œ ์ƒ์„ฑ์ž ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ return
    function innerPersonType() { }
    innerPersonType.prototype.getAge = function() {
      // age์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” closure ํ•จ์ˆ˜.
        return age;
    }
    return innerPersonType;
})();
// const Person3 = PersonType2();
const person3 = new Person3();
person3.getAge()

2. ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ๊ฐ์ฒด, ๋ทฐ์–ด ๊ฐ์ฒด

  • ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ๊ฐ์ฒด : ๋ฐ์ดํ„ฐ๋ฅผ ์€๋‹‰ํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€ ํ•„์š”ํ• ๋•Œ ๋‹ค๋ฅธ ๊ฐ์ฒด์— ์ „๋‹ฌํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๋Š” ๊ฐ์ฒด
  • ๋ทฐ์–ด ๊ฐ์ฒด : ๋ฐ์ดํ„ฐ, ์ธํ’‹, ๋ฒ„ํŠผ, ๋ทฐ์–ด๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฐ์ฒด

๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ๊ฐ์ฒด
textManager.js

// ๋ชจ๋“ˆ + ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž… ํŒจํ„ด์„ ์ด์šฉ!
const TextManager = (function () {
    let value = { data: "Hello Lions!"};
    function innerTextManager() { }
    innerTextManager.prototype.getValue = function () {
        return value.data;
    }
    innerTextManager.prototype.setValue = function(newValue) {
        value = newValue;
    }
    return innerTextManager;
})();

textManager.spec.js

describe('ํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž์ž…๋‹ˆ๋‹ค.', () => {
  // beforeEach : it ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ง์ „์— ์‹คํ–‰.
  // ๊ฐ it์—์„œ ๊ฒน์น˜๋Š” ๋ถ€๋ถ„์€ ์ด๋ ‡๊ฒŒ  ๋กœ ๋นผ์„œ ์‚ฌ์šฉํ•œ๋‹ค.
    let textManger;
    beforeEach(() => {
        textManager = new TextManager();
    })
    it('ํ…์ŠคํŠธ ๊ฐ’์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค', () => {
        const initValue = textManager.getValue();
        expect(textManager.getValue()).toBe(initValue);
    })
    it('ํ…์ŠคํŠธ ๊ฐ’์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.', () => {
        const newText = { data: 'Hello Zebras' };
        textManager.setValue(newText);
        expect(textManager.getValue()).toBe(newText.data);
    })
})

๋ทฐ์–ด ๊ฐ์ฒด
viewManager.js

function ViewManager(textManager, options) {
    if(!textManager || !options.btnEl || !options.viewerEl || !options.inpTxt) {
        // null์ด ํ•˜๋‚˜๋ผ๋„ ์žˆ์„ ๋•Œ
        // ์‚ฌ์šฉ์ž ์ •์˜ ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋“ค๊ณ  ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ.
        throw Error('์ „๋‹ฌ ์ธ์ž ์ค‘์— ๋นˆ ๊ฐ’์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.');
    }
    this.textManager = textManager;
    this.viewerEl = options.viewerEl;
    this.inpTxt = options.inpTxt;
    options.btnEl.addEventListener('click', () => {
        this.changeValue();
    })
}
// ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๊ฐ’์„ ์„ธํŒ….
ViewManager.prototype.changeValue = function() {
    this.textManager.setValue({ data: this.inpTxt.value });
    this.updateView();
}
ViewManager.prototype.updateView = function () {
    this.viewerEl.textContent = this.textManager.getValue();
}

viewManager.spec.js

describe('click event handling๊ณผ view๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.', () => {
    // dependency injection (์˜์กด ์ฃผ์ž…) : ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ ์ž์‹ ์—๊ฒŒ ์ฃผ์ž…์‹œ์ผœ์•ผ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.
    // view๋Š” textManager๋ฅผ ์ „๋‹ฌ๋ฐ›์•„์•ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    let textManager, viewerEl, btnEl, inpTxt, viewManager;
    beforeEach(() => {
        textManager = new TextManager();
        viewerEl = document.createElement('strong');
        btnEl = document.createElement('button');
        inpTxt = document.createElement('input');
        viewManager = new ViewManager(textManager, {viewerEl, btnEl, inpTxt});
    });
    it('viewManager์— ์ธ์ž๊ฐ€ ์ž˜ ์ „๋‹ฌ๋๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.', () => {
        const textManager = null;
        const btnEl = null;
        const viewerEl = null;
        const inpTxt = null;
      //
        // ์ธ์ž๊ฐ€ ์ „๋‹ฌ๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜.
        const actual = () => new ViewManager(textManager, { btnEl, viewerEl, inpTxt });
        // ๊ฐ’์„ ์ „๋‹ฌํ•ด์ฃผ๋Š” ํ•จ์ˆ˜ actual์ด๋‹ˆ๊นŒ, ๊ฐ’์ด ์ œ๋Œ€๋กœ ์ „๋‹ฌ๋๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋ฉด ๋จ.
        // expect(ํ•จ์ˆ˜) ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.
        expect(actual).toThrowError();
    })
    it('click event๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒฝ์šฐ changeValue ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.', () => {
        // spyOn : ํŠน์ • ๋ชจ๋“ˆ์˜ ํ•จ์ˆ˜ ๊ฐ์‹œ
        spyOn(viewManager, 'changeValue');
        btnEl.click();
        // toHaveBeenCalled : ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ์ด ๋œ ์ ์ด ์žˆ๋Š”์ง€ ํŒ๋ณ„ - spyOn์ด ์žˆ์–ด์•ผ ํ•จ.
        expect(viewManager.changeValue).toHaveBeenCalled();
    })
    it('updateView ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.', () => {
        // updateView๋Š” changeValue๊ฐ€ ์ผ์–ด๋‚˜๋ฉด ๋ฐ”๋กœ ์‹คํ–‰.
        spyOn(viewManager, 'updateView');
        viewManager.changeValue();
        expect(viewManager.updateView).toHaveBeenCalled();
    })
})

index.html

// ๋ถˆ๋Ÿฌ์™€์„œ ์—ฐ๊ฒฐ๋งŒ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
const viewerEl = document.querySelector('.viewer');
const inpTxt = document.querySelector('.inp-txt');
const btnEl = document.querySelector('.btn-push');
// ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ / ๋ทฐ ๊ฐ์ฒด
const textManager = new TextManager();
const viewManager = new ViewManager(textManager, { viewerEl, inpTxt, btnEl });
// ์ดˆ๊ธฐ๊ฐ’์„ ํ™”๋ฉด์— ๋ณด์—ฌ์คŒ.
viewManager.updateView();
  • ์•ฝ๊ฐ„.. ์ง€๊ธˆ์€ ๋‹น์—ฐํžˆ ๊ฐ™์•„์•ผ ๋˜๋Š”๊ฑฐ ์•„๋‹Œ๊ฐ€..? ํ…Œ์ŠคํŠธ๊นŒ์ง€ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‚˜? ์‹ถ์€๋ฐ ๊ตฌ์กฐ๊ฐ€ ๋” ๋ณต์žกํ•ด์ง€๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ง„๊ฐ€๋ฅผ ๋ฐœํœ˜ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

  • ๋ชจ๋“  ์ฝ”๋“œ์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ๋ฐ›๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, ViewManager ์ฒ˜๋Ÿผ ๋ญ”๊ฐ€๋ฅผ ๋ฐ›์•„์™€์„œ ์‹คํ–‰ํ•˜๋Š” utility ์ฝ”๋“œ์—๋งŒ ์ž‘์„ฑํ•œ๋‹ค.

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ์‚ฌ๋žŒ์ด ์ž‘์„ฑํ•˜๋Š”๊ฑฐ๋‹ˆ๊นŒ ๋ฐฉ์‹ฌํ•˜์ง€ ๋ง์ž!




2. ์‹œํ—˜

ํ”„๋กœ์ ํŠธ ํŒ€์„ ์œ„ํ•œ HTML/CSS ์‹œํ—˜์„ ๋ดค๋‹ค.

ํ‹€๋ฆฐ ๋ฌธ์ œ ์ค‘ ๋Œ€์ถฉ ์ฝ์—ˆ๊ฑฐ๋‚˜ ์•„๋Š” ๋‚ด์šฉ์ธ๋ฐ ๋นจ๋ฆฌ ํ’€๊ณ  ์‹ถ์–ด์„œ ํ˜ธ๋กœ๋ก ํ’€๋‹ค๊ฐ€ ํ‹€๋ฆฐ ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ 

์ง„์งœ๋กœ ๋ชฐ๋ผ์„œ ํ‹€๋ฆฐ ๊ฒƒ

  • js class : ์ˆ˜์—…์„ ๊ทธ ๋‚  ๋ชป ๋“ค์–ด์„œ, ๋”ฅ๋‹ค์ด๋ธŒ ์ˆœ์„œ๋Œ€๋กœ ์ฝ์œผ๋ฉด์„œ ๋ณด์ถฉํ•  ์˜ˆ์ •.

๊ธฐ์–ต ํ•˜๋ ค๊ณ  ํ•˜๋‹ˆ ์•ˆ๋‚˜์„œ ํ‹€๋ฆฐ ๊ฒƒ

  • CSS sprite : #16 ๋‹ค์‹œ๋ณด๊ธฐ
  • SASS extends : #19 ๋‹ค์‹œ๋ณด๊ธฐ
  • jQuery : #30 ๋‹ค์‹œ๋ณด๊ธฐ




3. codelion ๊ฐ•์˜ ์ˆ˜๊ฐ•

js ๊ฐ•์˜๋ฅผ ์ˆ˜๊ฐ•ํ–ˆ๋‹ค. ๋กœ๋˜ ๋ฒˆํ˜ธ ์ถ”์ฒจ๊ธฐ, textarea ๊ธ€์ž ์ˆ˜ ์„ธ๋Š” ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค. ์˜›๋‚ ์— ๋“ค์—ˆ์œผ๋ฉด ๋งˆ๋ƒฅ ๊ฐ„๋‹จํ•˜๋‹ค๊ณ  ์ƒ๊ฐ ์•ˆํ–ˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ, ๊ฐ„๋‹จํ•˜๊ฒŒ ๋Š๊ปด์กŒ๋‹ค.

์†Œ์Šค์ฝ”๋“œ - ๋กœ๋˜ ๋ฒˆํ˜ธ ์ถ”์ฒจ๊ธฐ, textarea ๊ธ€์ž ์ˆ˜ ์„ธ๊ธฐ




profile
0๋…„์ฐจ iOS ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€