Android Unit Testing

chaeny·2025년 1월 19일

Unit Test

Unit : 소프트웨어 시스템에서 단일 테스트 가능한 부분으로 객체지향 언어(OOP)에서는 주로 클래스를 의미한다.
Testing : 해당 유닛이 예상대로 동작하는지 검증하는 과정이다.

Unit Test란 각 단위가 예상대로 작동하는지 검증하는 것이다.

Android project 구성

유닛(Unit): 우리가 작성하는 프로덕션 코드 (일반적인 기능 구현 코드)
테스트(Test): 해당 유닛을 테스트하기 위한 테스트 클래스

장점

1. 유연하고 유지보수 가능

"유닛 테스트는 코드의 유연성과 유지보수성을 높이는 핵심 요소다."

유닛 테스트를 통해 프로덕션 코드를 변경하더라도 다른 코드에 영향을 미치지 않도록 안전하게 변경할 수 있다.
자신감을 가지고 코드 수정이 가능해지며, 이를 통해 비용 절감과 기능 변경 시 버그 감소 효과를 얻을 수 있다.

2. 재사용성

유닛 테스트를 작성하면 코드의 결합도를 낮춰(loose coupling) 재사용 가능성이 높아진다.
특히 TDD(Test Driven Development, 테스트 주도 개발)를 사용할 경우, 유닛 테스트가 재사용 가능한 코드를 작성하도록 강제한다.

3. 좋은 문서 역할

유닛 테스트가 깨끗하게 작성되었다면, 이는 시스템의 좋은 문서화 자료가 된다.
테스트를 통해 코드의 동작 방식을 쉽게 이해할 수 있다.

유닛 테스트는 기존 코드의 변경을 두려워하지 않게 하고, 코드의 재사용성을 높이며, 시스템의 동작을 문서화한다

Naming Convention

테스트 코드는 프로덕션 코드만큼이나 중요합니다. 생각, 설계, 그리고 주의가 필요합니다. 프로덕션 코드만큼 깨끗하게 유지되어야 합니다.

일부 개발자는 다음과 같은 규칙을 선호하지 않는다.

MethodName_StateUnderTest_ExpectedBehavior
메서드 이름 + 테스트 상황 + 예상 동작

왜냐하면 메서드 이름을 포함하고 있으며, 유닛 테스트는 코드가 아닌 동작을 테스트해야 한다는 주장이 있다. 즉, 프로덕션 코드의 변경에 의해 테스트 코드가 영향을 받아서는 안 된다.
이 규칙은 테스트 메서드를 프로덕션 코드에 강하게 결합시키므로, 프로덕션 코드에서 메서드 이름을 변경하면 해당 메서드를 테스트하는 모든 "테스트 메서드"의 이름도 변경해야 한다.

앞서 언급했듯이, 이는 테스트가 코드가 아닌 동작을 테스트해야 하기 때문이다. 그러나 유틸리티 클래스를 테스트하는 경우에는 이 규칙을 사용할 수 있다. 메서드 이름을 포함하고 있어 추적이 더 용이하기 때문이다.

따라서 비즈니스 로직의 경우 메서드 이름을 포함하지 않는 명명 규칙을 사용할 수 있다. 예를 들어, 다음과 같은 규칙이 있다.

When_[StateUnderTest]_Expect_[ExpectedBehavior]
상황 + 기대 결과

주어진 조건/입력과 기대결과/출력

Kotlin

Kotlin과 같은 현대적인 언어는 테스트 메서드에 공백을 허용한다.
따라서 더 가독성이 높아진다

@Test
fun `when server error expect post not cached`() {
    // 
}

Choosing Test Cases

모든 가능한 경우를 테스트할 수는 없다. 대신, 함수의 입력/인수를 카테고리와 경계 값으로 나누고, 각 카테고리와 경계 값에서 값을 선택하여 테스트한다.

테스트 케이스의 수는 각 경우에 따라 다르다. 그러나 코드 커버리지가 100%가 아니라면 더 많은 테스트를 작성해야 한다. 하지만 100% 커버리지가 모든 가능한 경우를 다루었다는 것을 의미하지는 않는다.

정리

유닛 테스트는 프로덕션 코드만큼이나 중요하다. 테스트 코드는 가능한 한 깨끗하게 유지해야 유지보수가 쉬워지고, 유닛 테스트가 실제로 도움이 될 수 있다. 그렇지 않으면 유닛 테스트가 오히려 코드를 복잡하게 만들고 유지보수를 어렵게 만들 수 있다.

올바른 테스트 케이스를 선택하는 방법은 입력값(혹은 인자)을 카테고리와 경계값으로 나눔으로써 가능하다.

또한, 코드 커버리지가 100% 미만이라면 더 많은 테스트 케이스를 추가해야 한다. 하지만, 100% 커버리지가 모든 가능한 경우를 다 다루었다는 뜻은 아니다.

https://betterprogramming.pub/android-unit-testing-choosing-naming-convention-and-test-cases-d1a3122ac28a

etc

TDD

https://jdragon.oopy.io/728182cb-91f3-4cb2-abe7-b4320bce2a9e

TDD (테스트 주도 개발) 실천

  • 테스트 -> 코드 -> 리팩토링 -> (반복) -> 커밋 이어야 한다.

TDD 계명

  • 새 코드를 작성하기에 앞서 실패하는 자동화 테스트를 작성하라.
  • 중복을 제거하라.
  • TDD는 중복을 제거함으로써 유지보수가 쉬운 코드를 작성하도록 유도한다.

JUnit 모범 사례: 실패하는 테스트를 먼저 작성하라.

  • 새로운 코드를 작성하려면 반드시 실패하는 테스트를 먼저 작성해야 한다.
  • 왜 만드나? 그 일을 성공케 하는 코드를 아직 작성하지 못했기 때문이다.
  • 실패한 테스트를 해결하는 단순한 구현을 찾아서 성공시킨다.
  • 그리고 잉여로직을 제거하고 의도를 명확히 하고 최적화 시킨다 (리팩토링).
  • 결론은 항상 테스트를 먼저 작성하고, 이후에 성공시키는 코드를 만들어라.

https://seunggabi.tistory.com/entry/JUnit-in-Action
https://mangkyu.tistory.com/397

0개의 댓글