3/4/2025
Timothy @ Daangn Frontend Core
Software Engineer, Frontend @Daangn Frontend Core Team
ex) Senior Lead Software Engineer @NHN Dooray (2021 ~ 2023)
ex) Lead Software Engineer @ProtoPie (2016 ~ 2021)
ex) Microsoft MVP
이 웅재
단위 테스트의 이름 짓기 규칙
읽기 좋은 테스트 작성
작업 단위의 진입점 (혹은 현재 테스트 중인 기능 이름)
작업 단위나 진입점의 이름은 테스트를 읽는 사람이 코드의 기능 범위를 쉽게 알 수 있게 해 주기 때문에 상당히 중요하다.
테스트 이름의 첫 부분에 이를 포함하면 테스트 파일에서 쉽게 탐색할 수 있다.
진입점을 테스트하는 상황
작업 단위의 종료점이 실행해야 하는 동작
무엇을 해야 하거나 반환해야 하는지, 또는 어떻게 동작해야 하는지 알기 쉬운 말로 설명해야 한다.
test("verifyPassword, with a failing rule, returns error based on rule.reason", () => {
// ...
});
describe("verifyPassword", () => {
describe("with a failing rule", () => {
it("returns error based on the rule.reason", () => {
// ...
});
});
});
verifyPassword_withFailingRule_returnsErrorBasedonRuleReason()
중요한 점은 이 세 가지 정보 중 하나라도 빠지면 테스트를 읽는 사람은 테스트를 이해하기 위해 테스트 코드를 읽어야 하므로 소중한 시간을 낭비하게 된다는 것이다.
// 무엇을 테스트하는가?
test("failing rule, returns error based on rule.reason", () => {
// ...
});
// 이것이 언제 발생해야 하는가?
test("verifyPassword, returns error based on rule.reason", () => {
// ...
});
// 그렇다면 어떤 일이 발생해야 하는가?
test("verifyPassword, with a failing rule", () => {
// ...
});
코드 가독성을 높여 다른 개발자가 테스트 이름만으로도 무엇을 테스트하는지 알기 쉽게 하는 것
테스트가 실패했을 때 보통 표시되는 유일한 정보가 테스트 이름이기 때문에필수 정보 세 가지를 포함해야 한다. (디버깅 시간과 코드를 읽는 시간을 훨씬 절약할 수 있다.)
좋은 테스트 이름은 문서 역할도 한다. 새로 온 동료가 테스트를 읽고 시스템을 이해할 수 있다면 가독성이 좋다는 것이다. 반대라면 문제가 있다는 것이다.
"매직"
마치 마법처럼 이 값들이 작동하지만 왜 그렇게 작동하는지 알 수 없다는 의미
매직 문제 (witchcraft value)
하드코딩된 값
기록에 남지 않은 값
명확하게 이해되지 않는 상수나 변수
describe('password verifier', () => {
test('on weekends, throws exceptions', () => {
expect(() => verifyPassword('jhGGu78!', [], 0))
.toThrowError("It's the weekend!");
});
});
매직 넘버
'jhGGu78!'
[]
0
describe('verifier2 - dummy object', () => {
test('on weekends, throws exceptions', () => {
const SUNDAY = 0, NO_RULES = [];
expect(() => verifyPassword2('anything', NO_RULES, SUNDAY))
.toThrowError("It's the weekend!");
});
});
매직 넘버
'jhGGu78!' => 'anything' : 아무거나
[] => NO_RULES : 비밀번호의 유효성을 검증할 규칙이 없음
0 => SUNDAY : 일요일을 의미하는 숫자
가독성을 높이려면 검증 단계와 실행 단계를 한 문장에 넣지 말아야 한다.
expect(verifier.verify("any value")[0]).toContain("fake reason");
const result = verifier.verify("any value");
expect(result[0]).toContain("fake reason");
설정 함수에서 목과 스텁을 설정하면 테스트 내에서는 어디서 만들었는지 찾을 수 없다.
테스트를 읽는 사람은 모의 객체가 사용되고 있다는 사실이나 테스트가 모의 객체가 어떤 값이나 동작을 수행하는지 알지 못할 수 있다.
초기화 함수는 파일 맨 앞에 있지만, 테스트는 그보다 훨씬 아래에 있다.
어디서 초기화 되었더라?
테스트에서 어떻게 동작할까?
목은 테스트 내에서 직접 초기화하고 모든 기댓값을 설정하는 것이 훨씬 더 가독성이 좋다.
describe("password verifier", () => {
let mockLog;
beforeEach(() => {
mockLog = Substitute.for<IComplicatedLogger>();
});
test("verify, with logger & passing, calls logger with PASS", () => {
const verifier = new PasswordVerifier([], mockLog);
verifier.verify("anything");
mockLog.received().info(
Arg.is((x) => x.includes("PASSED")),
"verify"
);
});
});
describe("password verifier", () => {
test("verify, with logger & passing, calls logger with PASS", () => {
const mockLog = Substitute.for<IComplicatedLogger>();
const verifier = new PasswordVerifier([], mockLog);
verifier.verify("anything");
mockLog.received().info(
Arg.is((x) => x.includes("PASSED")),
"verify"
);
});
});
describe("password verifier", () => {
test("verify, with logger & passing, calls logger with PASS", () => {
const mockLog = makeMockLogger();
const verifier = new PasswordVerifier([], mockLog);
verifier.verify("anything");
mockLog.received().info(
Arg.is((x) => x.includes("PASSED")),
"verify"
);
});
});