#Android Test 공식 문서
=>d.android.com/training/testing
#Google Testing Blog
=>https://testing.googleblog.com
-테스트는 변경되어서는 안된다. 작업이 변경되거나 기능이 변경되지 않고 어떤 코드의 재배치 같은 게 일어나거나 새로운 기능이 추가됐다든가 기존에 있는 것들과 연관성이 없는 것들이 추가됐다든가 버그가 수정됐다든가 하는 이유들로 인해서 테스트 코드가 변경되어서는 안된다는 것이 테스트 불변성의 원칙이다. 이로인해 테스트 코드는 코드 변경이 일어나도 신뢰할 수 있는 검증 수단으로서의 역할을 할 수 있다. 예를들면은 아까까지는 테스트가 잘 이루어졌는데 갑자기 원래 코드의 다른 기능을 수정했을 때 테스트 코드가 실행되지 않는다면(이런 것들을 깨지기 쉬운 테스트라고 부른다) 테스트 불변성의 원칙에 어긋난 것이다. 만약 테스트 불변성을 잘 지켰다면 새로운 기능이 추가되도 테스트 코드의 실행에 정상적으로 수행 될 것이다.
-Unit 테스트에서 메서드 하나만을 테스트하는 것은 흔한 방식이지만 이것이 전부가 아니다. 쉽게 말하면 하나의 메서드가 여러가지 다른 시나리오에서 어떻게 실행되는지에 대한 테스트 케이스를 작성 할 수 있다. 단순히 메서드가 올바른 코드인지 확인하는 것을 넘어서 메서드의 행위에 대한 검증(테스트)을 하라고 하는 것이다. 즉, 테스트 케이스에 단 하나의 행위 또는 상태만을 검증해야하는데 , 이는 두개 이상의 행위 또는 상태를 검증하려고 하면 결과 해석이 복잡해지고 유지보수 또한 어려워지고 테스트의 독립성 문제를 유발하기 때문이다.
-테스트 케이슨 사실 메서드를 테스트하기 위한 것이 아니라 어떠한 기능이 움직이는지 그리고 해당 기능이 어떤 결과를 보여주는지에 대한 어떤 행위에 대한 테스트이기 때문이다. 즉, 메서드와 테스트 케이스의 비율은 1 : N이 된다. 테스트 성공과 실패케이스는 다양하다. 그래서 메서드와 테스트 케이스의 비율이 1 : N이 되는것이다.
@Test
fun testDisplayTransactionResults() {
transactionProcessor.displayTransactionResults(
newUserWithBalance(
LOW_BALANCE_THRESHOLD.plus(dollars(2))),
Transaction("Some Item" , dollars(3)))
assertThat(ui.getText()).contains("You bought a Some Item")
assertThat(ui.getText()).contains("your balance is low")
}
@Test
fun displayTransactionResults_showsItemName() {
transactionProcessor.displayTransactionResults(
User() , Transaction("Some Item"))
assertThat(ui.getText()).contains("You bought a Some Item")
}
@Test
fun displayTransactionResults_showsLowBalanceWarning() {
transactionProcessor.displayTransactionResults(
newUserWithBalance(
LOW_BALANCE_THRESHOLD.plus(dollars(2))),
Transaction("Some Item" , dollars(3)))
assertTha(ui.getText()).contains("your balance is low")
}
-테스트 케이스의 3요소 [1. GIVEN : 조건 , 환경 , 상태] , [2. WHEN : 테스트 대상 행위] , [3. THEN : 의도한 결과]를 명확히 분리시키라는 의미이다.
@Test
fun transferFundsShouldMoveMoneyBetweenAccounts() {
val account1 = newAccountWithBalance(usd(150))
val account2 = newAccountWithBalance(usd(20))
bank.transferFunds(account1 , account2 , usd(100))
assertThat(account1.getBalance()).isEqualTo(usd(50))
assertThat(account2.getBalance()).isEqualTo(usd(120))
}
-테스트 코드가 대상이 되는 코드와 동일한 로직을 공유하지 않도록 유의해야 한다. 대상 코드와 다른 로직을 사용하는 것이 아니라 할 수 있는 한 로직을 제거해야한다. 어떤 로직이 들어감으로 인해서 대상 테스트 코드의 실패여부가 결정되거나 잘못된 결과가 나오는 것을 방지하기 위함이다.
-만약 테스트 코드에 로직이 포함되어 있다면 , 해당 로직에 대한 정확성 또한 검증을 해야되는데 테스트 코드의 목적은 대상 코드의 정확성을 검증하는 것이지 테스트 코드 자체의 로직을 검증하는 것은 아니다. 또한 , 테스트 코드는 예상 결과와 실제 결과를 비교함으로서 대상 코드의 동작을 검증한다. 그런데 테스트 코드 내부에 로직이 있으면 테스트 코드를 유지 및 보수하는데 불편하다.
@Test
fun shouldNavigateToAlbumsPage() {
val baseUrl = "https://photos.google.com/"
val nav = Navigator(baseUrl)
nav.goToAlbumPage()
assertThat(nav.getCurrentUrl).esEqualTo(baseUrl + "/////////albumns")
}
@Test
fun shouldNavigateToAlbumsPage() {
val baseUrl = "https://photos.google.com/"
val nav = Navigator(baseUrl)
nav.goToAlbumPage()
assertThat(nav.getCurrentUrl).esEqualTo("https://photos.google.com/albums")
}
-댐프와 드라이는 서로 상충되는 개념으로 볼 수 있는데 이때 우선순위는 DAMP가 DRY보다 더 높다. 왜냐하면 테스트 코드에서는 더 좋은 테스트 코드를 만들 수 있다고 하면 더 확실하고 더 의미있는 테스트 코드들이 만들어질 수 있다는 가정하에서는 어느 정도의 반복되는 코드는 허락된다.
-위 문장을 한글로 번역하면 "서술적이고 의미 있는 구문" 즉, 읽었을 때 이해가 빡빡되도록 작성하라는 뜻이다. 이렇게 하면 코드의 가독성이 높아져서 협업에 용이하고 문제가 발생했을 때 코드의 수정이 쉽다. DAMP 원칙을 지킨다면 , 코드의 반복이 생길 수 도 있고 코드의 길이가 늘어 날 수 도 있지만 , 테스트 코드의 이해도와 유지보수를 위해서 어느정도의 반복을 허용한다.
-반복된 코드를 작성하지말라는 의미이다.
만약 Immutable한 객체라면 공유해도 괜찮다.
초기 상태를 명확하게 설정할 수 있고 , 지금 테스트하고 있는 내용들과 상관없는 것들은 감출 수 있도록 하는 것들이 좋다.
나쁜 예시코드(1)
아래코드에서 다른코드가 끼게되면 setUp()에서 설정한 값들이 오염된 상태로 shouldReturnNameFromService()가 실행되서 테스트 코드가 명확하지 않다.
private lateinit nameService : NameService
private lateinit userStore : UserStore
@Before
fun setUp() {
nameService = NameService()
nameService.set("user1" , "Donald Knuth")
userStore = UserStore(nameService)
}
여기에 다른 코드가 낌.
@Test
fun shouldReturnNameFromService() {
val user = userStore.get("user1")
assertThat(user.getName()).isEqualTo("Margaret Hamilton")
}
private lateinit nameService : NameService
private lateinit userStore : UserStore
@Before
fun setUp() {
nameService = NameService()
nameService.set("user1" , "Donald Knuth")
userStore = UserStore(nameService)
}
@Test
fun shouldReturnNameFromService() {
nameService.set("user1" , "Margaret Hamilton")
val user = userStore.get("user1")
assertThat(user.getName()).isEqualTo("Margaret Hamilton")
}