Dev/General

[소프트웨어 테스트] 자동화 테스트와 유닛 테스트

싯벨트 2022. 9. 4. 21:52
728x90

1. 소프트웨어 테스트

1-1. 정의

소프트웨어를 배포하기 전에 정상적으로 작동하는가에 대한 검증하는 단계

  • 노출되지 않은 결함과 오류 발견을 통한 품질(동작, 성능, 안정성) 평가 및 개선

1-2. 분류

Did we build it right? (올바르게 코드를 짰는가?)

1) 단위 테스트(Unit Test)

  • 함수 및 클래스 단위로 진행할 수 있기에 가장 많은 수를 차지
  • 모든 코드의 단위 테스트가 가능하다는 것(Testable)은 독립적으로 테스트를 통해 다른 단위에 영향을 주지 않으면서 안전하게 테스트 하며 개발품질을 높일 수 있는, 즉 모듈화가 잘 되었다고 볼 수 있음

2) 통합 테스트(Integration Test)

  • 단위 테스트의 단위(모듈)들을 조합하여 테스트를 구성
  • 상향식 통합 테스트: 존재하는 모듈이 통합되었을 때 잘 동작하는지 테스트
  • 하향식 통합 테스트: 하위 모듈이 있는 것을 가정하고 상위 모듈을 테스트
Did we build the right thing? (코드가 올바르게 동작하는가?)

3) 인수 테스트(Acceptance Test)

  • 통합 테스트에서 더 나아가 실제 환경처럼 세팅하어 테스트
  • 알파 테스트: 통제된 환경에서 선별된 사용자들(사내 직원 등)이 개발자와 함께 수행하는 테스트
  • 베타 테스트: 실제와 동일한 환경에서 사외의 사용자들이 테스트하게 하고 피드백을 받는 방법

1-3. 코드 커버리지

1) 정의

테스트가 코드를 얼마나 커버하는지에 대한 정도

2) 분류

함수(function), 구문(statement), 조건(condition), 분기(branch)

  • 함수 커버리지 (함수가 실행되지 않는다면 다른 커버리지 또한 측정을 할 수 없음)
함수 커버리지(%) = 실행된 함수 개수 / 총 함수 개수 * 100
  • 구문 커버리지 (Javascript 에서 보통 세미 콜론(;)으로 구분됨)
구문 커버리지(%) = 실행된 구문 수 / 전체 구문 수 * 100
  • 조건 커버리지 (전체 조건 = 조건 개수 ^ 2)
조건 커버리지(%) = 실행된 경우의 수 / 전체 조건 수 * 100
  • 분기 커버리지 (분기 - 조건으로 인하여 나뉘게 되는 실행 경로)
분기 커버리지(%) = 실행된 분기 수 / 총 분기의 개수 * 100

1-4. 소프트웨어 테스트 역사

By Gelperin과 Hetzel의 진화적 테스팅 모델 개념

  • 디버깅 지향 시대 (debugging-oriented period) - 버그 제거
  • 증명 지향 시대 (demonstration-oriented period) - 요구사항 구현 포함
  • 파괴 지향 시대 (destruction-oriented period) - 오류 발견(테스트) 후 디버깅
  • 평가 지향 시대 (evaluation-oriented period) - 개발 생명 주기에서 검토 역할
  • 예방 지향 시대 (Prevention-oriented Period) - 문제를 미리 찾아냄

2. 테스트 자동화

2-1. TDD(Test Driven Develpment) - 테스트 주도 개발

테스트 코드가 설계의 역할(애자일한 개발 접근법)

  1. 테스트코드를 먼저 작성
  2. 하드 코딩하여 재빠르게 기능을 구현
  3. 리팩토링하여 성능 및 안정성을 높입니다.

2-2. 테스트 자동화의 중요성

개발팀의 생산성 향상과 시스템의 안정적 운영 추구

  • 휴먼 에러 제거
  • 정확성 유지
  • 반복적 테스트 실행 가능
  • 빠지는 부분없이 테스트 실행 가능

단위 테스트 자동화 필수 구현

디버깅 하기 쉽고 100% 자동화가 가능하기 때문에 시스템 테스트 전략중 가장 많은 비율을 차지(70%)

3. 테스트 자동화 실습

3-1. 유닛 테스트 실습

테스트 코드 구성

구성 형태 세부 설명
describe 부분 describe (description, callback) # description:
  해당 부분에 대한 카테고리 및 설명

# callback 내부:
- 다시 describe 및 test 를 선언가능
- expect를 통해 값을 비교하여 정상적으로 수행했는지 확인 가능
- 하나의 테스트에 여러개의 expect 가능
test 부분
# 새로운 폴더에서 한다면 $ npm init -y 로 package.json 설치부터

$ npm i -D jest

# npx 명령어를 수행하면 프로젝트 내에 설치된 jest가 *.test.js 나 *.spec.js 같은 테스트 코드 파일들을 찾아 자동으로 실행

$ npx jest

테스트 코드 예제 - 사칙연산 계산기

// calculate.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiple(a, b) {
  return a * b;
}

function divide(a, b) {
  return a / b;
}

module.exports = { add, subtract, multiple, divide }
// calculate.test.js
const { add, subtract, multiple, divide } = require('./calculate');

describe('calculate unit test', () => {
  describe("add", () => {
    test("add(1,2)는 3이 되어야합니다.", () => {
      expect(add(1, 2)).toBe(3);
    });
  });

  describe("subtract", () => {
    test("subtract(2,1)는 1이 되어야합니다.", () => {
      expect(subtract(2, 1)).toBe(1);
    });
  });

  describe("multiple", () => {
    test("multiple(3, 4)는 12가 되어야합니다.", () => {
      expect(multiple(3, 4)).toBe(12);
    });
  });

  describe("divide", () => {
    test("divide(10, 2)는 5가 되어야합니다.", () => {
      expect(divide(10, 2)).toBe(5);
    });
  });
});

3-2. 통합 테스트 실습

더하기 결과와 빼기 결과를 곱하는 과정

// calculate.test.js
const { add, subtract, multiple, divide } = require('./calculate');

...기존 unit test

/// 통합 테스트 추가
describe("calculate integration test", () => {
  test("add(1, 3)과 subtract(4, 2)를 multiple한 결과는 8입니다.", () => {
    const addResult = add(1, 3);
    expect(addResult).toBe(4);
    const subtractResult = subtract(4, 2);
    expect(subtractResult).toBe(2);
    expect(multiple(addResult, subtractResult)).toBe(8);
  });
});

3-3. 테스트 커버리지 확인

npx jest --coverage

분기 만들고 test 추가하기

calculate.js add함수에 if문을 통한 분기 함수 추가

function add(a, b) {
  if (b < 0) {
    throw new Error("b가 0보다 작습니다. subtract를 사용하세요!");
  }
  return a + b;
}

커버리지 확인 결과 - 분기(Branch 커버리지 50%)

해당 분기에 대한 테스트 추가

  • expect(err).toBeInstanceOf(Error) // Error Class의 인스턴스인지 확인
  • expect(err).toHaveProperty("message", "b가 0보다 작습니다. subtract를 사용하세요!"); // throw된 err객체의 프로퍼티(키벨류)가 일치하는지 확인
describe("add", () => {
  test("add(1, 2)는 3이 되어야합니다.", () => {
    expect(add(1, 2)).toBe(3);
  });

  // 아래 테스트 추가
  test("add(1, -1)은 에러를 던집니다.", () => {
    try {
      add(1, -1);
    } catch (err) {
      expect(err).toBeInstanceOf(Error)
      expect(err).toHaveProperty("message", "b가 0보다 작습니다. subtract를 사용하세요!");
    }
  });
});

참고자료