[TIL] TDD

ㅜㅜ·2022년 12월 4일
1

Today I learn

목록 보기
65/77
post-thumbnail

TDD (Test-Driven-Developmemt)

TDD는 '테스트 주도 개발'로 코드를 작성하기 전에 테스트를 쓰는 소프트웨어 개발 방법론이다.

TDD를 통해 소프트웨어를 개발한다는 것은 작은 단위의 테스트 케이스를 작성하고, 이를 통과하는 코드를 작성하는 과정을 반복하는 것을 의미한다.

테스트를 먼저 작성하는 것은 필연적으로 코드를 어떻게 구성할지 고민하게 된다는 것을 의미하고, 결과적으로 버그가 더 적은 코드를 짤 수 있게 되며, 불필요한 설계를 피할 수 있고, 테스트 코드의 요구 사항에 집중할 수 있게 된다고 한다.

일반적으로 TDD를 따라 소프트웨어를 개발할 경우 그렇지 않은 경우보다 결함을 50 ~ 90% 까지 감소시킬 수 있으나 모든 회사에서 TDD를 채택하는 건 아니다. TDD를 사용하면 코드 작성 전 테스트 코드를 먼저 작성해야 하므로 그게 좀 더 느리다고 생각할 수도 있음. (TDD는 처음에 시간을 좀 더 투자해 뒤에 있을 예상치 못한 버그 요소를 미리 줄이는 것)

TDD 개발 순서

1. Write Falling Test : 실패하는 테스트를 먼저 작성
2. Make Test Pass : 테스트 코드를 성공시키지 위한 실제 코드 작성
3. Refactor : 중복 코드 제거, 일반화 등의 리팩토링을 수행

🚫 1과정을 마치기 전에 2작업을 시작하지 않도록 주의해야 하며, 2를 진행할 때 1의 테스트를 통과할 정도의 최소 코드만 작성해야 함.

방법

  • console.log를 통해 확인하는 것도 일종의 테스트라고 볼 수 있고,

  • 보통은 테스트 프레임워크에서 제공하는 도구들을 사용하기도 한다.
    테스트 오픈소스 프레임워크 : mocha(프레임워크), chai(라이브러리)

  • 리액트 테스트 라이브러리
    : Testing Library, Jest(Javascript Testing Framework)
    => CRA를 사용하면 자동으로 Testing Library를 이용할 수 있음.
    => 둘은 역할이 각각 다르기 때문에 React에 대한 테스트를 진행할 땐 어느 한쪽 라이브러리를 이용하는 것만으로는 테스크를 진행할 수 없다.





🍭 과제 : Test Builder

목표

  • 신용카드 번호를 바탕으로 해당 신용카드가 어떤 발급기관(Network)에 속해있는지 알아낼 수 있는 함수를 작성
  • Mocha, Chai가 제공하는 자동화 테스트를 이용해 위 함수 테스트하기

작성한 코드 : detectNetwork.js

  • 'Diner's Club' 카드 번호는 항상 38이나 39로 시작을하고, 14 자리 숫자입니다.
  • 'American Express' 카드 번호는 항상 34 나 37로 시작하고, 15 자리 숫자입니다.
  • Visa 카드번호는 항상 4로 시작하고 13, 16, 혹은 19자리의 숫자입니다.
  • MasterCard 카드번호는 항상 51, 52, 53, 54, 혹은 55로 시작하고 16자리의 숫자입니다.
//detectNetwork.js 파일 : 카드 번호를 받아서 어떤 발급 기관에 속해있는지 알아내는 함수를 작성하는 곳 
function detectNetwork(cardNumber) {
  let prefix = cardNumber.slice(0, 2);
  let prefixOne = cardNumber.slice(0, 1);

  if ((prefix === "38" || prefix === "39") && cardNumber.length === 14) {
    return `Diner's Club`;
  } else if ((prefix === "34" || prefix === "37") && cardNumber.length === 15) {
    return `American Express`;
  } else if (
    prefixOne === "4" &&
    (cardNumber.length === 13 ||
      cardNumber.length === 16 ||
      cardNumber.length === 19)
  ) {
    return "Visa";
  } else if (
    (prefix === "51" ||
      prefix === "52" ||
      prefix === "53" ||
      prefix === "54" ||
      prefix === "55") &&
    cardNumber.length === 16
  ) {
    return "MasterCard";
  }

  if (
    (cardNumber.startsWith("6011") ||
      cardNumber.startsWith("65") ||
      /^64[4-9]/.test(cardNumber.slice(0, 3))) &&
    (cardNumber.length === 16 || cardNumber.length === 19)
  ) {
    return "Discover";
  }
}

정규식

advanced인 'Discover'부분의 조건을 지정하면서 카드 첫 시작 세 자리가 644부터 649 사이인 경우를 조건으로 걸러줘야했기 때문에 어떤 방법을 사용할까 하다가 정규식이 생각나서 정규식을 사용해보았다.

정규식은 '특정 패턴의 문자열'을 찾기 위한 방법인데, 정규식 규칙은 MDN에서 확인할 수 있고, 여러 블로그들에서도 잘 정리해주고 있다.

내가 쓴 /^64[4-6]/.test(cardNumber.slice(0,3)) 은 644~649 형식의 문자열이 카드 번호 시작 세 자리(slice로 자름)와 동일하도록 test하는 코드이다.

정규 표현식에도 여러 매서드들이 존재하는데, match가 매칭되는 항복들을 배열로 반환한다면, test는 매칭되면 true, 아니면 fasle를 반환한다. 조건식에서는 boolean이 더 나을 것 같아 test를 선택했다.



작성한 코드 : detectNetwork.test.js

const discoverTestCase = (testFunction, start, end) => {
  if (end === undefined) end = start; //6011, 65는 끝나는 숫자가 없으므로!

  let dummyNum = "1231345678901234123"; //19자리 임의의 숫자 문자열 
  let length = String(start).length;

  for (let i = start; i <= end; i++) {
    it(`has a prefix of ${i} and a length of 16`, function () {
      testFunction.equal(
        detectNetwork(`${i}${dummyNum.slice(length, -3)}`),
        "Discover"
      );
    });
    it(`has a prefix of ${i} and a length of 19`, function () {
      testFunction.equal(
        detectNetwork(`${i}${dummyNum.slice(length)}`),
        "Discover"
      );
    });
  }
};

describe("Discover", function () {
  let should = chai.should();

  discoverTestCase(should, 6011);
  discoverTestCase(should, 65);
  discoverTestCase(should, 644, 649);
});
  • mocha는 함수를 실행할 때 오류가 발생하면 실패하고, 오류가 발생하지 않으면 실패하지 않는 기능을 하는 도구이다. (공식문서)

  • chai는 테스트에 필요한 헬퍼 함수들이 담긴 라이브러리이고 좀 더 영어 문법에 가까운 코드로 테스트를 작성할 수 있도록 도와준다. (공식문서)
    => should, exprect, assert 같은 함수들을 사용할 수 있다.

test 코드 중에서는 discover 부분만 가지고 왔는데,
원래는 아래와 같이... 엄청 노가다로 해결해보려고 했었는데, 페어가 반복문을 이용해 해결하는 방법을 알려주셨다.

//비효율의 극치 : 모든 경우의 수를 검증 

describe("Discover", function () {
  let should = chai.should();
  // 함수가 없는 테스트는 "pending"이라는 표시가 뜨며 실행되지 않습니다.
  // 아래 테스트를 작성하고 테스트가 통과하도록 만드십시오.
  it("has a prefix of 6011 and a length of 16", function () {
    should.equal(detectNetwork("6011123456789101"), "Discover");
  });
  it("has a prefix of 6011 and a length of 19", function () {
    should.equal(detectNetwork("6011123456789101123"), "Discover");
  });
  it("has a prefix of 65 and a length of 19", function () {
    should.equal(detectNetwork("6511123456789101123"), "Discover");
  });
  it("has a prefix of 65 and a length of 16", function () {
    should.equal(detectNetwork("6511123456789101"), "Discover");
  });
  it("has a prefix of 644 and a length of 19", function () {
    should.equal(detectNetwork("6441123456789101123"), "Discover");
  });
  it("has a prefix of 644 and a length of 16", function () {
    should.equal(detectNetwork("6441123456789101"), "Discover");
  });
  it("has a prefix of 645 and a length of 19", function () {
    should.equal(detectNetwork("6451123456789101123"), "Discover");
  });
  it("has a prefix of 645 and a length of 16", function () {
    should.equal(detectNetwork("6451123456789101"), "Discover");
  });
  it("has a prefix of 646 and a length of 19", function () {
    should.equal(detectNetwork("6461123456789101123"), "Discover");
  });
  it("has a prefix of 646 and a length of 16", function () {
    should.equal(detectNetwork("6461123456789101"), "Discover");
  });
  it("has a prefix of 647 and a length of 19", function () {
    should.equal(detectNetwork("6471123456789101123"), "Discover");
  });
  it("has a prefix of 647 and a length of 16", function () {
    should.equal(detectNetwork("6471123456789101"), "Discover");
  });
  it("has a prefix of 648 and a length of 19", function () {
    should.equal(detectNetwork("6481123456789101123"), "Discover");
  });
  it("has a prefix of 648 and a length of 16", function () {
    should.equal(detectNetwork("6481123456789101"), "Discover");
  });
  it("has a prefix of 649 and a length of 19", function () {
    should.equal(detectNetwork("6491123456789101123"), "Discover");
  });
  it("has a prefix of 649 and a length of 16", function () {
    should.equal(detectNetwork("6491123456789101"), "Discover");
  });
});

아래는 래퍼런스 코드이다. 반복문으로 풀었지만 644~649에만 반복문을 적용한듯.

describe("Discover", function() {
  let should = chai.should();

  //scope가 달라지므로 사용하고자 한다면 꼭 다시 불러와야 합니다.
  //let expect = chai.expect;

  it('has a prefix of 6011 and a length of 16', function() {
    should.equal(detectNetwork("6011123456789012"), "Discover");
    //expect(detectNetwork("6011123456789012")).to.equal("Discover");
  });
  it('has a prefix of 6011 and a length of 19', function() {
    should.equal(detectNetwork("6011123456789012345"), "Discover");
    //expect(detectNetwork("6011123456789012345")).to.equal("Discover");
  });
  it('has a prefix of 65 and a length of 16', function() {
    should.equal(detectNetwork("6512345678901234"), "Discover");
    //expect(detectNetwork("6512345678901234")).to.equal("Discover");
  });
  it('has a prefix of 65 and a length of 19', function() {
    should.equal(detectNetwork("6512345678901234567"), "Discover");
    //expect(detectNetwork("6512345678901234567")).to.equal("Discover");
  });

  for (let i = 644; i <= 649; i++) {
    it(`has a prefix of ${i} and a length of 16`, function() {
      should.equal(detectNetwork(`${i}1234567890123`), "Discover");
      //expect(detectNetwork(`${i}1234567890123`)).to.equal("Discover");
    });
    it(`has a prefix of ${i} and a length of 19`, function() {
      should.equal(detectNetwork(`${i}1234567890123456`), "Discover");
      //expect(detectNetwork(`${i}1234567890123456`)).to.equal("Discover");
    });
  }
});
profile
다시 일어나는 중

0개의 댓글