여러분, 좋은 코드와 테스트 용이한 코드가 무엇인지 알고 계신가요? 소프트웨어 개발에서 중요한 두 가지 개념은 “좋은 코드”와 “테스트가 용이한 코드”입니다. 이 두 가지를 이해하고 적용하는 것은 우리의 개발 효율성을 극대화하고, 소프트웨어의 품질을 높이는 데 큰 도움이 됩니다.
테스트 용이성의 정의와 중요성
먼저, 테스트 용이성이란 무엇일까요? 소프트웨어 테스트 용이성에 대한 정의는 여러 곳에서 다양하게 찾아볼 수 있습니다. 일반적으로 “시스템을 테스트하는 것이 얼마나 쉬운가” 또는 “코드를 어느 정도까지 테스트할 수 있는가”로 정의됩니다. 그러나 이러한 정의는 주관적일 수 있습니다.
그래서 저는 “테스트 용이성은 코드의 복잡성과 테스트 설정의 복잡성 간의 관계를 설명하는 소프트웨어의 특성”이라고 정의하고 싶습니다. 테스트 용이성은 코드와 테스트 설정의 복잡성이 어떻게 상호작용하는지에 따라 달라집니다. 코드만으로는 테스트 용이성을 판단할 수 없으며, 항상 코드와 그 복잡성 간의 관계에 따라 평가되어야 합니다.
코드 복잡성과 테스트 용이성의 관계
코드의 복잡성과 테스트 설정의 복잡성 사이의 관계를 이해하는 것은 중요합니다. 예를 들어, 아래의 두 함수를 살펴보겠습니다.
export function add(a: number, b: number) {
return a + b;
}
export async function updatePost(postId: string, payload: Post) {
const existingPost = await db.posts.findFirst({ id: postId });
if (!existingPost) {
throw new InputError(`Cannot find post with id "${postId}"`);
}
const nextPost = await existingPost.update(payload);
return nextPost;
}
객관적으로 보면 add 함수는 updatePost 함수보다 덜 복잡합니다. 그러나 복잡성이 낮다고 해서 반드시 테스트 용이성이 높은 것은 아닙니다. 각각의 함수는 그 자체의 목적을 달성하기 위해 설계되어야 하며, 복잡성은 필요한 경우 정당화될 수 있습니다.
테스트 설정의 중요성
복잡한 코드일지라도 잘 설계된 테스트 설정이 있다면 테스트가 쉬워질 수 있습니다. 반대로, 단순한 코드라도 테스트 설정이 복잡하면 테스트가 어려울 수 있습니다. 예를 들어, 아래의 isLegacyUser 함수를 살펴봅시다.
export async function isLegacyUser(userId: string) {
const user = await db.users.findFirst({ id: userId });
return user?.type === 'legacy';
}
이 함수는 표면적으로는 간단하지만, 데이터베이스 의존성 때문에 테스트 설정이 복잡해질 수 있습니다. 이러한 복잡성은 코드의 의도와 테스트 설정의 복잡성 사이의 불일치를 보여줍니다.
좋은 코드와 테스트 용이성
좋은 코드는 항상 테스트하기 쉬운 코드입니다. 코드의 품질을 높이기 위해서는 좋은 코드 작성을 목표로 해야 합니다. 그러나 좋은 코드 작성만으로는 충분하지 않습니다. 테스트 용이성은 지속적으로 관찰하고 관리해야 하는 특성입니다.
또한, 테스트 설정을 단순화하고, 반복적인 작업을 자동화하는 유틸리티 함수를 제공함으로써 테스트 환경에 투자하는 것도 중요합니다. 이러한 노력은 테스트 설정의 복잡성을 줄이고, 더 나은 테스트 용이성을 확보하는 데 큰 도움이 됩니다.
결론
좋은 코드와 테스트 용이한 코드는 소프트웨어 개발에서 중요한 요소입니다. 이 두 가지를 이해하고 적용하면, 더 나은 소프트웨어를 개발할 수 있습니다. 코드의 복잡성과 테스트 설정의 복잡성을 적절히 관리하여 테스트 용이성을 높이는 것이 중요합니다. 여러분도 지금부터 이 가이드를 참고하여 더 나은 코드를 작성해 보세요!
참고 자료: Epic Web Dev, “Good Code, Testable Code”