Модульное тестирование Сервиса полностью зависит от стороннего приложения - PullRequest
3 голосов
/ 19 августа 2011

В настоящее время я пишу службу Windows на C #, которая будет отвечать за размещение службы WCF.Когда запускаются сервисные операции, моя служба выполняет приложение командной строки, выполняет некоторую обработку данных в StandardOutput и возвращает результаты.Это приложение командной строки изменяет состояние других сторонних служб на сервере.

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

Мои сервисные операции примерно равны 1: 1 с операциями, выполняемыми приложением командной строки.Если вы не догадались, я создаю инструмент, позволяющий удаленно администрировать сервис, который обеспечивает только администрирование CLI.

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

    private string RunAdmin(String arguments)
    {
        try
        {
            var exe = String.Empty;
            if (System.IO.File.Exists(@"C:\Program Files (x86)\App\admin.exe"))
            {
                exe = @"C:\Program Files (x86)\App\admin.exe";
            }
            else
            {
                exe= @"C:\Program Files\App\admin.exe";
            }

            var psi = new System.Diagnostics.ProcessStartInfo
            {
                FileName = exe,
                UseShellExecute = false,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
            };

            psi.Arguments = String.Format("{0} -u {1} -p {2} -y", arguments, USR, PWD);

            var adm= System.Diagnostics.Process.Start(psi);

            var output = fmsadmin.StandardOutput.ReadToEnd();

            return output;
        }
        catch (Exception ex)
        {
            var pth = 
                System.IO.Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                    "FMSRunError.txt");

            System.IO.File.WriteAllText(pth, String.Format("Message:{0}\r\n\r\nStackTrace:{1}", ex.Message, ex.StackTrace));

            return String.Empty;
        }
    }

    public String Restart()
    {
        var output = this.RunAdmin("/restart");
        return output; // has cli tool return code
    }

Без добавления большого количества «тестового» кода в мой метод RunAdmin, есть ли какой-нибудь способ для модульного тестирования этого?Очевидно, я мог бы добавить много кода в метод RunAdmin для «поддельного» вывода, но я бы хотел избежать этого, если это возможно.Если это рекомендуемый способ, я, вероятно, могу создать скрипт для себя, чтобы создать все возможные выходные данные.Есть ли другой способ?

Ответы [ 3 ]

8 голосов
/ 19 августа 2011

IMO, придумать способ проверить код, который вы пишете, не будет больше проблем, чем оно того стоит. Наличие хороших тестов облегчает работу новых функций, исправление ошибок и поддержку вашего приложения. Исходя из того, что вы описали, я думаю, вам нужно решить, какие тесты лучше использовать в вашем случае: модульные тесты или интеграционные тесты.

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

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

Другой подход, и, вероятно, тот, который я выбрал бы, состоит в том, чтобы использовать немного обоих. Когда инструмент командной строки легко подделать, используйте модульные тесты. Если это не так, используйте интеграционные тесты. Для своих проектов я очень предпочитаю, когда я могу написать настоящие модульные тесты, которые не зависят от чего-то внешнего. К сожалению, поскольку мне приходится иметь дело с большим количеством старого кода, это не всегда возможно. Однако, даже когда это так, я всегда чувствую себя лучше, если смогу пройти интеграционный тест высокого уровня для всего этого, просто для небольшого дополнительного покрытия. Например, если я пишу веб-сайт, у меня может быть 100 модульных тестов, которые охватывают все мелочи, связанные с созданием веб-страницы, но если я могу, у меня будет 1 или 2 интеграционных теста, которые делают веб запросить и проверить текст на странице, просто ради здравомыслия. Наличие обоих типов тестов определенно дает мне более высокий уровень уверенности в том, что код будет работать так, как ожидается, когда он будет запущен.

Надеюсь, это поможет.

РЕДАКТИРОВАТЬ
Я думал об этом еще немного, и может быть относительно простой способ провести модульное тестирование вашего приложения. Чтобы подделать входные данные из инструмента командной строки, просто запустите инструмент командной строки для любого сценария, который вы пытаетесь протестировать. Сохраните выходные данные в виде текстового файла, а затем используйте этот текстовый файл в качестве входных данных для теста. Если вы используете интерфейс и внедрение зависимостей, ваш сервисный код будет одинаковым, независимо от того, запускаете ли вы тесты или само приложение. Например, скажем, мы тестируем метод, который выведет номер версии CLI:

public interface ICommandLineRunner
{
    string RunCommand(string command);
}

public class CLIService
{
   private readonly ICommandLineRunner _cliRunner;
   public CLIService(ICommandLineRunner cliRunner)
   {
       _cliRunner = cliRunner;
   }

   public string GetVersionNumber()
   {
       string output = _cliRunner.RunCommand("-version");
       //Parse output and store in result
       return result;        
   }
}

[Test]
public void Test_Gets_Version_Number()
{
    var mockCLI = new Mock<ICommandLineRunner>();
    mockCLI.Setup(a => a.RunCommand(It.Is<string>(s => s == "-version"))
       .Returns(File.ReadAllText("version-number-output.txt"));

    var mySvc = new CLIService(mockCLI.Object);
    var result = mySvc.GetVersionNumber();
    Assert.AreEqual("1.0", result);
}

В этом случае мы вводим ICommandLineRunner в CLIService и насмехаемся над вызовом RunCommand, чтобы вернуть определенный вывод, который мы настроили заранее (содержимое нашего текстового файла). Этот подход позволяет нам проверить, что CLIService делает правильный вызов к ICommandLineRunner, и что он правильно анализирует вывод этого вызова. Если вам нужно, чтобы я что-то разъяснил об этом подходе, пожалуйста, дайте мне знать.

4 голосов
/ 19 августа 2011

@ rsbarro хорошо это закрывает (+1 к его ответу). Лично я забуду про интеграционные тесты. Я бы хотел убедиться, что мой код правильно взаимодействует со службой. Я уверен, что служба, с которой вы взаимодействуете, уже прошла необходимое тестирование, и что у меня нет оснований вкладывать усилия в ее тестирование. Мои тесты должны быть примерно мой код, а не их .

1 голос
/ 20 августа 2011

Просто чтобы предложить вам второе мнение. На основании вашего описания и примера кода мне трудно найти код, который не зависит от системных вызовов. Единственная логика в этом коде - это вызов класса .NET Process. Это буквально один-к-одному, веб-оболочка над System.Diagnostics.Process. Выходные данные процесса возвращаются веб-клиенту без изменений.

Две основные причины написать единицу тесты:

  • Улучшение дизайна ваших классов и взаимодействия между классами

Едва ли можно что-то улучшить, потому что вам в значительной степени нужен один или два класса. Этот код просто не несет достаточной ответственности. Как вы описываете, все вписывается в экземпляр службы WCF. Нет смысла создавать оболочки над .NET Process, потому что это будет просто искусственный прокси.

  • Убедитесь, что вы уверены в своем коде, избегаете ошибок и т.д.

Единственные ошибки, которые могут произойти с этим кодом, - это если вы неправильно используете .NET Process API. Поскольку вы не управляете этим API, тогда ваш тестовый код будет повторять ваши предположения об API. Позвольте привести пример. Предположим, что вы или ваш коллега случайно установили RedirectStandardOutput = false. Это будет ошибка в вашем коде, потому что вы не сможете получить вывод процесса. Ваш тест поймает это? Я так не думаю, если вы буквально не напишите что-то вроде:

public class ProcessStartInfoCreator {
    public static ProcessStartInfo Create() {
        return new ProcessStartInfo {
            FileName = "some.exe",
            UseShellExecute = false,
            RedirectStandardInput = true,
            RedirectStandardOutput = false  // <--- BUG
        };
    }
}

[Test]
public void TestThatFindsBug() {
    var psi = ProcessStartInfoCreator.Create();
    Assert.That(psi.RedirectStandardOutput, Is.True);
}

Ваш тест просто повторяет ваш код, ваше предположение об API у вас нет. Даже после того, как вы потратили время на внедрение искусственного, ненужного класса, такого как ProcessStartInfoCreator, Microsoft представит еще один флаг, который потенциально может нарушить ваш код. Будет ли этот тест поймать его? Нет. Ваш тест обнаружит ошибку, когда исполняемый файл EXE будет переименован или изменит параметры командной строки? Нет. Я настоятельно рекомендую вам прочитать эту статью. Идея состоит в том, что код, представляющий собой просто тонкую оболочку над API, который вы не контролируете, не требует модульных тестов , он требует интеграционных тестов. Но это другая история.

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

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

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