기본 콘텐츠로 건너뛰기

[javascript] 자스민(Jasmine)을 이용하여 TDD 작성후 웹 페이지에 적용해보자.

 다음 코드는 멍개의 코드 저장소에 있습니다.



시작하기 전에...

책을 시작하기 전에 책에서 사용하는 자스민을 어떤식으로 이용하여 테스트 코드를 작성할 수 있는지 봐보자.

자스민 사용방법

  • 테스트 러너파일 작성
    테스트 러너란 자스민 코드와 소스파일, 스펙을 참조하는 html 파일
app_spec.html

테스트 코드를 작성하기 전에 첫째로 js 파일을 UI에서 완전히 분리하여 작성하는 것이 중요하다.

모듈패턴

테스트 코드가 가능한 코드를 작성하기 위해서는 모듈패턴을 사용하도록 합니다. 모듈패턴이란? 함수로 데이터를 감추고, 모듈 API를 담고있는 객체를 반환하는 형태다. 모듈패턴은 자바스크립트에서 가장 많이 사용하는 패턴입니다. 두 가지 형태로 모듈패턴을 사용할 수 있습니다.
첫 번째는 호출하는 방식으로 사용합니다. 두 번째는 즉시 실행 함수 기반으로 사용할 수 있습니다. 여기서 즉시 실행 함수를 IIFE(Immediately Invoked Function Expression)라고 합니다.
모듈 패턴 코드를 어떤식으로 작성할 수 있는지 간단하게 확인해보겠습니다.
// 공간생성
let App = App || {};

// 생성된 공간에 함수를 추가한다. 인자로 함수를 넘긴다.( == 의존성 있는 함수를 주입)
App.Person = function(God){
    let name = God.makeName();

    // API 노출
    return {
        getName: function(){return name;},
        setName: function(newName){name = newName;}
    }
};
App 변수를 생성합니다.
생선된 변수에 Person() 함수를 만듭니다. person() 함수는 첫 번째 인자로 함수를 전달받습니다.
return을 이용하여 객체를 반환합니다. 반환되는 객체를 API라고 표현할 수 있고, API를 이용하여 해당 기능을 사용합니다.
이렇게 만든 모듈 패턴은 다음과 같이 사용할 수 있습니다.
const person = App.Person(God);
person.getName()
God() 이라는 함수가 있다고 가정을 하고 인자로 넘깁니다. God()에서 makeName()을 호출하여 이름을 만듭니다. 만들어진 이름을 getName()을 호출하여 가져올 수 있습니다. 반대로 setName()을 이용하면 이름을 바꿀수 있습니다.
이 방식은 모듈패턴을 사용하는 첫 번째 방식입니다. App을 호출하여 사용하고 있습니다. 두 번째 방식인 즉시 실행 함수(IFF)를 이용하는 방법은 다음과 같이 코드를 작성할 수 있습니다.
let App = App || {};

App.Person = (function(){
    var name = '';

    return{
        getName: function(God){
            name = name || God.makeName();
            return name
        },
        setName: function(newName){
            name = newName;
        }
    }
})();

App.Person.getName(God)
이렇게 즉시 실행이 가능한 형태로 작성하면, 앞의 방식처럼 호출을 하지 않아도 실행과 동시에 호출이 됩니다. 호출을 하지 않았지만 실행이 됬기 때문에 App은 바로 사용가능 합니다. 이렇게 생성된 App은 싱글톤 인스턴스가 됩니다.
  • 모듈 생성 원칙
  1. 단일 책임 원칙에 따라 모듈은 한 가지 역할만 한다.
  2. 모듈 자신이 사용할 객체가 있다면 의존성 주입 형태로 제공한다.
이 두가지 원칙은 테스트를 좀더 편하게 할 수 있도록 도와줍니다.

자스민을 사용하여 테스트할 시나리오 - 1

웹 페이지에서 가장 많이사용하는 클릭 이벤트를 통해 발생되는 데이터를 다루는 모듈을 테스트 합니다.
counter 변수를 ClickCounter안에서 관리.
  • 첫 번째 스펙
ClickCounter 모듈의 getCounter 함수는 카운터 값을 반환.
Counter_spec.js에 해당 스펙에 대한 내용을 작성한다.
Counter_spec.js
describe('ClickCounter 모듈', () => {
    describe('getCount()', () => {
        it('초기값이 0인 카운터 값을 반환', () => {
            const counter = ClickCounter();
            expect(counter.getCounter()).toBe(0)
        });
    });
});
테스트 러너 파일인 app_spec.html을 실행시키면, 1 spec, 1 failure가 뜰 입니다. app_spec.js에서 getCounter로 가져온 값이 0이 맞는지 검사를 하고 있기 때문에 해당 테스트가 통과할 수 있도록 app.js에 코드를 작성합니다.
Counter.js
var ClickCounter = function() {
    return {
        getCounter(){
            return 0;
        }
    }
};
테스트 러너파일을 실행하면 테스트를 성공적으로 마칩니다. 하지만 ClickCounter는 항상 0을 반환합니다. 코드를 다음과 같이 수정합니다. 다시 테스트 러너 파일을 실행하여 테스트의 결과를 확인합니다. 성공적으로 테스트를 마쳤다고 녹색으로 표시될 것입니다.
ClickCounter의 첫 번째 스펙을 확인하는 코드를 작성해봤습니다. 여기서 중요한점은 Red - Green - Refactor 단계로 테스트 코드를 작성한다는 것입니다.
  1. Red는 어떤 테스트를 할지 스펙을 정하는 단계가 됩니다.
  2. Green는 해당 스펙이 통과 가능할 정도의 코드만 작성합니다.
  3. Refactor은 해당 코드의 보안 개선합니다. Refactor 과정에서는 테스트 러너 파일을 실행하면서 실수없이 진행하고 있는지 확인하면서 작성이 가능합니다.
첫 번째 스펙을 테스트 했으니 두 번째 스펙을 정의한 후 테스트 합니다.
  • 두 번째 스펙
ClickCounter 모듈의 increse()는 counter 값을 1 증가한다.
Counter_spec.js
describe('ClickCounter 모듈', () => {
    describe('getCount()', () => {
        it('초기값이 0인 카운터 값을 반환', () => {
            const counter = ClickCounter();
            expect(counter.getCounter()).toBe(0)
        });
    });

    describe('increse()', () => {
        it('카운터 값을 1만큼 증가', () => {
            const counter = ClickCounter();
            counter.increse();
            expect(counter.getCounter()).toBe(1);
        })
    })
});
describe를 추가하여 다음 스펙을 정의합니다. 테스트 러너 파일을 실행하면, 2 specs, 1 failure와 함께 적색 표시가 됩니다. 구현할 기능에 대해서 정의를 했으니 기능을 구현합니다.
Counter.js
var ClickCounter = function() {
    var counter = 0;
    return {
        getCounter(){
            return counter;
        },
        increse(){
            counter++;
        }
    }
};
다시 테스트 러너 파일을 실행하면 2개의 스펙이 성공했다고 녹색으로 표시를 합니다.
테스트 코드 파일인 Counter_spec.js 코드를 보면 코드에서 '구린내가 난다', '더럽다'라고 표현합니다. 프로그램을 만들때 첫번째 원칙중 하나는 반복되는 코드는 분리입니다. 테스트 코드를 보면 ClickCounter() 모듈을 생성하는 코드가 반복합니다.
우리는 로직을 담고 있는 Counter.js 파일뿐 아니라 테스트 코드인 Counter_spec.js도 리펙토링을 같이 진행을 합니다.
it 함수 호출 전, 후에 실행되는 befoeEach()와 afterEach()를 이용하여 중복되는 코드를 제거할 수 있습니다.
Counter_spec.js
describe('ClickCounter 모듈', () => {
    var counter;

    beforeEach(() => {
        counter = ClickCounter();
    });
    describe('getCount()', () => {
        it('초기값이 0인 카운터 값을 반환', () => {
            expect(counter.getCounter()).toBe(0)
        });
    });

    describe('increse()', () => {
        it('카운터 값을 1만큼 증가', () => {
            counter.increse();
            expect(counter.getCounter()).toBe(1);
        })
    })
});
beforeEach() 함수는 자스민에서 지원하는 함수 중 하나이며, 각각의 스펙을 테스트 하기전에 실행되는 함수입니다.
테스트 코드를 보면 '카운터 값을 1만큼 증가' 하는 부분에서 increse() 함수를 호출하면 반드시 1이 될지 고려해야 합니다. 우선 값의 초기값에 따라 increse()후의 counter 값은 달라지기 때문에 수정이 필요합니다.
Counter_spec.js
describe('increse()', () => {
    it('카운터 값을 1만큼 증가', () => {
        const initalValue = counter.getCounter();
        counter.increse();
        expect(counter.getCounter()).toBe(initalValue + 1);
    })
})
getCounter()로 가져온 카운터 값과 increse() 호출된 값을 비교하여 검사합니다. 테스트 러너 파일을 실행하면 테스트 결과를 녹색으로 띄워줍니다.

자스민을 사용하여 테스트할 시나리오 - 2

지금까지 카운터 모듈을 통해서 카운터 변수를 관리하는 모듈을 테스트했습니다. 카운터 모듈을 통해 관리하는 카운터 값을 돔에 반영하기 위한 테스트 합니다. 돔에 반영하기 위해 ClickCountView 모듈을 만들것이며 이 모듈은 데이터를 출력, 이벤트 핸들러 바인딩하는 일을 담당합니다.
새로운 모듈을 생성하기 위해 필요한 파일 생성과 테스트 러너 파일을 수정합니다.
ClickCountView 모듈을 만들고 테스트 할 2개의 파일을 생성합니다. CounterView.jsCounterView_spen.js을 생성한 후 테스트 러너 파일을 수정합니다.
app_spec.html

모듈 파일을 생성했으니, 테스트 대상 코드와 테스트 코드를 추가합니다.
  • 첫 번째 스펙
ClickCountView 모듈의 updateView()는 카운트 값을 출력한다.
CounterView_spec.js
describe('ClickCountView 모듈', () => {
    describe('updateView()', () => {
        it('ClickCounter의 getCounter() 값을 출력', () =>{

        })
    })
});
이번에도 앞의 첫 번째 시나리오에서 하듯이 테스트 코드에 스펙을 먼저 작성을 합니다. 그런데 여기서 2가지의 문제가 있습니다.
  1. 데이터를 조회할 ClickCounter를 얻는 방법.
  2. 데이터를 출력할 돔 엘리먼트를 테스트하는 방법.
이 두가지를 해결하는 방법이 주입 입니다.
  1. ClickCounter는 객체를 만들어 파라미터로 전달.
  2. 데이터를 출력할 돔 엘리멘트도 만들어 파라미터로 전달.
이렇게 하면 단일 책임 원칙을 지킬 수 있습니다.
describe('ClickCountView 모듈', () => {

    var updateEl;
    var clickCounter;
    var view;

    beforeEach(() => {
        updateEl = $('');
        clickCounter = ClickCounter();
        view = ClickCounterView(clickCounter, updateEl);
    });

    describe('updateView()', () => {
        it('ClickCounter의 getCounter() 값을 출력', () =>{
            const counterValue = clickCounter.getCounter().toString();
            view.updateView();
            expect(updateEl.text()).toBe(counterValue);
        })
    })
});
테스트 러너 파일을 실행하면 3개의 스펙중 하나가 실해했다고 적색표시가 됩니다.
코드가 갑자기 길어졌습니다. 하지만 한 줄씩 해석하면 어렵지 않습니다.
updateEl, ClickCounter, view 3개의 변수가 있습니다. updateEl은 값이 출력될 돔이 저장되는 변수이고, ClickCounter 변수는 앞에서 만든 카운터 모듈을 저장하는 변수입니다. view는 이번에 만들어질 카운터 모듈의 데이터를 돔에 출력하는 모듈입니다.
CounterView 모듈은 2개의 인자를 받습니다. 카운터와 돔을 받습니다.
it 함수를 살펴보면 카운터 모듈에서 카운터 값을 가져온 후 counterView 모듈의 updateView() 함수를 호출합니다. 다음으로 updateEl의 값과 카운터 값을 비교합니다. 여기서 $는 제이쿼리를 의미함며 .text()는 태그의 값을 가져오는 jquery의 함수입니다.
스펙을 정의 했으니 해당 스펙의 테스트 결과가 성공할 수 있도록 카운터뷰 모듈 기능을 추가합니다.
CounterView.js
var ClickCounterView = function(clickCounter, updateEl){
    return {
        updateView(){
            updateEl.text(clickCounter.getCounter());
        }
    }
}
카운터뷰 모듈의 구현을 마쳤습니다. 테스트 러너 파일을 실행하면 테스트 결과가 녹색으로 뜹니다.
첫 번째 스펙을 마치기 전에 추가적으로 테스트를 저 해줍니다.
이렇게 작성된 코드는 그냥 기능만 구현한 겁니다. 하지만 우리는 수 많은 예외를 생각하고 처리를 해야합니다. 수 많은 예외중 간단하게 생각할 수 있는것이 인자를 잘 전달 받았는지 테스트 하는겁니다. 이것을 의존성 주입 테스트라고 합니다. 쉽게 말하면 필요한 모듈을 넘겨 주지 않았을때 처리를 하는 코드를 작성하면 됩니다.
코드를 작성하기 먼저 CounterView_spec에 어떤 기능을 테스트할지 작성합니다.
CounterView_spec.js
describe('ClickCountView 모듈', () => {

    var updateEl;
    var clickCounter;
    var view;

    beforeEach(() => {
        updateEl = $('');
        clickCounter = ClickCounter();
        view = ClickCounterView(clickCounter, updateEl);
    });

    describe('updateView()', () => {
        it('ClickCounter의 getCounter() 값을 출력', () =>{
            const counterValue = clickCounter.getCounter().toString();
            view.updateView();
            expect(updateEl.text()).toBe(counterValue);
        });

        it('ClickCounter를 주입하지 않으면 에러를 던짐', () => {

        });
        it('updateEl를 주입하지 않으면 에러를 던짐', () => {

        });
    });
});
스펙을 정의하다보니 의문이 생깁니다. 에러를 어떻게 확인할 수 있을까?
expect(function() { throw new Error()}).toThrowError()
toThrowError() 함수를 이용하여 에러를 확인할 수 있습니다.
it('ClickCounter를 주입하지 않으면 에러를 던짐', () => {
    const updateEl = $('');
    const actual = () => ClickCounterView(null, updateEl);

    expect(actual).toThrowError();
});
첫 번째 인자인 카운터에 null값을 넣습니다. 그리고 에러가 발생하는지 확인하는 코드를 작성합니다. 테스트 러너 파일을 실행하면 테스트 결과는 실패가 되고 적색표시가 됩니다.
CounterView.js
var ClickCounterView = function(clickCounter, updateEl){
    if(!clickCounter) throw new Error();
    return {
        updateView(){
            updateEl.text(clickCounter.getCounter());
        }
    }
};
카운터뷰 모듈은 첫 번째 인자를 검사한 후 에러를 발생시킵니다.
테스트 러너 파일을 실행하면 테스트 결과가 성공적으로 마치고 녹색표시를 띄워줍니다.
똑같은 방법으로 두번째 인자의 주입을 확인할 수 있습니다.
CounterView_spec.js
javascript it('updateEl를 주입하지 않으면 에러를 던짐', () => { const clickCounter = ClickCounter(); const actual = () => ClickCounterView(clickCounter, null);
expect(actual).toThrowError();
}); ```
CounterView.js
var ClickCounterView = function(clickCounter, updateEl){
    if(!clickCounter) throw new Error();
    if(!updateEl) throw new Error();
    
    return {
        updateView(){
            updateEl.text(clickCounter.getCounter());
        }
    }
};
첫 번째 스펙 테스트를 마쳤습니다.
  • 두 번째 스펙
카운트뷰 모듈의 increseAndUpdateView()는 카운트 값을 증가하고 그 값을 출력한다.
카운터의 increse() 함수 실행 -> updateView 함수 실행
CounterView_spec.js
describe('increaseAndUpdateView()', () => {
    it('ClickCounter의 increse를 실행', () => {
            view.increseAndUpdateView();
    });

    it('updateView를 실행', () => {

    });
})
스펙과 해당 스펙의 테스트 케이스 정의를 했습니다. 카운트뷰 모듈인 view에서 increseAndUpdateView() 함수를 호출합니다. 해당 함수는 카운터 모듈의 카운터를 증가하는 함수를 호출합니다. 하지만 해당 테스트 코드에서 카운터를 증가하는 함수인 clickCounter.increase()함수가 호출된 것을 어떻게 검증할 수 있을까요?
이럴때는 테스트 더블을 사용합니다. 자스민에서 테스트 더블을 스파이스(spies)이라고 부릅니다. 테스트 더블이란 단위 테스트 패턴으로, 테스트하기 곤란한 컴포넌트를 대체하여 테스트하는 것을 의미합니다. 특정한 동작을 흉내만 낼뿐이지만 테스트 하기에는 적합합니다. 다음 5가지를 통칭하여 테스트 더블이라고 합니다.
  1. 더미 : 인자를 채우기 위해 사용
  2. 스텁 : 더비를 개선하여 실제 동작하게끔 만든 것. 리턴값을 하드 코딩한다
  3. 스파이 : 스텁과 유가. 내부적으로 기록을 남기는 추가기능
  4. 페이크 : 스텁에서 발전한 실제 코드. 운영에서는 사용할수 없음
  5. 목 : 더미, 스텁, 스파이를 혼합한 형태
CounterView_spec.js
describe('increaseAndUpdateView()', () => {
    it('ClickCounter의 increse를 실행', () => {
        spyOn(clickCounter, 'increse');
        view.increseAndUpdateView();
        expect(clickCounter.increse).toHaveBeenCalled();
    });

    it('updateView를 실행', () => {

    });
})
spyOn으로 모듈과 감시할 함수를 등록합니다. 테스트 러너 파일을 실행하면 실패하여 적색표시가 됩니다.
CounterView.js
var ClickCounterView = function(clickCounter, updateEl){
    if(!clickCounter) throw new Error();
    if(!updateEl) throw new Error();

    return {
        updateView(){
            updateEl.text(clickCounter.getCounter());
        },
        increseAndUpdateView(){
            clickCounter.increse();
        }
    }
};
increseAndUpdateView() 함수를 만들어 준 후 increse() 호출을 합니다. 다시 테스트 러너 파일을 실행하면 성공적으로 테스트를 마칩니다.
다음 테스트 케이스를 테스트 합니다.
CounterView_spec.js
describe('increaseAndUpdateView()', () => {
    it('ClickCounter의 increse를 실행', () => {
        spyOn(clickCounter, 'increse');
        view.increseAndUpdateView();
        expect(clickCounter.increse).toHaveBeenCalled();
    });

    it('updateView를 실행', () => {
         spyOn(view, 'updateView');
        view.increseAndUpdateView();
        expect(view.updateView).toHaveBeenCalled();
    });
})
이번에는 카운터뷰 모듈에서 updateView가 호출됬는지 확인합니다. increseAndUpdateView()는 clickCounter.increse()만 호출하고 있기 때문에 해당 테스트 케이스는 실패했다고 적색표시를 합니다.
CounterView.js
var ClickCounterView = function(clickCounter, updateEl){
    if(!clickCounter) throw new Error();
    if(!updateEl) throw new Error();

    return {
        updateView(){
            updateEl.text(clickCounter.getCounter());
        },
        increseAndUpdateView(){
            clickCounter.increse();
            this.updateView();
        }
    }
};
increseAndUpdateView()에서 increse()와 updateView()를 호출하여 성공적으로 테스트가 될 수 있도록 수정했습니다. 테스트 러너 파일을 실행하면 테스트가 성공적으로 마치고 녹색표시가 됩니다.
  • 세 번째 스펙
세 번째 스펙은 클릭 이벤트가 발생하면 increseAndUpdateview를 실행한다.
CounterView_spec.js
describe('ClickCountView 모듈', () => {
    . . . 중 략 . . .
    describe('ClickCountView 모듈', ()=>{
            it('클릭 이벤트가 발생하면 increseAndUpdateView를 실행한다', () => {
    
            });
        });
    });
});
클릭 이벤트가 발생되면 increseAndUpdateView()가 실행되는지에 대한 테스트 케이스 입니다.
spyOn(view, 'increseAndUpdateView');

// 이벤트 발생
// 핸들러 바인딩

expect(view.increseAndUpdateView).toHaveBeenCalled();
spyOn을 이용하여 increseAndUpdateView가 호출되는지 확인할 수 있습니다. 그러나 클릭 이벤트의 발생과 어디에 핸들러를 바인징 해야하는지에 대한 의문이 있습니다.
카운터 모듈에 값을 출력할 돔 엘리먼트 인 updateEl 변수를 주입한 것처럼, 클릭 이벤트 핸들러(increseAndUpdateView)를 바인딩할 돔 엘리먼트를 주입을 받으면 됩니다.
CounterView_spec.js
describe('ClickCountView 모듈', () => {
     var updateEl;
    var triggerEl;
    var clickCounter;
    var view;

    beforeEach(() => {
        updateEl = $('');
        triggerEl = $('');
        clickCounter = ClickCounter();
        view = ClickCounterView(clickCounter, {updateEl, triggerEl});
    });
    
    . . .  중 략 . . .
    
    describe('ClickCountView 모듈', ()=>{
        it('클릭 이벤트가 발생하면 increseAndUpdateView를 실행한다', () => {
            spyOn(view, 'increseAndUpdateView');
            triggerEl.trigger('click');
            expect(view.increseAndUpdateView).toHaveBeenCalled();
        });
    });
});
카운터 뷰에 인자를 전달하는 방법이 바뀌었습니다. 테스트 러너를 실행하니 많은 에러가 발생합니다. 차근차근 고치면 됩니다.
CounterView.js
var ClickCounterView = function(clickCounter, options){
    if(!clickCounter) throw new Error();
    if(!options.updateEl) throw new Error();

    const view =  {
        updateView(){
            options.updateEl.text(clickCounter.getCounter());
        },
        increseAndUpdateView(){
            clickCounter.increse();
            this.updateView();
        }
    };

    options.triggerEl.on('click', function(){
       view.increseAndUpdateView();
    });

    return view;
};
options은 updateEl과 triggerEl을 객체로 받는 인자입니다. updateEl과 triggerEl을 사용하기 위해서는 options.updateEl, options.triggerEl의 형태로 사용할 수 있습니다.
테스트 러너 파일을 실행하면 모든 테스트가 성공적으로 끝났기 때문에 녹색이 표시됩니다.

화면에 붙여보자.

지금까지 작성한 코드를 실제 웹 페이지에 넣어서 확인을 합니다.
index.html



    
    Title

    



    0
    

    
    
    

이제 해당 파일을 실행한 후 버튼을 클릭하면 수치가 1씩 증가 됩니다.

정리

보다 효율적인 테스트 코드를 작성하려면...
  1. 단일 책임 원칙을 지킨다(역할에 따라 데이터와 뷰를 분리하여 ClickCounter, ClickCountView 구현)
  2. 모듈 단위로 개발한다
  3. 뷰 모듈은 돔을 캡슐화 해야한다.
여기서 사용한 자스민 API
  1. describe / it/ beforeEach
  2. expect / toBe / toThrow
  3. spyOn / toHaveBeenCalled
추가적인 자스민 API
  1. toEqual / toContain
  2. TobeLessThan / toBeGreaterThen
  3. jasmine.createSpy / toHaveBeenCalledWith / toHaveBeenCalledTimes

댓글

  1. 안녕하세요. 좋은 글 감사합니다. 현재 포스팅을 차례대로 따라해보고 있는데요.

    describe('ClickCountView 모듈', () => {

    var updateEl;
    var clickCounter;
    var view;

    beforeEach(() => {
    updateEl = $('');
    clickCounter = ClickCounter();
    view = ClickCounterView(clickCounter, updateEl);
    });

    describe('updateView()', () => {
    it('ClickCounter의 getCounter() 값을 출력', () =>{
    const counterValue = clickCounter.getCounter().toString();
    view.updateView();
    expect(updateEl.text()).toBe(counterValue);
    })
    })
    });

    expect(updateEl.text()).toBe(counterValue); 에서 updateEl.text()값이 비어있는 값이 나옵니다. 본문에는 0이 리턴되서 테스트가 통과한다고 하는데 안되는거 같아요!

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

[git] git log 확인하기

git log를 통해서 커밋 이력과 해당 커밋에서 어떤 작업이 있었는지에 대해 조회를 할 수 있다. 우선 git에서의 주요 명령어부터 알아보겠다. $ git push [branch name] $ git pull [branch name] 여기서 branch name은 로컬일 경우 해당 브런치 이름만 적으면 되지만 깃허브 원격 저장소로 연결을 원할 경우는 해당 브런치 이름 앞에 꼭 origin을 붙이도록 한다. $ git brnch [branch name] $ git checkout [branch name] branch일경우 해당 브런치를 생성을 한다. 여기서 현재의 브런치를 기준으로 브런치를 따는것이다. checkout은 브런치를 바꾸는 것이다.(HEAD~[숫자]를 이용하면 해당 커밋으로 움직일수 있다.. 아니면 해당 커밋 번호를 통해 직접 옮기는것도 가능하다.) -> 해당 커밋으로 옮기는 것일뿐 실질적으로 바뀌는 것은 없다. 해당 커밋으로 완전히 되돌리려면 reset이라는 명령어를 써야한다. 처음 checkout을 쓰면 매우 신기하게 느껴진다. 막 폴더가 생겼다가 지워졌다가 ㅋㅋㅋㅋㅋ  master 브런치에서는 ht.html파일이 존재하지만 a브런치에서는 존재하지않는다. checkout 으로 변경을 하면 D 로 명시를 해준다.  $ git log 해당 브런치의 커밋 내역을 보여준다. a 브런치의 커밋 내역들이다. (머지 테스트를 하느라 커밋 내용이 거의 비슷하다 ㅋㅋ) master 브런치의 커밋 내역들이다. 커밋 번호, 사용자, 날짜, 내용순으로 등장을 한다. 이건 단순히 지금까지의 내역을 훑어보기 좋다. 좀더 세밀한 내용을 봐보자. $ git log --stat --stat을 붙이면 기존의 로그에서 간략하게 어떤 파일에서

[git] pull을 하여 최신코드를 내려받자

보면 먼가 로고가 다르게 뜨는것을 확인을 할 수가있다. C:\Users\mung\Desktop\etc\study\python-gene>git checkout remotes/origin/master Note: checking out 'remotes/origin/master'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example:   git checkout -b HEAD is now at 29e282a... fetch test C:\Users\mung\Desktop\etc\study\python-gene>git branch * (HEAD detached at origin/master)   master   test1   test2 깃이 잘 쓰면 참 좋은놈인데 어지간히 쓰기가 까다롭다. 처음에 깃을 푸시 성공하는데만 한달정도 걸렸던걸로 기억이 난다.. ㅋㅋㅋ 여담으로  깃 프로필을 가면 아래사진 처럼 보인다. 기여도에 따라서 초록색으로 작은 박스가 채워지는데 저걸 잔디라고 표현을 한다고 합니다 ㅎ 저 사진은 제 깃 기여도 사진입니당 ㅋㅋㅋㅋ 다시 본론으로 돌아와서 ㅋㅋ pull을 하면 깃에 최신 소

[kali linux] sqlmap - post요청 injection 시도

아래 내용은 직접 테스트 서버를 구축하여 테스트 함을 알립니다.  실 서버에 사용하여 얻는 불이익에는 책임을 지지 않음을 알립니다. sqlmap을 이용하여 get요청이 아닌 post요청에 대해서 injection공격을 시도하자. 뚀한 다양한 플래그를 이용하여 DB 취약점 테스트를 진행을 해보려고 한다. 서버  OS : windows 7 64bit Web server : X Server engine : node.js Framework : expresss Use modules : mysql Address : 172.30.1.30 Open port : 6000번 공격자 OS : kali linux 64bit use tools : sqlmap Address : 172.30.1.57 우선 서버측 부터  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 var  express  =  require( 'express' ); var  app  =  express(); var  mysql  =  require( 'mysql' ); var  ccc  =  mysql.createConnection({     host: '127.0.0.1' ,     user: 'root' ,     post: '3306' ,     password: '*********' ,     database: 'test' }) app.post(