Game of Life 테스팅하기

브라우저 JavaScript 환경에서 실행할 수 있도록 Game of Life를 구현했으니 이제 Rust 코드에서 WebAssembly 함수를 테스팅하는 방법에 대해 알아봅시다.

tick 함수로 예상값과 일치하는 올바른 값을 불러올 수 있는지 테스팅 해보겠습니다.

우선 테스팅을 작성하기 전에 wasm_game_of_life/src/lib.rs 파일에 작성한 impl Universe 블럭 내부에 setter 함수와 getter 함수들을 조금 더 작성해 보겠습니다. set_widthset_height 함수를 작성해서 다른 사이즈의 Universe들을 만들어볼 수 있도록 해봅시다.

#![allow(unused)]
fn main() {
#[wasm_bindgen]
impl Universe { 
    // ...

    /// 세상의 너비를 설정합니다.
    ///
    /// 모든 세포를 죽은 상태로 리셋합니다.
    pub fn set_width(&mut self, width: u32) {
        self.width = width;
        self.cells = (0..width * self.height).map(|_i| Cell::Dead).collect();
    }

    /// 세상의 넓이를 설정합니다.
    ///
    /// 모든 세포를 죽은 상태로 리셋합니다.
    pub fn set_height(&mut self, height: u32) {
        self.height = height;
        self.cells = (0..self.width * height).map(|_i| Cell::Dead).collect();
    }

}
}

wasm_game_of_life/src/lib.rs 파일에 #[wasm_bindgen] 속성 없이 impl Universe 블럭을 하나 더 만들어보겠습니다. 추가로 테스팅에 사용하는 데 필요한 함수가 몇 개 있는데, 이 함수들은 JavaScript로 노출시키지 않아야 합니다. Rust로 생성한 WebAssembly 함수는 대여한 참조를 반환하지 못하는데, 이 함수들 위에 #[wasm_bindgen] 속성을 추가해 보고 어떤 에러를 확인할 수 있게 되는지 살펴봅시다.

get_cells 함수를 구현해서 Universecells 필드 값을 가져와 보겠습니다. set_cells 함수도 작성해서 주어진 행과 열에 위치한 Universe의 세포를 Alive 상태로 업데이트할 수 있도록 해보겠습니다.

#![allow(unused)]
fn main() {
impl Universe {
    /// 세상에 존재하는 모든 죽어있는 세포와 살아있는 세포를 반환합니다.
    pub fn get_cells(&self) -> &[Cell] {
        &self.cells
    }

    /// 배열로 주어진 행과 열들을 확인하고 세포들을 살아있는 상태로 업데이트합니다.
    pub fn set_cells(&mut self, cells: &[(u32, u32)]) {
        for (row, col) in cells.iter().cloned() {
            let idx = self.get_index(row, col);
            self.cells[idx] = Cell::Alive;
        }
    }

}
}

이제 wasm_game_of_life/tests/web.rs 파일에 테스팅 코드를 작성해 보도록 하겠습니다.

진행하기 전에, 이미 완성된 테스팅 코드가 있으니 한번 살펴봅시다. wasm-game-of-life 경로에서 wasm-pack test --chrome --headless 명령어를 실행하여 Rust로 생성한 WebAssembly 테스팅 코드가 잘 작동하는지 확인할 수 있습니다. --firefox, --safari, --node 옵션을 사용하여 특정 브라우저 환경에서 코드를 테스트할 수도 있습니다.

우선 wasm_game_of_life/tests/web.rs 파일에서, wasm_game_of_life 크레이트와 Universe를 익스포트 해줍시다.

#![allow(unused)]
fn main() {
extern crate wasm_game_of_life;
use wasm_game_of_life::Universe;
}

wasm_game_of_life/tests/web.rs 파일에서 spaceship 예시 패턴을 만드는 함수들을 작성해 봅시다.

tick 함수를 호출할 때 사용할 패턴과, 한 틱 이후의 결괏값을 비교할 때 사용할 예상값이 필요한데, 우선은 input_spaceship 함수에서 어떤 세포들을 Alive 상태로 생성할지 정해줍시다. expected_spaceship 함수를 테스트해 보는데, input_spaceship 호출 이후 시점 틱의 spaceship 패턴을 직접 계산해서 값을 채워보도록 하겠습니다.

#![allow(unused)]
fn main() {
#[cfg(test)]
pub fn input_spaceship() -> Universe {
    let mut universe = Universe::new();
    universe.set_width(6);
    universe.set_height(6);
    universe.set_cells(&[(1,2), (2,3), (3,1), (3,2), (3,3)]);
    universe
}

#[cfg(test)]
pub fn expected_spaceship() -> Universe {
    let mut universe = Universe::new();
    universe.set_width(6);
    universe.set_height(6);
    universe.set_cells(&[(2,1), (2,3), (3,2), (3,3), (4,2)]);
    universe
}
}

마지막으로 test_tick 함수를 구현해 주도록 하겠습니다. 먼저 input_spaceship()expected_spaceship()를 호출해서 인스턴스들을 만들어줍시다. 그다음에 input_universe 인스턴스의 tick 함수를 호출해 주도록 합시다. 추가로, assert_eq! 매크로를 사용해서 get_cells()를 호출한 다음 input_universeexpected_universe 가 동일한 Cell 타입의 배열을 값으로 가지고 있는지 확인해 보겠습니다. 마무리로는 이 코드 블럭 위에 #[wasm_bindgen_test] 속성을 추가해서 wasm-pack test 명령어로 Rust로 생성한 WebAssembly 코드를 테스트할 수 있도록 해줍시다.

#![allow(unused)]
fn main() {
#[wasm_bindgen_test]
pub fn test_tick() {
    // 작은 사이즈의 Universe과 spaceship 패턴을 생성해 봅시다.
    let mut input_universe = input_spaceship();

    // `input_universe` 인스턴스의 다음 틱 결괏값과 비교하게 될 spaceship 패턴입니다.
    let expected_universe = expected_spaceship();

    // `tick` 함수를 실행하고 두 `Universe`의 세포들이 동일한지 확인합니다.
    input_universe.tick();
    assert_eq!(&input_universe.get_cells(), &expected_universe.get_cells());
}
}

이제 wasm-game-of-life 경로에서 wasm-pack test --firefox --headless 명령어를 실행하여 테스팅 코드를 실행해 주면 됩니다.