Правильное модульное тестирование: тестирование метода без [доступного] состояния или возвращаемого значения - PullRequest
7 голосов
/ 17 января 2009

Я немного новичок в модульном тестировании. Одна вещь (на данный момент) о правильном тестировании смущает меня.

Например, как вы тестируете основной метод, если у него нет состояния и только консольный вывод? Например, где методы и состояние myServer являются частными?

 public static void main(String[] args)
 {
     Server myServer = new Server()
     if(myServer.start())
          System.out.println("started");
     else
          System.out.println("failed"); 
 }

Я не хочу менять свой код и предоставлять информацию о методах и состоянии сервера, чтобы сделать их общедоступными.

Обратите внимание, я не спрашиваю, как проверить myServer.start (), я спрашиваю, как проверить саму main ().

Пожалуйста, дайте мне знать.

Спасибо, ребята, JBU

Ответы [ 6 ]

8 голосов
/ 17 января 2009

Основные методы должны быть очень тонкими и просто заставляют мяч двигаться. Следовательно вообще не проверено. например .net Winforms app Основной метод может содержать

static void Main()
{
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);
  Application.Run(new MainWindow());
}

Если ваш метод main выполняет много задач, рассмотрите возможность его извлечения (в случае необходимости, в новый класс). например как показано ниже ... (у меня нет под рукой JavaIDE .. следующий код .net должен легко переводиться)

static void Main()
{
  new MainApp().Run();
}

Теперь, если Run может быть проверен. Main тоже покрыт. Давайте посмотрим на MainApp. Рефакторинг 2 задач запуска сервера и протоколирование результата как 2 метода.

public class MainApp
{
  private Server m_Server;
  public MainApp():this(new Server())
  {}
  public MainApp(Server s)
  {  m_Server = s;      }

  public void Run()
  {  Log(LaunchServer());  }

  private string LaunchServer()
  {  return (m_Server.start() ? "started" : "failed");        }

  protected virtual void Log(string sMessage)
  {  System.Console.WriteLine(sMessage);        }
}

Теперь давайте посмотрим на тестовый код ... Я буду использовать трюк "подкласс и переопределение", чтобы кэшировать результат, как показано ниже. (Вы также можете использовать Mocks .. по вкусу)

public class FakeApp : MainApp
{
  private string sLastLoggedMessage;

  public FakeApp(Server s) : base(s) { }

  public string getLastLoggedMessage()
  {  return sLastLoggedMessage;        }

  protected override void Log(string sMessage)
  {  sLastLoggedMessage = sMessage;        }
}

тест теперь тривиален

[TestFixture]
public class TestMainApp
{
  [Test]
  public void TestRun()
  {
    Server s = new Server();
    FakeApp f = new FakeApp(s);

    f.Run();

    Assert.AreEqual("started", f.getLastLoggedMessage());
  }
}

если вы не можете принудительно заставить Server.start () выполнить или потерпеть неудачу по своему желанию .. Вы можете создать подкласс FakeServer .. и переопределить start () для возврата фиксированных значений.

Если метод что-то делает, его можно проверить. Если это не так, он должен прекратить свое существование. Перефразировать его. HTH

4 голосов
/ 17 января 2009

Вы (обычно) не тестируете методы main (). В вашем случае вы тестируете модульный класс Server (и, возможно, другие), т.е. тестируете его открытый интерфейс.

1 голос
/ 17 января 2009

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

Для этого вы можете использовать одну из нескольких структур фиктивных объектов, например, EasyMock.

Во время модульного тестирования фиктивный объект будет выдавать правильное исключение, если вызовы, сделанные с его помощью методом main (), не будут соответствовать настроенным ожиданиям.

Конечно, чтобы предоставить фиктивный объект, вам нужно немного изменить метод в отношении реализации объекта - сделать «Сервер» интерфейсом, а не конкретным классом, и использовать каркас (например, Spring). ) вместо создания экземпляра объекта Server вручную, например вместо:

Server myServer = new Server();

у вас будет это:

Server myServer = applicationContext.getBean("server");

Вы создадите отдельные конфигурации контекста приложения Spring для производственного приложения и для модульных тестов, и разница будет в том, что контекст приложения для производства будет настроен с использованием файла XML, тогда как для модульных тестов будет программно создан и заполнен макетом объекты из методов setUp () ваших тестовых классов.

0 голосов
/ 17 января 2009

оберните код неосновным методом и проверьте это; -)

серьезно, что все остальные сказали

но если вы должны ... выполнить модульный тест, запустите консольное приложение в оболочке DOS (см. Объект Process) с STDOUT, перенаправленным в файл, а затем прочитайте содержимое выходного файла, чтобы убедиться, что оно говорит "запущено "или" не удалось ", в зависимости от того, что вы ожидаете

0 голосов
/ 17 января 2009

Вообще говоря, main () не является "модулем", и было бы сложно разработать модульный тест для. По соглашению main () должна возвращать int, чтобы указать состояние ошибки. Возможно, это все, что вам нужно?

0 голосов
/ 17 января 2009

Если вы хотите прояснить этот случай и многие другие, могу ли я предложить серию Прагматическое модульное тестирование ?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...