“Привет Мир” - TDD путь?

Оказывается, это не должно быть слишком плохо для грубой силы, по крайней мере, если ваша доска не слишком большая.

Вы можете определить свой набор бомб, где каждая ячейка может быть Present, Not Present или Unexplored, где Unexplored означает, что там может быть или не быть бомба. Затем вы можете написать метод, который берет вашу доску и этот набор бомб, и определяет, является ли доска недействительной или может быть действительной в зависимости от фактического значения любых Unexplored ячеек.

Тогда вы начинаете ходить по доске. Установите в первой ячейке значение Present и посмотрите, не приведет ли это к доске, которая может быть действительной (или определенно недействительной). Если это возможно, установите recurse на следующую ячейку. Затем установите для первой ячейки значение NotPresent и посмотрите, может ли это быть действительным, и рекурсивна ли она в следующую ячейку.

Эта обрезка досок, которые имеют небольшую площадь, которая является недействительной, должна значительно сократить пространство поиска по сравнению с полной грубой силой.

Когда вы проверяете, может ли доска быть действительной, вы можете оптимизировать ее, проверяя только ячейки в квадрате вокруг ячейки, которую вы изменили, поскольку это единственные ячейки, на которые можно повлиять.

Это не полноценное динамическое программирование, и, вероятно, выиграет от некоторого запоминания: если в правом нижнем углу есть комбинация бомб, которая недопустима (нижний правый является последней исследованной областью), она продолжит пробовать их снова и снова с различными (действительными) комбинациями бомб в других местах.

Это также застрянет, если у вашей доски большая открытая область, так как будет большое количество комбинаций, и это тщательно изучит каждую из них.

Я собрал немного C #, чтобы проиллюстрировать мою идею. Это не опрятно или не совсем ясно (и за это я прошу прощения - у меня не хватило времени, чтобы привести его в порядок), но он находит 4 решения для вашего второго примера.

Это написано с использованием рекурсии, поэтому взорвется стек с большими досками. Перепишите его, чтобы оно было итеративным.

class Program
{
    static void Main(string[] args)
    {
        var board = new Board(new CellState[,]
        {
            { CellState.Odd, CellState.None },
            { CellState.Odd, CellState.None },
            { CellState.Odd, CellState.Even }
        });

        var bombs = new BombState[board.Height, board.Width];
        int numSolutions = 0;

        Explore(board, bombs, 0, 0, ref numSolutions);
    }

    private static void Explore(Board board, BombState[,] bombs, int x, int y, ref int numSolutions)
    {
        int nextX = x + 1;
        int nextY = y;
        if (nextX >= board.Width)
        {
            nextX = 0;
            nextY++;
        }

        bombs[y, x] = BombState.Present;
        if (board.DetermineValidity(bombs, x, y))
        {
            if (nextY >= board.Height)
                numSolutions++;
            else
                Explore(board, bombs, nextX, nextY, ref numSolutions);
        }

        bombs[y, x] = BombState.NotPresent;
        if (board.DetermineValidity(bombs, x, y))
        {
            if (nextY >= board.Height)
                numSolutions++;
            else
                Explore(board, bombs, nextX, nextY, ref numSolutions);
        }

        bombs[y, x] = BombState.Unexplored;
    }
}

public enum CellState
{
    Odd,
    Even,
    None,
}

public enum BombState
{
    Unexplored,
    Present,
    NotPresent,
}

public class Board
{
    private readonly CellState[,] cells;
    public int Width { get; }
    public int Height { get; }

    public Board(CellState[,] cells)
    {
        this.cells = cells;
        this.Width = cells.GetLength(1);
        this.Height = cells.GetLength(0);
    }

    // Takes a board of bombs, and the position of a bomb to inspect, and determines
    // whether that bomb position is definitely valid, or is unknown/invalid
    public bool DetermineValidity(BombState[,] bombs, int changedX, int changedY)
    {
        // We only need to consider the cells in a square around the cell which was just changed

        for (int x = Math.Max(0, changedX - 1); x < Math.Min(this.Width, changedX + 1); x++)
        {
            for (int y = Math.Max(0, changedY - 1); y < Math.Min(this.Height, changedY + 1); y++)
            {
                var cellState = this.cells[y, x];

                // If this is a "None", there's nothing to check
                if (cellState == CellState.None)
                    continue;

                // For each cell, check its neighbours... If they're all specified, get the number of boms
                int numBombs = 0;
                bool areAllSpecified = true;

                if (x > 0)
                    InspectNeighbour(bombs[y, x - 1], ref numBombs, ref areAllSpecified);
                if (areAllSpecified && x < this.Width - 1)
                    InspectNeighbour(bombs[y, x + 1], ref numBombs, ref areAllSpecified);
                if (areAllSpecified && y > 0)
                    InspectNeighbour(bombs[y - 1, x], ref numBombs, ref areAllSpecified);
                if (areAllSpecified && y < this.Height - 1)
                    InspectNeighbour(bombs[y + 1, x], ref numBombs, ref areAllSpecified);

                if (areAllSpecified && ((numBombs % 2) == 0) != (cellState == CellState.Even))
                    return false;
            }
        }

        return true;
    }


    private static void InspectNeighbour(BombState state, ref int numBombs, ref bool areAllSpecified)
    {
        switch (state)
        {
            case BombState.NotPresent:
                break;
            case BombState.Present:
                numBombs++;
                break;
            case BombState.Unexplored:
                areAllSpecified = false;
                break;
        }
    }
}
14
задан abhilash 27 April 2009 в 14:46
поделиться

9 ответов

Вам необходимо скрыть консоль за интерфейсом (Это можно считать полезным в любом случае)

Написать тест

[TestMethod]
public void HelloWorld_WritesHelloWorldToConsole()
{
  // Arrange
  IConsole consoleMock = MockRepository.CreateMock<IConsole>();

  // primitive injection of the console
  Program.Console = consoleMock;

  // Act
  Program.HelloWorld();

  // Assert
  consoleMock.AssertWasCalled(x => x.WriteLine("Hello World"));
}

Написать программу

public static class Program
{
  public static IConsole Console { get; set; }

  // method that does the "logic"
  public static void HelloWorld()
  {
    Console.WriteLine("Hello World");
  }

  // setup real environment
  public static void Main()
  {
    Console = new RealConsoleImplementation();
    HelloWorld();
  }
}

Refactor на что-то более полезное; -)

21
ответ дан 1 December 2019 в 06:31
поделиться

Презентатор-просмотр? (модель не кажется строго необходимой)

Представление - это класс, который передает вывод на консоль (простые однострочные методы).

Presenter - это интерфейс, который вызывает view.ShowText («Hello World»), Вы можете проверить это, предоставив представление заглушки.

Для повышения производительности я бы просто написал чертову программу:)

Достаточно одного теста (в псевдокоде):

IView view = Stub<IView>();
Expect( view.ShowText("Hello World") );

Presenter p = new Presenter( view );
p.Show();

Assert.IsTrue( view.MethodsCalled );
5
ответ дан 1 December 2019 в 06:31
поделиться

Ну ... Я не видел TDD-версию Hello World. Но, чтобы увидеть такую ​​же простую проблему, к которой подходили с учетом TDD и управляемости, вы можете взглянуть на Enterprise FizzBuzz ( код ). По крайней мере, это позволит вам увидеть уровень чрезмерной инженерии, которого вы могли бы достичь в мире приветствия.

5
ответ дан 1 December 2019 в 06:31
поделиться

Псевдокод:

  • Создать макет чего-то, что принимает поток.
  • Вызвать helloworld на этот макет через своего рода внедрение зависимостей (как аргумент конструктора).
  • Убедитесь, что строка «Hello World» была перенаправлена ​​в ваш макет.

В рабочем коде вместо макета вы используете подсказку.

Правило большого пальца:

  • Определите критерии успеха в том, как Компонент взаимодействует с другими вещами, а не только с тем, как он взаимодействует с вами. TDD фокусируется на внешнем поведении.
  • Настройте среду (макеты) для обработки цепочки событий.
  • Запустите его.
  • Проверьте.
4
ответ дан 1 December 2019 в 06:31
поделиться

Очень интересный вопрос. Я не большой пользователь TDD, но я выскажу некоторые мысли.

Я предполагаю, что приложение, которое вы хотите протестировать, таково:

public static void Main()
{
    Console.WriteLine("Hello World");
}

Теперь, так как я не могу придумать какой-либо хороший способ проверить это напрямую, я бы разбил задачу записи на интерфейс.

public interface IOutputWriter
{
    void WriteLine(string line);
}

public class ConsoleWriter : IOutputWriter
{
    public void WriteLine(string line)
    {
        Console.WriteLine(line);
    }
}

И разбить приложение следующим образом:

public static void Main()
{
    IOutputWriter consoleOut = new ConsoleWriter();
    WriteHelloWorldToOutput(consoleOut);
}

public static void WriteHelloWorldToOutput(IOutputWriter output)
{
    output.WriteLine("Hello World");
}

Теперь у вас есть точка внедрения в метод, который позволяет вам использовать выбранную вами среду для моделирования, чтобы утверждать, что метод WriteLine вызывается с параметром «Hello World».

Проблемы, которые я оставил нерешенными (и я был бы заинтересован во вводе):

  1. Как протестировать класс ConsoleWriter, я думаю, вам все еще нужна некоторая среда тестирования пользовательского интерфейса, чтобы добиться этого, и если у вас это было, то В любом случае вся проблема в споре ...

  2. Тестирование основного метода.

  3. Почему я чувствую, что чего-то достиг, изменив одну строку непроверенного кода на семь строк кода, только одна из которых фактически протестирована (хотя я предполагаю, что охват увеличился )

3
ответ дан 1 December 2019 в 06:31
поделиться

В Java вы можете захватить («перенаправить») поток System.out и прочитать его содержимое. Я уверен, что то же самое можно сделать в C #. Это всего лишь несколько строк кода в Java, так что я уверен, что в C # этого не будет намного больше

2
ответ дан 1 December 2019 в 06:31
поделиться

Я действительно должен возразить против вопроса! Все методологии имеют свое место, и TDD хорош во многих местах. Но пользовательские интерфейсы - это первое, что я действительно отхожу от TDD. Это, по моему скромному мнению, является одним из лучших обоснований шаблона проектирования MVC: протестируйте свои модели и контроллер программно; визуально осмотреть свой взгляд. То, о чем вы говорите, - это жесткое кодирование данных «Hello World» и тестирование их попадания на консоль. Чтобы выполнить этот тест на одном и том же исходном языке, вам, скорее всего, потребуется фиктивный объект консоли, , который является единственным объектом, который вообще что-либо делает .

С другой стороны, вы можете написать свой тест в bash:

echo `java HelloWorldProgram`|grep -c "^Hello World$"

Немного сложно добавить в набор тестов JUnit, но что-то подсказывает мне, что это никогда не было планом ...

2
ответ дан 1 December 2019 в 06:31
поделиться

Я думаю, что-то вроде этого:

using NUnit.Framework;
using System.Diagnostics;

[TestFixture]
public class MyTestClass {
    [Test]
    public void SayHello() {
        string greet = "Hello World!";
        Debug.WriteLine(greet);
        Assert.AreEqual("Hello World!", greet);
    }
}
0
ответ дан 1 December 2019 в 06:31
поделиться

Я согласен с Дэвидом Бергером; отделите интерфейс и протестируйте модель. Похоже, что "модель" в данном случае - это простой класс, который возвращает "Hello, world!". Тест выглядел бы так (на Java):

  Greeter greeter = new Greeter();
  assertEquals("Hello World!", greeter.greet());

Я создал запись решения Hello World в стиле TDD на http://ziroby.wordpress.com/2010/04/18/tdd_hello_world/ .

1
ответ дан 1 December 2019 в 06:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: