[UnitTest] Testowanie klasy, która jest zależna od innej.

Odpowiedz Nowy wątek
2019-09-09 11:48
0

Uczę się pisać testy jednostkowe i napotkałem na problem. Mam dwie klasy pierwsza reprezentuje mapę gry, którą przekazuję do drugiej klasy która sprawdza czy nastąpiła wygrana. Chciałem przetestować jednostkowo klasę WinAlgorithm ale nie wiem jak się za to zabrać przez to, że ta klasa jest zależna od Board. Czy mógłbym prosić o wskazówki jak to ugryźć albo jeden test, który pokaże mi jak się coś takiego testuje?

public class Board
{
    private const int DefaultSize = 3;

    public Cell[,] Cells { get; }
    public int BoardSize { get; }
    public int MoveCount { get; private set; }

    public Board(Options options)
    {
        Guard.ThrowIfNull(options, nameof(options));

        BoardSize = options.BoardSize >= DefaultSize
            ? options.BoardSize
            : DefaultSize;

        Cells = new Cell[BoardSize, BoardSize];
        CreateCells();
    }

    public Cell GetCell(int row, int col)
    {
        ThrowIfInvalid(row, col);

        return Cells[row, col];
    }

    public void SetCell(int row, int col, Player player)
    {
        ThrowIfInvalid(row, col);

        if (!IsEmpty(GetSell(row, col))
            throw new InvalidOperationException();

        Cells[row, col] = player == Player.Cross
            ? Cell.CreateCrossCell()
            : Cell.CreateCircleCell();

        MoveCount++;
    }

    public bool IsEmpty(Cell cell)
    {
        return cell.CellType == CellType.Empty;
    }

    // other methods
}
public class WinAlgorithm
{
    private readonly Board _board;

    public WinAlgorithm(Board board)
    {
        Guard.ThrowIfNull(board, nameof(board));

        _board = board;
    }

    public bool IsRowWinner(int row)
    {
        var cell = _board.GetCell(row, 0);

        if (_board.IsEmpty(cell))
            return false;

        for (var i = 1; i < _board.BoardSize; i++)
            if (_board.GetCell(row, i) != cell)
                return false;

        return true;
    }

    public bool IsColWinner(int col)
    {
        var cell = _board.GetCell(0, col);

        if (_board.IsEmpty(cell))
            return false;

        for (var i = 1; i < _board.BoardSize; i++)
            if (_board.GetCell(i, col) != cell)
                return false;

        return true;
    }

    // other methods
}

Pozostało 580 znaków

2019-09-09 13:19
1

Jeśli klasa jest zależna od Board, to pewnie najwygodniej zmockować ten Board. Tu http://www.altcontroldelete.p[...]-w-praktyce-z-biblioteka-moq/ masz przykład . Ale wydaje mi się, żeby mock mógł zwracać jakieś fejkowe dane to jego metody muszą być virtualne lub musisz zmockować interfejs, który będzie implementowany przez Board.

Pozostało 580 znaków

2019-09-09 13:29
0

Teraz pytanie czym jest test jednostkowy

Tak, tutaj wiele osob bedzie sie niezgadzac ze mna. Ale opisze obie sytuacje

  • Testujesz jedynie klase WinAlgorithm a dokladniej konkretne tej klasy metody. Mockujesz wszystkie zaleznosci (polecam darmowy Moq do tego). W takim przypadku potrzebujesz miec interfejs do IBoard (nie ze potrzebujesz, ale darmowy moq nie potrafi zamockowac nieinterfejsu). Przygotowywujesz mock tak by robil to co chcesz i sprawdzasz swoj wynik. Dla mnie to troche testowanie kodu, ktory sie napisalo, a nie jednostki

  • Testujesz flow. WinAlgorithm nie moze dzialac bez board. Jezeli board bedzie mial jakis blad, to nie bedzie mialo znaczenia, ze ladnie zmockowales. Wiec tworzysz Board i tworzysz WinAlgorithm wszystkie zaleznosci ktore wychodza po za "Twoj kod" (baza danych, http call) mockujesz by zwracaly dane takie jak chcesz. Dzieki temu testujesz rzeczywiscie czy ta funkcja dziala jak powinna a nie sam kod

Zalezy co wolisz. Jak sa trudne algorytmy to pisze w pierwszy sposob, bo mnie interesuje tylko i wylacznie ta metoda, jezeli chce przetestowac czy moj feature dziala pisze testy w sposob drugi

@fasadin: Ja się z tobą zgadzam, z tym, że interfejsu raczej nie potrzebuje a wystarczy oznaczyć metody virtual - szydlak 2019-09-09 13:35
nie dla Moq przynajmniej pamietam ze mialem podobny problem - fasadin 2019-09-09 13:36
no ja też i miałem komunikat, że metody nie są virtualne, https://stackoverflow.com/que[...]-a-class-without-an-interface - szydlak 2019-09-09 13:38
Losowe metody jako virtual. Tak programują prawdziwi Hindusi. - somekind 2019-09-09 16:24

Pozostało 580 znaków

2019-09-09 14:59
0

Na razie wyodrębniłem interfejs z klasy Board i zrobiłem mock tak jak pisaliście. Napisałem dwa testy jeden testujący sam algorytm a drugi chyba flow. Mniej więcej tak ma to wyglądać? Tylko teraz nasuwają mi się wątpliwości czy powinienem testować metodę UpdateGameStatus jeśli algorytm działa poprawnie to, może SetWinState ustawić jako public i tylko to przetestować? Pisanie testów jest trudniejsze niż nauka programowania, nie wiem co mam testować... Czy ten drugi test nie podchodzi pod testy integracyjne?

public class Game
{
    // row and col are coordinates of last move
    public void UpdateGameState(int row, int col)
    {
        // check row
        if (_winAlgorithm.IsRowWinner(row))
            SetWinState();

        // check col
        else if (_winAlgorithm.IsColWinner(col))
            SetWinState();

        // check diagonal only if cell is on its
        else if (Board.IsCellOnDiagonal(row, col) && _winAlgorithm.IsDiagonalWinner())
            SetWinState();

        // check anti diagonal if cell is on its
        else if (Board.IsCellOnAntiDiagonal(row, col) && _winAlgorithm.IsAntiDiagonalWinner())
            SetWinState();

        // check for tie
        else if (_winAlgorithm.IsTie())
            State = State.Tie;
    }

    private void SetWinState()
    {
        State = State.Win;
        Winner = CurrentPlayer;
    }
}
[Fact]
public void IsRowWinner_WhenInRowIsEmptyCell_ReturnFalse()
{
    // arrange
    var board = Mock.Of<IBoard>(x =>
        x.GetCell(0, 0) == Cell.CreateCrossCell() &&
        x.GetCell(0, 1) == Cell.CreateCircleCell() &&
        x.GetCell(0, 2) == Cell.CreateEmptyCell() &&
        x.IsEmpty(It.Is<Cell>(c => c.CellType == CellType.Empty)) == true &&
        x.IsEmpty(It.Is<Cell>(c => c.CellType != CellType.Empty)) == false &&
        x.BoardSize == 3);

    var algorithm = new WinAlgorithm(board);

    // act
    var result = algorithm.IsRowWinner(0);

    // assert
    result.Should().BeFalse();
}
[Fact]
public void UpdateGameStatus_WhenIsWinner_ChangeStatus()
{
    // arrange
    var board = new Board(new Options { BoardSize = 3 });
    var winAlgorithm = new WinAlgorithm(board);
    var game = new Game(board, winAlgorithm, Player.Cross);

    game.Move(0, 0);
    game.Move(0, 1);
    game.Move(0, 2);

    // act
    game.UpdateGameState(0, 2);

    // assert
    game.State.Should().Be(State.Win);
    game.Winner.Should().Be(Player.Cross);
}

Pozostało 580 znaków

2019-09-09 16:28
6

Po co mockować coś tak trywialnego jak Board? To jest tylko struktura danych, bez żadnej logiki, nie korzysta z żadnych zewnętrznych zasobów, mockując niczego nie zyskujemy.

Jaki to problem utworzyć obiekt Board w testach i przekazać go do WinAlgorithm? Przecież to będzie nawet mniej kodu niz tym mockiem.
To trochę tak jakby próbować mockować int na potrzeby testowania dodawania.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2019-09-09 16:59
0

@somekind czy miałeś na myśli coś takiego?

Mam jeszcze pytanie czy powinienem testować algorytm dla macierzy o rozmiarze większym niż [3, 3] np. [5, 5]?

[Fact]
public void IsRowWinner_WhenInRowIsEmptyCell_ReturnFalse()
{
    // arrange
    var board = new Board(new Options { BoardSize = 3 });
    var algorithm = new WinAlgorithm(board);

    // act
    var result = algorithm.IsRowWinner(0);

    // assert
    result.Should().BeFalse();
}

[Theory]
[InlineData(Player.Cross)]
[InlineData(Player.Circle)]
public void IsRowWinner_WhenCellsInRowAreEquals_ReturnTrue(Player player)
{
    // arrange
    var board = new Board(new Options { BoardSize = 3 });
    var algorithm = new WinAlgorithm(board);

    board.SetCell(0, 0, player);
    board.SetCell(0, 1, player);
    board.SetCell(0, 2, player);

    // act
    var result = algorithm.IsRowWinner(0);

    // assert
    result.Should().BeTrue();
}

Pozostało 580 znaków

2019-09-09 20:15
0

No na przykład (chociaż ja bym to inaczej zapisał, jak dla mnie Board powinien mieć porządny konstruktor, a nie publiczne Options czy SetCell).


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0