프리코스 1주차에 TDD를 적용해보면서 테스트를 잘 하는 것이 생각보다 어렵다고 느꼈다.
따라서, 그동안 기본적인 assertThat, Thrown 정도만 사용하고 있었는데 테스트를 적극 활용해보기 위해 JUnit의 여러 테스트 메소드들과 이외의 테스트를 좀 더 활용할 수 있는 방법을 살펴보려고 한다!
1.조건을 통과하는 경우 2. 한 예외에 해당하는 여러 입력 경우 3. 공백인 경우 등을 테스트 할 때 이렇게 많은 테스트 메소드들이 필요했다.
하지만, 이걸 간략화 할 수 있는 Parameterized Tests와 기타 Junit의 테스트 메소드를 활용하는 방법을 알아보려고 한다!
예) 1,2가 아닌 입력인 a, 11, a A 등이 모두 예외 처리가 돼야함
이렇게 하면 똑같은 테스트 메소드를 입력만 다르게 여러개 쓸 필요가 없다.
예외 처리 확인할 때 유용할듯!
예) Q를 입력하면 true, R을 입력하면 false
예) 1을 입력하면 RETRY, 2를 입력하면 Retry enum 객체의 END
이렇게 객체 등의 조금 복잡한 형태로 입력, 출력을 보려면 @MethodSource
를 쓰면 된다!
이렇게 하면 validate 메소드가 public일 필요도 없겠구나!
그동안은 테스트 코드 안에 정상 코드 껴놓고 넘어가면 된 걸로 했는데 이렇게 정상인 경우를 테스트 하는 코드가 있었다니...
의문이 들었을 때 그냥 지나가곤 했던 나를 반성한다...
static void assertEquals(Object expected, Object actual) {
assertEquals(expected, actual);
}
이 코드를 해석해보면 실제 어떤 숫자, 문자의 객체가 들어와서 어떤 결과가 나오는지를 자세히 알 수는 없지만 그 메소드 안에 구현 부분이 잘 작동한다는 걸 테스트하는 것 같다.
그럼 이게 무슨 의미가 있냐. 라고 한다면
그 메소드 안의 구현 부분, 그러니까 조금 더 구체적인 메소드는 mock을 이용한 게 아니라 실제 값으로 테스트가 된 부분이라는 전제 조건 하이기 때문에,
속의 메소드가 제대로 동작하는 게 보장되었다면
그걸 이용한 메소드는 속의 보장된 메소드로 이어지는 부분이 잘 작동되었는지만 확인하면 되기 때문이다!
mock(MoveHistory.class)
: 이 코드는 MoveHistory 클래스의 mock(가짜) 인스턴스를 생성한다.
이 Mock 객체는 실제 객체처럼 동작하지 않고, 대신 테스트 중에 모의상태로 사용된다.
verify(moveHistory).mark(any(PlayerMove.class))
: MoveHistory 객체의 mark 메서드가 PlayerMove 클래스의 어떠한 인스턴스로 호출되었는지를 검증한다.
verify(mock).method(param);
해당 Mock Object 의 메소드를 호출했는지를 검증한다.
Mockito Argument Matchers - any() - 공식문서 번역
때때로 우리는 주어진 유형의 인수에 대한 동작을 mocking하고 싶을 때가 있습니다. 이 경우 Mockito Argument Matchers(인자 일치자)를 사용할 수 있습니다. org.mockito.ArgumentMatchersMockito 인수 메서드는 클래스에 static 메서드로 정의됩니다.
이 markHistory나 mark 메소드는 작은 단위의 테스트이므로 실제 값으로 테스트를 마친 메소드들이라 이렇게 쓸 수 있는 것이다!
아래와 같은 메소드를 활용하면 호출 횟수도 검증할 수 있다.
단, 횟수 관련 메소드를 파라미터에 입력하지 않으면 기본적으로 1번 호출하는지를 검증한다.
getVictoryStatus가 Success인 경우를 테스트하는 코드가 만들어진 과정은 다음과 같다.
메소드가 속한 Player 객체를 모의로 만들어야하고
메소드 속이 moveHistory.isAllMoved(true) 이렇게 되어있어야한다.
이 때 이렇게 when(메소드).thenReturn(기대반환값)
이 쓰일 수 있다!
단, when~을 쓸 때에는 해당 인자가 mock 객체여야한다.
set.add()
는 중복되지 않은 값이 add되면 true를, 이미 있던 값이 add 되면 false를 반환한다.
이를 이용해 중복없는 랜덤 뽑기가 정상적으로 작동하는지를 테스트할 수 있다!
위와 같이 범위 내 숫자 검증을 isBetween으로 구현할 수 있다!
공식문서를 살펴보니 활용할 수 있는 더 다양한 메소드들이 있어서 다음에 써봐야겠다!
@Test
void isPlayerMoved는_움직인_경우_true응답한다() {
// 메소드를 호출하기 위해 player를 mock 객체들을 이용해 생성한다.
TrialCount trialCount = mock(TrialCount.class);
MoveHistory moveHistory = mock(MoveHistory.class);
Player player = Player.fromTest(trialCount, moveHistory);
// player.isPlayerMoved 메소드에서 실행될 moveHistory.isPlayerMoved의 반환값을 true로 주입해놓음
when(moveHistory.isPlayerMoved()).thenReturn(true);
boolean isMoved = player.isPlayerMoved();
// 위의 메소드 실행 후, moveHistory 객체에서 isPlayerMoved가 실행되었는지 확인
verify(moveHistory).isPlayerMoved();
// 매소드 실행 결과 일치 확인
assertTrue(isMoved);
}
enum은 생성자가 따로 없거나 생성자로 생성되는 게 아니기 때문에 입력을 통해 enum으로 변환되어야 하는 경우 validate 위치가 애매하다고 생각했는데,
이 때 from 메소드 안의 stream에 orElseThrow 안에 넣는 방법이 있었다!
filter로 validate 내용을 넣어주고 해당되지 않는 예외 경우들을 다 orElseThrow로 던져주면 되는 방식이다.
Predicate란?
Predicate는 함수형 인터페이스로, 조건을 검사하는 데 사용됩니다. Predicate는 일반적으로 함수형 프로그래밍 또는 Java 8의 람다 표현식과 함께 사용됩니다.
Predicate를 사용함으로써, 해당 조건을 명시적으로 정의하고 필터링할 수 있으며, 이는 함수형 프로그래밍에서 코드를 간결하게 작성하고 가독성을 향상시키는 데 도움이 됩니다.
원래는 이렇게 boolean 메소드로 작성할 수도 있겠지만
Predicate 메소드로 사용하면 스트림 안에서 작성된 코드가 간결해지는 효과가 있다.
물론 간단한 경우에는 그냥 boolean을 사용하는 것이 더 낫다!
공부를 위해 mock을 안써보라는 프리코스 채널의 답변을 보고 mock을 대체하기위해,
함수형 인터페이스의 추상메소드가 하나인 속성을 이용해 number -> ~~ 이렇게 알아서 인자를 넣어주고 결과로 간주하는 방법을 적용하고 싶었다.
하지만 그러면 generate가 3,4,5를 불러오는 게 아니라 3을 갖고 intstream을 다 돌아버렸다.
그래서 일단 mock을 쓰는 버전으로 테스트를 구현해놓은채로 고민에 빠졌다.. to be continue...