단위테스트(Unit Test)를 작성하는데 AAA 패턴을 이용해서 작성하면 단순하고 균일한 구조를 갖는데 도움을 주며 가독성 또한 좋아진다.
AAA패턴이란 준비(arrange), 실행(act), 검증(assert) 세 단계로 Test를 작성하는 패턴을 의미한다.
- 준비(arrange) : 테스트에 필요한 변수나 객체를 생성한다. 필요에 따라 mock 객체를 만든다.
- 실행(act) : 테스트 할 코드를 실행해 본다.
- 검증(assert) : 실행한 코드가 설계한대로 정확하게 동작했는지를 검증해본다. Unit Test의 Api인 assertEqals() 등의 코드를 사용하여 검증한다.
비슷한 패턴으로 Given-When-Then 패턴이 존재 한다.
아래 코드는 AAA패턴을 이용해서 Test 코드를 작성한 것이다.
import static org.junit.Assert.*;
import org.junit.Test;
public class MyMathTest {
@Test
public void mul_of_two_numbers() {
// arrange
MyMath math = new MyMath();
int first = 5;
int second = 30;
// act
int result = math.mul(first, second);
// assert
assertEquals(150, result);
}
}
각 단계마다 코드의 길이에 대한 설명이다.
- 준비(arrange) : 필요한 객체를 생성하고 필요한 변수들을 셋팅하는 과정이기 실행이나 검증 단계보다 코드가 길어질 수 있다. 준비 단계 코드가 너무 길어지고 다른 테스트와의 유사성이 있다면 중복된 부분을 private 함수를 이용해서 리팩토링하는 것도 좋은 방법이다.
- 실행(act) : 보통 한줄의 코드이다. 실행 구절이 두줄 이상이라면 잘못 설계된 API일 가능성이 높다.
- 검증(assert) : 유닛 테스트란 단일 동작을 검증하는 단위테스트다. 하지만 단일 동작으로 여러 결과를 낼 수 있기 때문에 코드가 길어 질 수 있다.
그런데 substring() 함수를 사용하다가 문자열의 길이보다 긴 값을 substring의 인자로 전달하게 되면 아래 예제와 같이 StringIndexOutOfBoundsException 에러가 발생하게 된다. 총 글자수는 12개이지만 10부터 20을 출력하라고 했기때문에 에러가 발생하였다.
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Range [10, 20) out of bounds for length 12 at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:55) at java.base/jdk.internal.util.Preconditions$1.apply(Preconditions.java:52) at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:213) at java.base/jdk.internal.util.Preconditions$4.apply(Preconditions.java:210) at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:98) at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:112) at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:349) at java.base/java.lang.String.checkBoundsBeginEnd(String.java:4589) at java.base/java.lang.String.substring(String.java:2703) at TestSubString.main(TestSubString.java:5)
이것을 방지하기 위해서는 아래예제와 같이 StringIndexOutOfBoundsException 에러를 예외처리해주면 된다.
프로그래밍을 하다보면 특히 게임쪽에서 랜덤(random) 또는 난수 를 생성할 일이 있다. 난수는 무작위 숫자를 뜻하지만 컴퓨터 쪽에선 100% 무작위(랜덤)은 아니다. 정해진 난수표에서 숫자를 가저오는 방식이기 때문이다.
자바(JAVA) 에서 난수를 생성하는 방식은 크게 두가지 이다.
첫번째로 Math.random() 이용하는 방식이다. Math Class의 random 함수는 double 형 타입으로 0.0 이상 1.0 미만 사이의 값을 반환한다.
public class TestRandom {
public static void main(String[] args) {
for(int i =0; i < 10; i++) {
System.out.print(Math.random()+" ");
}
System.out.println("");
}
}
보통은 자연수의 난수가 필요한 경우가 많기 때문에 아래 예제 처럼 0부터 9까지의 난수가 필요하다면 x10을 한 이후에 int형으로 강제 형변환을 시켜주면 된다.
public class TestRandom {
public static void main(String[] args) {
for(int i =0; i < 10; i++) {
System.out.print((int)(Math.random()*10) + " ");
}
System.out.println("");
}
}
* 결과 값 :
2 6 3 7 6 9 8 7 9 7
하지만 이 방식은 직관적이지 않고 형변환등을 하면서 익셉션(Exception) 처리등을 해주어야 하기 때문에 두번째 방식을 추천한다.
두번째로 Random Class를 이용하는 방식이다. 난수를 생성하는데 필요한 기능 등을 묶어놓은 클레스 이다. Random Class를 사용하려면 import java.util.Random; 를 해주어야 한다. Random Class의 맴버변수중에 setSeed를 이용하거나 생성자에 매개변수로 시스템시간을 매개로 전달하면 난수표의 시드값을 초기화 할 수 있다. 아래는 0부터 9까지의 정수를 출력하는 예제이다.
import java.util.Random;
public class TestRandom {
public static void main(String[] args) {
Random random = new Random(System.nanoTime());
for(int i =0; i < 10; i++) {
System.out.print((int)(random.nextInt(10)) + " ");
}
System.out.println("");
}
}
* 결과 값 :
1 3 3 7 6 9 5 7 2 4
아래는 1부터 45까지의 숫자를 출력하는 로또 번호 추첨 예제이다. 랜덤으로 숫자를 추첨하는데 기존에 나왔던 숫자가 있을 경우 다시 재추첨하도록 하였다.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TestRandom {
public static void main(String[] args) {
Random random = new Random(System.nanoTime());
List<Integer> result = new ArrayList<Integer>();
int num;
for (int i = 0; i < 6; i++) {
while (true) {
num = random.nextInt(45) + 1;
if (result.contains(num)) {
continue;
} else {
result.add(num);
System.out.print(num+" ");
break;
}
}
}
System.out.println("");
}
}
* 결과 값 :
12 38 33 29 4 17
아래 테이블은 다양한 Random class의 멤버함수 들이다. 이외에도 다양한 함수들이 많이 존재하니 라이브러리를 참고하도록 하자.