【问题标题】:Testing mediator design pattern with Jest使用 Jest 测试中介设计模式
【发布时间】:2023-02-09 01:36:07
【问题描述】:

我目前正在为我的一项Odin Project 作业开发一款基于网络的战舰游戏。在某些时候,我觉得中介者模式是处理播放器和 CPU 发射导弹的完美选择。现在,我的任务鼓励我在没有 console.log 但使用 Jest 的情况下彻底测试游戏。我已经能够测试游戏的某些功能,但调解器模式令人困惑。模拟功能或模块可能是正确的方向,但老实说,我已经阅读了大量指南,但我无法实现它们(理解模拟也很困难)。 notifyAttackinside EventManager 函数已经用console.log 以旧方式进行了测试。

谁能让我知道我做错了什么?

事件管理器

export {EventManager}

const EventManager = {
  gameManager: GameManager,
  notifyAttack(who, coordinate){
    if(!who)
      throw new Error(`Unknown player`);
    else
      who === `CPU` ? GameManager.player.board.getAttack(coordinate) : GameManager.cpu.board.getAttack(coordinate);

    GameManager.turn = who;
  }
}

游戏经理

import {Player} from "./player";
export {GameManager}

const GameManager = {
  turn: undefined,
  player: undefined,
  cpu: Player(),
}

播放器

import {coordinate, GameBoard} from './gameboard';
import { EventManager } from './eventmanager';
export {Player}

const playerActions = {
  eventManager: EventManager,
  fire(coordinate){
    this.eventManager.notifyAttack(this.name, coordinate);
  }
}

function Player(name){
  const player = Object.create(playerActions);
  player.board = GameBoard();
  name === undefined ? player.name = `CPU`: player.name = name;
  return player;
}

游戏板

import {  Ship } from "./ship"
export {GameBoard, coordinate, shipOrientation, tile}

function coordinate(x,y){
  const boardSize = 10;
  if(x > boardSize || x < 1)
    throw new Error(`X coordinate is out of boundaries`);
  if(y > boardSize || y < 1)
    throw new Error(`Y coordinate is out of boundaries`);
  return{x:x, y:y}
}

function tile(coordinate, id){
  return{coordinate: coordinate, id: id}
}

const shipOrientation = {
  HORIZONTAL: Symbol(`horizontal`),
  VERTICAL: Symbol(`vertical`),
}

const gameboardActions = {
  placeShips(shipType, orientation, inputCoordinate){
    const ship = Ship(shipType);
    ship.ID = `${inputCoordinate.x},${inputCoordinate.y}`;
  
    this.tiles.forEach(tile=>{
      if(tile.coordinate.x === inputCoordinate.x && tile.coordinate.y === inputCoordinate.y)
      throw new Error(`There's already an object on that input coordinate`);
    })

    if(orientation === shipOrientation.HORIZONTAL){
      if(inputCoordinate.x + ship.length > this.size)
        throw new Error(`Part of ship is out of board X boundary`);
      for(let i = 0; i<ship.length; ++i)
        this.tiles.push(tile(coordinate(inputCoordinate.x+i, inputCoordinate.y), `${ship.ID}`));
    }else if(orientation === shipOrientation.VERTICAL){
      if(inputCoordinate.y + ship.length > this.size)
        throw new Error(`Part of ship is out of board Y boundary`);
      for(let i = 0; i<ship.length; ++i)
        this.tiles.push(tile(coordinate(inputCoordinate.x, inputCoordinate.y+i), `${ship.ID}`));
    }else
      throw new Error(`Undefined ship orientation`);

    this.shipsLog.set(`${ship.ID}`,ship);
  },

  getAttack(inputCoordinate){
    let isShip, ID;
    this.tiles.forEach(tile=>{
      if(tile.coordinate.y===inputCoordinate.y&&tile.coordinate.x===inputCoordinate.x&&tile.id){
        ID = tile.id;
        return isShip = true;
      }
    })

    if(isShip){
      this.shipsLog.get(ID).hit()
      if(this.shipsLog.get(ID).isSunk){
        this.removeShip(ID);
        this.checkSunkFleet();
      }
    }else
      this.tiles.push(tile(inputCoordinate, undefined));
  },

  removeShip(ID){
    this.shipsLog.delete(ID);
    for(let i = 0; i<this.tiles.length; ++i)
      if(this.tiles[i].id===ID)
        this.tiles.splice(i,1);
  },

  checkSunkFleet(){
    this.shipsLog.size === 0 ? this.sunkFleet=true:this.sunkFleet=false;
  }

}


function GameBoard (){
  const gameboard = Object.create(gameboardActions);
  gameboard.shipsLog = new Map();
  gameboard.tiles= []; 
  gameboard.size= 10;
  gameboard.sunkFleet = false;

    return gameboard;
}

笑话测试

import {GameBoard, coordinate} from "./gameboard";
import {GameManager} from './gamemanager';
import {Player} from "./player";
import {EventManager} from "./eventmanager";

GameManager.player = Player(`Pablo`);

describe(`Player set up`, ()=>{
  test(`Player's name is Pablo`,()=>{
    expect(GameManager.player.name).toMatch(/^[A-Z]+$/i); 
  });
  test(`Player has a board to play with`, ()=>{
    expect(GameManager.player.board).toMatchObject(GameBoard());
  });
})

describe(`Player's actions`,()=>{
  test(`Pablo fires a missile, he misses ship target though`, ()=>{
    const myCoordinate = coordinate(5,5);
    const spy = jest.spyOn(EventManager, 'notifyAttack')
    GameManager.player.fire(myCoordinate);
    expect(spy).toBeCalled();
    expect(GameManager.cpu.getAttack).toBeCalledWith(myCoordinate);
    expect(GameManager.cpu.shipsLog.has(`${myCoordinate.x}, ${myCoordinate.y}`));
  })
})

所以流程是这样的:

  1. 已经在GameManager (Pablo) 中设置的玩家通过触发玩家对象内的fire() 发射导弹
  2. fire() 报告EventManager 谁发射了导弹及其坐标
  3. EventManager调用CPUGameBoardgetAttack()方法记录Pablo丢失的导弹

    你们可能想知道为什么我使用 EventManager 而不是依赖 GameManager。基本上,GameManager负责改变回合,设置游戏,而EventManager专门处理战斗以防止PlayerCPU之间的耦合

    如果您需要有关该问题的更多详细信息,我希望收到您的来信。

    干杯!

【问题讨论】:

    标签: javascript testing design-patterns jestjs mediator


    【解决方案1】:

    好吧,经过一番努力,我找到了答案。在这种情况下,模拟不是最好的主意,相反,开玩笑的间谍让我检查函数是否被调用而不像模拟那样改变实现,所以我离答案不远了。

    给你开玩笑的测试代码以供将来参考

    describe(`Player's attack`,()=>{
    
      GameManager.cpu = Player();
      GameManager.cpu.board.placeShips(shipType.DESTROYER, shipOrientation.HORIZONTAL, coordinate(4,4))
      let myCoordinate = coordinate(5,5);
      const eventManager = jest.spyOn(EventManager, 'notifyAttack');
      const cpuGetsAttack = jest.spyOn(GameManager.cpu.board, 'getAttack')
    
      test(`Pablo fires a missile, he misses his target ship  though`, ()=>{
        GameManager.player.fire(myCoordinate);
        expect(eventManager).toBeCalled();
        expect(cpuGetsAttack).toBeCalledWith(myCoordinate);
        expect(GameManager.cpu.board.shipsLog.has(`${myCoordinate.x},${myCoordinate.y}`)).toBeFalsy();
        expect(GameManager.cpu.board.tiles).toContainEqual(tile(myCoordinate, undefined));
      });
    
      test(`Pablo fires a missile and hits his target ship`, ()=>{
        myCoordinate.x = 4;
        myCoordinate.y = 4;
    
        GameManager.player.fire(myCoordinate);
        expect(eventManager).toBeCalled();
        expect(cpuGetsAttack).toBeCalledWith(myCoordinate);
        expect(GameManager.cpu.board.shipsLog.has(`${myCoordinate.x},${myCoordinate.y}`)).toBeTruthy();
        expect(GameManager.cpu.board.tiles).toContainEqual(tile(myCoordinate, GameManager.cpu.id));
      });
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-21
      • 2015-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-12
      • 1970-01-01
      相关资源
      最近更新 更多