랜덤 요소가 들어간 로직의 테스트 코드는 어떻게 짜야할까요?

2013-08-26 16:21

안녕하세요 이번에 베틀쉽이라는 게임을 짜고 있었는데 2차원 배열로 되어있는 맵에 크기가 각기 다른 배를 배치를 해야 했었습니다.

먼저 수동으로 각 배마다 좌표를 지정해서 배치를 하는 코드를 구현을 하고 이에 맞게 테스트 코드로 배열안에 잘 들어가 있는지를 확인했습니다.

하지만 다음 단계로 맵에 랜덤으로 배가 겹치지 않고 여러 방향으로 배치를 하는 코드를 구현하게 되었는데 맵에 배가 잘 배치되었는지 테스트 코드를 짜야하는데 어떻게 짜야할지 곤란해하고 있습니다.

그냥 맵인 2차원 배열안에 데이터가 몇개가 들어갔는지 갯수만 체크하고 sysout에 의존해서 배열을 다 프린트해서 수동으로 검사를 하고 있습니다.

지금은 규모도 작고 복잡한 것이 아니어서 몇번 돌려보고 통과했는데 앞으로 이런 상황이 발생을 할 때에는 어떻게 해야 할지 잘 모르겠습니다.

혹시 현업에서 사용하시는 노하우가 있으시면 공유좀 부탁드리겠습니다.ㅠㅠ

BEST 의견 원본위치로↓
2013-08-27 00:11

"랜덤 요소가 들어간 로직의 테스트 코드는 어떻게 짜야할까?"를 생각하면 막연합니다. 범위를 줄여서 생각해보면 좋겠습니다. 내가 테스트하고 싶은 코드는 무엇인가? 어떤 기능이 잘 동작하는지 확인하고 싶은가? 어떤 기능이 잘 동작하지 않을 것 같은가?

"하지만 다음 단계로 맵에 랜덤으로 배가 겹치지 않고 여러 방향으로 배치를 하는 코드를 구현하게 되었는데 맵에 배가 잘 배치되었는지 테스트 코드를 짜야하는데 어떻게 짜야할지 곤란해하고 있습니다."

위를 읽고 제가 이해하기로는 "배가 잘 배치되었는지 알고 싶다"입니다. 그럼 배가 잘 배치되었는지 확인하는 테스트가 필요하겠죠. 우선 S*S 판에 N개의 배를 모두 랜덤으로 배치하고 판에 N개의 배가 존재하는지 검사할 수 있겠죠? 이렇게 한 번 테스트를 하면 안심이 될까요? 우연히 잘 배치가 되어서 판에 N개의 배가 있을 수 있습니다. 하지만 실제로는 버그가 있어서 배를 겹치에 놓아서 N개가 모자랄 수도 있습니다. 아주 많이 실행하면 좀 더 안심이 되겠지죠 :)

위 테스트로는 안심이 되지 않습니다. 더 안심할 수 있는 방법은 무엇일까요? 배를 하나 배치할 때마다 다른 배와 겹치지 않는 자리에 놓고, 이를 N번 반복하면 항상 모든 배를 서로 겹치지 않는 위치에 놓을 수 있겠죠. 그럼 두 가지를 테스트하면 안심이 되겠네요.

  1. 배를 기존 배와 겹치지 않게 배치한다.
  2. 배 배치는 N번 반복되어야 한다.

2번은 굳이 테스트하지 않아도 확신할 수 있을 것 같으니 1번을 생각해보겠습니다. 1번도 두 단계로 나눌 수 있습니다.

  1. 새 배의 위치는 기존 배의 위치와 겹치지 않아야 한다.
  2. 새 배는 기존 배 목록에 추가되어야 한다.

이번에도 2번은 굳이 테스트할 필요가 없을 것 같네요. 그럼 새 배의 위치가 기존 배의 위치와 겹치지 않는지 확인하는 테스트가 필요합니다.

while (true) { x = random.nextInt(s); y = random.nextInt(s); if (!exist(x, y)) { return (x, y); } }

새로운 배 위치를 얻는 함수는 위와 비슷하겠죠. 이 함수를 어떻게 테스트하면 안심이 될까요? 이 함수의 핵심은 exist(x, y)니가 exist(x, y)가 잘 동작하면 배가 올바른 위치에 배치됩니다. exist를 테스트해야겠죠. exist의 동작에 영향을 미치는 요소는 기존 배들의 좌표, 입력 좌표 두 가지입니다. 이 두 요소를 조합하여 여러 가지 경우를 생각한 다음 테스트를 하면 될 것 같네요.

  1. 기존 배가 없고, 임의의 좌표 -> true
  2. 기존 배가 한 대 있고, 기존 배 좌표와 x는 다르고 y도 다른 좌표 -> true
  3. 기존 배가 한 대 있고, 기존 배 좌표와 x는 같고 y는 다른 좌표 -> true
  4. 기존 배가 한 대 있고, 기존 배 좌표와 x는 다르고 y는 같은 좌표 -> true
  5. 기존 배가 한 대 있고, 기존 배 좌표와 x, y모두 같은 좌표 -> false
  6. 기존 배가 두 대 있고, 어떤 배와도 일치하지 않는 좌표 -> true
  7. 기존 배가 두 대 있고, 첫 번째 배와 일치하는 좌표 -> false
  8. 기존 배가 두 대 있고, 두 번째 배와 일치하는 좌표 -> false

이 정도면 N개의 배가 서로 겹치지 않고 배치된다고 안심할 수 있겠죠?

7개의 의견 from SLiPP

2013-08-26 22:33

답은 없지만 지금 까지의 아이디어는 이렇습니다.

1.테스트 로직을 작성하기 배열을 계속 반복해서 돌면서 배가 온전하게 잘 들어있는지 검사하기 (예를 들어 5칸짜리 배가 있으면 가로로 5칸이 들어갈 수 있는 곳을 전부 검사 해서 배가 있는지 검사 후 없으면 세로로도 반복) 하지만 배보다 배꼽이 더 커질수 있고 정말 간단하게 짜지 못하면 테스트 코드를 테스트하는 바보같은 상황이 벌어질수 있고...

2.통계 이용하기 지금 같은 상황에는 적용하기 힘들겠지만 만약 랜덤으로 1~10까지의 숫자를 뽑았을 때 반복을 많이 하고 원하는 결과값이 10%에 한 신뢰도 95이내에 수렴하는지를 체크

3.시드값이용하기 이것도 배에 적용하거나 아예 다른 경우에도 어떻게 사용할 지 모르겠지만 자바에서는 자동으로 타임값을 시드로 넣어주지만 따로 변수로 시드값을 보관하고 랜덤 뽑을때 지정하고 그 시드값을 테스트 코드에도 똑같이 적용하기인데 이또한 어떨때 쓰일수 있을지까지는 잘 모르겠습니다.

2013-08-26 23:30

지극히 개인 취향적으로 Mock 데이터를 이용합니다. ( 즉 결과를 동일하게. 박는다라고 생각해도 무방할듯합니다 ) 사실 랜덤값에 대한 검증이 필요하다면 그 부분에 대해서는 별도의 테스트를 만듭니다. 아니면 랜덤데이터를 랜덤하게 만들어내는 경우도 있긴한데.. 사실상 Mock데이터를 이용한 경우가 대부분인듯합니다.

2013-08-27 00:11

"랜덤 요소가 들어간 로직의 테스트 코드는 어떻게 짜야할까?"를 생각하면 막연합니다. 범위를 줄여서 생각해보면 좋겠습니다. 내가 테스트하고 싶은 코드는 무엇인가? 어떤 기능이 잘 동작하는지 확인하고 싶은가? 어떤 기능이 잘 동작하지 않을 것 같은가?

"하지만 다음 단계로 맵에 랜덤으로 배가 겹치지 않고 여러 방향으로 배치를 하는 코드를 구현하게 되었는데 맵에 배가 잘 배치되었는지 테스트 코드를 짜야하는데 어떻게 짜야할지 곤란해하고 있습니다."

위를 읽고 제가 이해하기로는 "배가 잘 배치되었는지 알고 싶다"입니다. 그럼 배가 잘 배치되었는지 확인하는 테스트가 필요하겠죠. 우선 S*S 판에 N개의 배를 모두 랜덤으로 배치하고 판에 N개의 배가 존재하는지 검사할 수 있겠죠? 이렇게 한 번 테스트를 하면 안심이 될까요? 우연히 잘 배치가 되어서 판에 N개의 배가 있을 수 있습니다. 하지만 실제로는 버그가 있어서 배를 겹치에 놓아서 N개가 모자랄 수도 있습니다. 아주 많이 실행하면 좀 더 안심이 되겠지죠 :)

위 테스트로는 안심이 되지 않습니다. 더 안심할 수 있는 방법은 무엇일까요? 배를 하나 배치할 때마다 다른 배와 겹치지 않는 자리에 놓고, 이를 N번 반복하면 항상 모든 배를 서로 겹치지 않는 위치에 놓을 수 있겠죠. 그럼 두 가지를 테스트하면 안심이 되겠네요.

  1. 배를 기존 배와 겹치지 않게 배치한다.
  2. 배 배치는 N번 반복되어야 한다.

2번은 굳이 테스트하지 않아도 확신할 수 있을 것 같으니 1번을 생각해보겠습니다. 1번도 두 단계로 나눌 수 있습니다.

  1. 새 배의 위치는 기존 배의 위치와 겹치지 않아야 한다.
  2. 새 배는 기존 배 목록에 추가되어야 한다.

이번에도 2번은 굳이 테스트할 필요가 없을 것 같네요. 그럼 새 배의 위치가 기존 배의 위치와 겹치지 않는지 확인하는 테스트가 필요합니다.

while (true) { x = random.nextInt(s); y = random.nextInt(s); if (!exist(x, y)) { return (x, y); } }

새로운 배 위치를 얻는 함수는 위와 비슷하겠죠. 이 함수를 어떻게 테스트하면 안심이 될까요? 이 함수의 핵심은 exist(x, y)니가 exist(x, y)가 잘 동작하면 배가 올바른 위치에 배치됩니다. exist를 테스트해야겠죠. exist의 동작에 영향을 미치는 요소는 기존 배들의 좌표, 입력 좌표 두 가지입니다. 이 두 요소를 조합하여 여러 가지 경우를 생각한 다음 테스트를 하면 될 것 같네요.

  1. 기존 배가 없고, 임의의 좌표 -> true
  2. 기존 배가 한 대 있고, 기존 배 좌표와 x는 다르고 y도 다른 좌표 -> true
  3. 기존 배가 한 대 있고, 기존 배 좌표와 x는 같고 y는 다른 좌표 -> true
  4. 기존 배가 한 대 있고, 기존 배 좌표와 x는 다르고 y는 같은 좌표 -> true
  5. 기존 배가 한 대 있고, 기존 배 좌표와 x, y모두 같은 좌표 -> false
  6. 기존 배가 두 대 있고, 어떤 배와도 일치하지 않는 좌표 -> true
  7. 기존 배가 두 대 있고, 첫 번째 배와 일치하는 좌표 -> false
  8. 기존 배가 두 대 있고, 두 번째 배와 일치하는 좌표 -> false

이 정도면 N개의 배가 서로 겹치지 않고 배치된다고 안심할 수 있겠죠?

2013-08-28 09:24

changhwa.oh처럼 임의로 구성한 값으로 테스트하는 방법이 간편할 것 같습니다. 실제 랜덤 값 출력에 대해서는 별도의 테스트를 거치면 되겠죠.

(x,y) 위치에 겹치지 않게 n개를 놓는 방법에 대한 팁입니다. (물론 1차원 배열도 마찬가지입니다.) 그냥 n개를 순서대로 2차원 배열에 나열합니다. 나열된 부분의 (x,y) 값들을 랜덤값(x1,y1) 위치와 여러 번 반복하여 교환하면 랜덤하게 배치됩니다.

이 방법은 제가 카드를 랜덤하게 섞을 때 1차원 배열에서 사용한 것인데 단순한 방법이지만 아주 효율적입니다.

O_O

2013-08-30 18:05

저 같으면 이런식으로 짤거 같아요.

@Test public void 사물체크() {
    assertNull(world.at(Pos.zero));
    world.add(new Ship(Pos.zero));
    assertNotNull(world.at(Pos.zero));
}


@Test(expected = DuplicatedPositionException.class) public void 중복불가() {
    world.add(new Ship(Pos.zero));
    world.add(new Ship(Pos.zero));
}


@Test public void 자동배치() {
    world.addShipsRandomly(10);
    assertEquals(10, world.countShips());
}


class World {
    public void addShipsRandomly(int count) {
        for (int i = 0; i < count; i++) 
            add(new Ship(emptyPos())
    }
    public Pos emptyPos() {
        while (true) {
            Pos pos = new Pos(random.nextInt(MAX_X), random.nextInt(MAX_Y));
            if (null == at(pos)) return pos;
        }
    }
    public void add(Ship s) {
        if (null != at(s.pos)) throw new DuplicatedPositionException();
        ...
    }
}
2013-08-30 18:17

좌표가 아주 많지 않다면 무식하게 하는 방법도 있죠.

class World {
    List<Position> emptyPositionsRandomly(int count) {
        for (int x = 0; x < world.maxX; x++) 
            for (int y = 0; y < world.maxY; y++) {
                Position p = new Position(x, y);
                if (null == at(p)) positions.add(p);
            }
        Collections.shuffle(positions);
        return positions.subList(0, 10);
    }
}

이제 2,2 크기의 맵으로 3개의 랜덤 좌표를 뽑아낸 다음에 중복좌표가 있는지 100번 정도 루프돌려보면 될 것 같은데 ...

의견 추가하기

연관태그

← 목록으로