4

我正在测试这段代码——它是一个名为 MazeBuilder 的类。我的问题是大多数方法都受到保护,所以我无法在测试中访问它们......

所以我的想法是测试应该只关注 Run(),所以它可以访问很多其他方法。但我担心仅通过一种方法就不可能完成任何类型的内聚测试。

此外,测试 2 个构造函数( MazeBuilder() 和 MazeBuilder(boolean deterministic) )的正确方法是什么?就目前而言,我只是在测试形成的对象是否不为空 - 即它们是否正在构建。有没有更广泛的方法来测试我不知道的构造函数?

package falstad;

public class MazeBuilder implements Runnable {
    // Given input information: 
    protected int width, height ;   // width and height of maze, 
    Maze maze;              // reference to the maze that is constructed, results are returned by calling maze.newMaze(..)
    private int rooms;      // requested number of rooms in maze, a room is an area with no walls larger than a single cell
    int expectedPartiters;  // user given limit for partiters

    // Produced output information to create the new maze
    // root, cells, dists, startx, starty
    protected int startx, starty ; // starting position inside maze for entity to search for exit
    // conventional encoding of maze as a 2 dimensional integer array encapsulated in the Cells class
    // a single integer entry can hold information on walls, borders/bounds
    protected Cells cells; // the internal representation of a maze as a matrix of cells
    protected Distance dists ; // distance matrix that stores how far each position is away from the exit positino

    // class internal local variables
    protected SingleRandom random ; // random number stream, used to make randomized decisions, e.g for direction to go

    Thread buildThread; // computations are performed in own separated thread with this.run()

    //int colchange; // randomly selected in run method of this thread, used as parameter to Segment class constructor

    /**
     * Constructor for a randomized maze generation
     */
    public MazeBuilder(){
        random = SingleRandom.getRandom();
    }
    /**
     * Constructor with option to make maze generation deterministic or random
     */
    public MazeBuilder(boolean deterministic){
        if (true == deterministic)
        {
            System.out.println("Project 2: functionality to make maze generation deterministic not implemented yet! Fix this!");
            // Control random number generation
            // TODO: implement code that makes sure that if MazeBuilder.build is called for same skill level twice, same results
            // HINT: check http://download.oracle.com/javase/6/docs/api/java/util/Random.html and file SingleRandom.java

        }
        random = SingleRandom.getRandom();
    }

    /**
     * Provides the sign of a given integer number
     * @param num
     * @return -1 if num < 0, 0 if num == 0, 1 if num > 0
     */
    static int getSign(int num) {
        return (num < 0) ? -1 : (num > 0) ? 1 : 0;
    }

    /**
     * This method generates a maze.
     * It computes distances, determines a start and exit position that are as far apart as possible. 
     */
    protected void generate() {
        // generate paths in cells such that there is one strongly connected component
        // i.e. between any two cells in the maze there is a path to get from one to the other
        // the search algorithm starts at some random point
        generatePathways(); 

        final int[] remote = dists.computeDistances(cells) ;

        // identify cell with the greatest distance
        final int[] pos = dists.getStartPosition();
        startx = pos[0] ;
        starty = pos[1] ;

        // make exit position at true exit in the cells data structure
        setExitPosition(remote[0], remote[1]);
    }

    /**
     * This method generates pathways into the maze.
     * 
     */
    protected void generatePathways() {
        int[][] origdirs = new int[width][height] ; 

        int x = random.nextIntWithinInterval(0, width-1) ;
        int y = 0; 
        final int firstx = x ; 
        final int firsty = y ;
        int dir = 0;        
        int origdir = dir;  
        cells.setVisitedFlagToZero(x, y); 
        while (true) {      

            int dx = Constants.DIRS_X[dir];
            int dy = Constants.DIRS_Y[dir];

            if (!cells.canGo(x, y, dx, dy)) { 

                dir = (dir+1) & 3; 
                if (origdir == dir) { 

                    if (x == firstx && y == firsty)
                        break; 


                    int odr = origdirs[x][y];
                    dx = Constants.DIRS_X[odr];
                    dy = Constants.DIRS_Y[odr];

                    x -= dx;
                    y -= dy;



                    origdir = dir = random.nextIntWithinInterval(0, 3);
                }
            } else {


                cells.deleteWall(x, y, dx, dy);

                x += dx;
                y += dy;

                cells.setVisitedFlagToZero(x, y);

                origdirs[x][y] = dir;
                origdir = dir = random.nextIntWithinInterval(0, 3);
            }
        }
    }
    /**
     * Establish valid exit position by breaking down wall to outside area.
     * @param remotex
     * @param remotey
     */
    protected void setExitPosition(int remotex, int remotey) {
        int bit = 0;
        if (remotex == 0)
            bit = Constants.CW_LEFT;
        else if (remotex == width-1)
            bit = Constants.CW_RIGHT;
        else if (remotey == 0)
            bit = Constants.CW_TOP;
        else if (remotey == height-1)
            bit = Constants.CW_BOT;
        else
            dbg("Generate 1");
        cells.setBitToZero(remotex, remotey, bit);
        //System.out.println("exit position set to zero: " + remotex + " " + remotey + " " + bit + ":" + cells.hasMaskedBitsFalse(remotex, remotey, bit)
        //      + ", Corner case: " + ((0 == remotex && 0 == remotey) || (0 == remotex &&  height-1 == remotey) || (width-1 == remotex && 0 == remotey) || (width-1 == remotex && height-1 == remotey)));
    }


    static final int MIN_ROOM_DIMENSION = 3 ;
    static final int MAX_ROOM_DIMENSION = 8 ;
    /**
     * Allocates space for a room of random dimensions in the maze.
     * The position of the room is chosen randomly. The method is not sophisticated 
     * such that the attempt may fail even if the maze has ample space to accommodate 
     * a room of the chosen size. 
     * @return true if room is successfully placed, false otherwise
     */
    private boolean placeRoom() {
        // get width and height of random size that are not too large
        // if too large return as a failed attempt
        final int rw = random.nextIntWithinInterval(MIN_ROOM_DIMENSION, MAX_ROOM_DIMENSION);
        if (rw >= width-4)
            return false;

        final int rh = random.nextIntWithinInterval(MIN_ROOM_DIMENSION, MAX_ROOM_DIMENSION);
        if (rh >= height-4)
            return false;

        // proceed for a given width and height
        // obtain a random position (rx,ry) such that room is located on as a rectangle with (rx,ry) and (rxl,ryl) as corner points
        // upper bound is chosen such that width and height of room fits maze area.
        final int rx = random.nextIntWithinInterval(1, width-rw-1);
        final int ry = random.nextIntWithinInterval(1, height-rh-1);
        final int rxl = rx+rw-1;
        final int ryl = ry+rh-1;
        // check all cells in this area if they already belong to a room
        // if this is the case, return false for a failed attempt
        if (cells.areaOverlapsWithRoom(rx, ry, rxl, ryl))
            return false ;
        // since the area is available, mark it for this room and remove all walls
        // from this on it is clear that we can place the room on the maze
        cells.markAreaAsRoom(rw, rh, rx, ry, rxl, ryl); 
        return true;
    }

    static void dbg(String str) {
        System.out.println("MazeBuilder: "+str);
    }



    /**
     * Fill the given maze object with a newly computed maze according to parameter settings
     * @param mz maze to be filled
     * @param w width of requested maze
     * @param h height of requested maze
     * @param roomct number of rooms
     * @param pc number of expected partiters
     */
    public void build(Maze mz, int w, int h, int roomct, int pc) {
        init(mz, w, h, roomct, pc);
        buildThread = new Thread(this);
        buildThread.start();
    }

    /**
     * Initialize internal attributes, method is called by build() when input parameters are provided
     * @param mz maze to be filled
     * @param w width of requested maze
     * @param h height of requested maze
     * @param roomct number of rooms
     * @param pc number of expected partiters
     */
    private void init(Maze mz, int w, int h, int roomct, int pc) {
        // store parameters
        maze = mz;
        width = w;
        height = h;
        rooms = roomct;
        expectedPartiters = pc;
        // initialize data structures
        cells = new Cells(w,h) ;
        dists = new Distance(w,h) ;
        //colchange = random.nextIntWithinInterval(0, 255); // used in the constructor for Segments  class Seg
    }

    static final long SLEEP_INTERVAL = 100 ; //unit is millisecond
    /**
     * Main method to run construction of a new maze with a MazeBuilder in a thread of its own.
     * This method is called internally by the build method when it sets up and starts a new thread for this object.
     */
    public void run() {
        // try-catch block to recognize if thread is interrupted
        try {
            // create an initial invalid maze where all walls and borders are up
            cells.initialize();
            // place rooms in maze
            generateRooms();

            Thread.sleep(SLEEP_INTERVAL) ; // test if thread has been interrupted, i.e. notified to stop

            // put pathways into the maze, determine its starting and end position and calculate distances
            generate();

            Thread.sleep(SLEEP_INTERVAL) ; // test if thread has been interrupted, i.e. notified to stop

            final int colchange = random.nextIntWithinInterval(0, 255); // used in the constructor for Segments  class Seg
            final BSPBuilder b = new BSPBuilder(maze, dists, cells, width, height, colchange, expectedPartiters) ;
            BSPNode root = b.generateBSPNodes();

            Thread.sleep(SLEEP_INTERVAL) ; // test if thread has been interrupted, i.e. notified to stop

            // dbg("partiters = "+partiters);
            // communicate results back to maze object
            maze.newMaze(root, cells, dists, startx, starty);
        }
        catch (InterruptedException ex) {
            // necessary to catch exception to avoid escalation
            // exception mechanism basically used to exit method in a controlled way
            // no need to clean up internal data structures
            // dbg("Catching signal to stop") ;
        }
    }

    static final int MAX_TRIES = 250 ;

    /**
     * Generate all rooms in a given maze where initially all walls are up. Rooms are placed randomly and of random sizes
     * such that the maze can turn out to be too small to accommodate the requested number of rooms (class attribute rooms). 
     * In that case less rooms are produced.
     * @return generated number of rooms
     */
    private int generateRooms() {
        // Rooms are randomly positioned such that it may be impossible to place the all rooms if the maze is too small
        // to prevent an infinite loop we limit the number of failed to MAX_TRIES == 250
        int tries = 0 ;
        int result = 0 ;
        while (tries < MAX_TRIES && result <= rooms) {
            if (placeRoom())
                result++ ;
            else
                tries++ ;
        }
        return result ;
    }

    /**
     * Notify the maze builder thread to stop the creation of a maze and to terminate
     */
    public void interrupt() {
        buildThread.interrupt() ;
    }



}
4

2 回答 2

7

要对受保护的方法进行单元测试,只需将测试类放在与要测试的类相同的包中(在本例中falsted)。仅仅因为它们在同一个包中,并不意味着它们必须在同一个目录中(只是一个并行的测试目录层次结构)。

例如,如果您使用的是 maven,那么您的源代码将在,src/main/java/falsted而您的测试将在src/test/java/falsted. 从 Maven 的角度来看,这些是单独的目录,因此可以很容易地单独管理,而从 Java 的角度来看,它们是同一个包(因此受保护的方法是可见的)。

通过探测对象的状态来测试您的构造函数,以确保所有值都获得了它们的默认值或初始值。

于 2013-09-24T21:00:19.887 回答
0

您可以在测试方法中使用受保护的。构建项目的推荐方法是。

在 src/main/java

package falstad;

   public class MazeBuilder {}

在 src/test/java

package falstad;

   public class MazeBuilderTest {}
于 2013-09-24T21:18:47.123 回答