Как протестировать веб-сервис с Linq

1

У меня есть веб-сервис, на который я хотел бы провести тестирование отдельных модулей, однако я не уверен, как это сделать. Может ли кто-нибудь дать какие-либо предложения? Ниже webservice, он создает объект с тремя полями, но только тогда, когда в очереди базы данных есть значения.

    [WebMethod]
    public CommandMessages GetDataLINQ()
    {
        CommandMessages result;
        using (var dc = new TestProjectLinqSQLDataContext())
        {
            var command = dc.usp_dequeueTestProject();
            result = command.Select(c => new CommandMessages(c.Command_Type, c.Command, c.DateTimeSent)).FirstOrDefault();
            return result;
        }
    }
Теги:
linq
unit-testing

3 ответа

1
Лучший ответ

Прежде всего, то, что вы опубликовали, действительно не может быть Unit Test; по определению, Единичный тест может иметь только одну причину; Однако в вашем случае один тест GetDataLINQ() ("Системный тест" или "SUT") может завершиться неудачей из-за проблемы с любой из зависимостей в функции, а именно TestProjectLinqSQLDataContext и usp_dequeueTestProject.

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

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

(Я буду считать, что ваш файл класса страницы "MyPageClass" с этого момента я буду делать вид, что это не код страницы или код asmx, потому что, как отмечали другие плакаты, это имеет значение только в контексте доступ к коду через HTTP, который мы здесь не делаем)

var sut = new MyPageClass(); //sut now contains a DataContext over which the Test Method has no control.
var result = sut.GetDataLINQ(); //who know what might happen?

Подумайте о некоторых возможных причинах отказа в этом методе, когда вы вызываете sut.GetDataLINQ():

  • new TestProjectLinqSQLDataContext() приводит к исключению из-за ошибки в конструкторе TestProjectLinqSQLDataContext

  • dc.usp_dequeueTestProject() приводит к исключению из-за сбоя подключения к базе данных или из-за того, что хранимая процедура изменилась или не существует.

  • command.Select(...) приводит к исключению из-за некоторого неизвестного дефекта в конструкторе CommandMessage

  • Возможно, существует еще много причин (т.е. Неспособность выполнить корректно, в отличие от исключения)

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

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

Поэтому, если вы действительно хотите выполнить Unit Test, вам придется внести некоторые изменения в свой код. Я опишу идеальный сценарий, а затем одну альтернативу (из многих), если вы не можете по какой-либо причине реализовать это. В код ниже не включена проверка ошибок, и она не компилируется, поэтому прошу простить любые опечатки и т.д.

Идеальный сценарий

Выясните зависимости и введите их в конструктор.

Обратите внимание, что этот идеальный сценарий потребует, чтобы вы внесли в свой проект инфраструктуру IOC (Ninject, AutoFAC, Unity, Windsor и т.д.). Он также требует ракурса Mocking (Moq и т.д.).

1. Создайте интерфейс IDataRepository, который содержит метод DequeueTestProject

public interface IDataRepository
{
     public CommandMessages DequeueTestProject();
}

2. IDataRepository как зависимость MyPageClass

 public class MyPageClass
    {
        readonly IDataRepository _repository;
        public MyPageClass(IDataRepository repository)
        {
             _repository=repository;
        }
    }

3. Создайте фактическую реализацию IDataRepository, которая будет использоваться в "реальной жизни", но не в ваших модульных тестах

public class RealDataRepository: IDataRepository
{
    readonly MyProjectDataContext _dc;
    public RealDataRepository()
    {
        _dc = new MyProjectDataContext(); //or however you do it.
    }

    public CommandMessages DequeueTestProject()
    {
        var command = dc.usp_dequeueTestProject();
        result = command.Select(c => new CommandMessages(c.Command_Type, c.Command, c.DateTimeSent)).FirstOrDefault();
        return result;
    }
}

Здесь вам нужно будет задействовать свою инфраструктуру IOC, чтобы она могла вводить правильный IDataRepository (то есть RealDataRepository) всякий раз, когда ваш MyPageClass помощью структуры ASP.NET

4. Перекодируйте метод GetDataLINQ() чтобы использовать элемент _ repository

public CommandMessages GetDataLINQ()
    {
        CommandMessages result;
        return _repository.DequeueTestProject();
    }

Так что это нас купило? Итак, рассмотрим, как вы можете протестировать следующую спецификацию для GetDataLINQ:

  1. Должен всегда ссылаться на DequeueTestProject
  2. Должен возвращать NULL если в базе данных нет данных
  3. Должен возвращать действительный экземпляр CommandMessages если в базе данных есть данные.

Тест 1 - всегда должен вызывать DequeueTestProject

public void GetDataLINQ_AlwaysInvokesDequeueTestProject()
{
    //create a fake implementation of IDataRepository
    var repo = new Mock<IDataRepository>();
    //set it up to just return null; we don't care about the return value for now
    repo.Setup(r=>r.DequeueTestProject()).Returns(null);
    //create the SUT, passing in the fake repository
    var sut = new MyPageClass(repo.Object);
    //call the method
    sut.GetDataLINQ();
    //Verify that repo.DequeueTestProject() was indeed called.
    repo.Verify(r=>r.DequeueTestProject(),Times.Once);
}

Тест 2 - должен вернуть NULL, если в базе данных нет данных

public void GetDataLINQ_ReturnsNULLIfDatabaseEmpty()
{
//create a fake implementation of IDataRepository
    var repo = new Mock<IDataRepository>();
    //set it up to return null; 
    repo.Setup(r=>r.DequeueTestProject()).Returns(null);
    var sut = new MyPageClass(repo.Object);
    //call the method but store the result this time:
    var actual = sut.GetDataLINQ();
    //Verify that the result is indeed NULL:
    Assert.IsNull(actual);
}

Тест 3 - должен возвращать действительный экземпляр CommandMessages если в базе данных есть данные.

public void GetDataLINQ_ReturnsNCommandMessagesIfDatabaseNotEmpty()
{
//create a fake implementation of IDataRepository
    var repo = new Mock<IDataRepository>();
    //set it up to return null; 
    repo.Setup(r=>r.DequeueTestProject()).Returns(new CommandMessages("fake","fake","fake");
    var sut = new MyPageClass(repo.Object);
    //call the method but store the result this time:
    var actual = sut.GetDataLINQ();
    //Verify that the result is indeed NULL:
    Assert.IsNotNull(actual);
}
  • Поскольку мы можем IDataRepository интерфейс IDataRepository, мы можем полностью контролировать, как он себя ведет.
  • Мы даже могли бы сделать это броском исключения, если нам нужно было проверить, как GetDataLINQ реагирует на непредвиденные результаты.
  • Это реальная выгода от абстрагирования ваших зависимостей, когда дело доходит до Unit Testing (не говоря уже об уменьшении связи в вашей системе, поскольку зависимости не привязаны к конкретному конкретному типу).

Не совсем идеальный метод

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

  • Создание интерфейса IDataRepository
  • Создать класс RealDataRepository
  • Создайте другие реализации IDataRepository, которые имитируют поведение, которое мы создали на лету в предыдущем примере. Они называются заглушками, и в основном это просто классы с одним предопределенным поведением, которое никогда не меняется. Это делает его идеальным для тестирования, потому что вы всегда знаете, что произойдет, когда вы их вызовете.

public class FakeEmptyDatabaseRepository:IDataRepository
{
  public CommandMessages DequeueTestProject(){CallCount++;return null;}
  //CallCount tracks if the method was invoked.
  public int CallCount{get;private set;}
}

public class FakeFilledDatabaseRepository:IDataRepository
{
  public CommandMessages DequeueTestProject(){CallCount++;return new CommandMessages("","","");}
  public int CallCount{get;private set;}
}

Теперь измените MyPageClass в соответствии с первым методом, за исключением того, что не объявляйте IDataRepository в конструкторе, вместо этого выполните следующее:

   public class MyPageClass
    {
       private IDataRepository _repository; //not read-only
       public MyPageClass()
       {
          _repository = new RealDataRepository();
       }

       //here is the compromise; this method also returns the original repository so you can restore it if for some reason you need to during a test method.
       public IDataRepository SetTestRepo(IDataRepository testRepo)
       {
          _repository = testRepo;
       }
    }

И, наконец, измените свои модульные тесты для использования FakeEmptyDatabaseRepository или FakeFilledDatabaseRepository если это необходимо:

public void GetDataLINQ_AlwaysInvokesDequeueTestProject()
{
    //create a fake implementation of IDataRepository
    var repo = new FakeFilledDatabaseRepository();  
    var sut = new MyPageClass();
    //stick in the stub:
    sut.SetTestRepo(repo);
    //call the method
    sut.GetDataLINQ();
    //Verify that repo.DequeueTestProject() was indeed called.
    var expected=1;
    Assert.AreEqual(expected,repo.CallCount);
}

Обратите внимание, что этот второй сценарий не является идеальным сценарием с использованием слоновой кости и не приводит к строго чистым FakeEmptyDatabaseRepository (т. FakeEmptyDatabaseRepository в FakeEmptyDatabaseRepository может быть дефект, ваш тест также может потерпеть неудачу), но это довольно хороший компромисс; однако, если возможно, стремиться к достижению первого сценария, поскольку это приводит ко всем видам других преимуществ и делает вас на один шаг ближе к действительно SOLID-коду.

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

3

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

  • 0
    Я не очень понимаю, как бы я назвал метод веб-службы? Извините, я новичок в модульном тестировании. В качестве примера, что бы я сделал для приведенного выше кода для его модульного тестирования?
  • 0
    Метод веб-сервиса - это метод, подобный любому другому методу. Единственное отличие - это атрибут WebMethod, который вы помещаете поверх него. Однако атрибут - это информация метаданных. Он не влияет на саму функциональность, а скорее описывает или расширяет функциональность. В случае модульного теста важная вещь - это результат метода, и на него вообще не влияет атрибут.
Показать ещё 2 комментария
0

Я бы изменил ваш код следующим образом:

public class MyRepository
{       
     public CommandMessage DeQueueTestProject()
     {
        using (var dc = new TestProjectLinqSQLDataContext())
        {
            var results = dc.usp_dequeueTestProject().Select(c => new CommandMessages(c.Command_Type, c.Command, c.DateTimeSent)).FirstOrDefault();
            return results;
        }
     }
}

Затем введите свой веб-метод как:

[WebMethod]
public CommandMessages GetDataLINQ()
{
    MyRepository db = new MyRepository();
    return db.DeQueueTestProject();
}

Затем введите код блока:

    [Test]
    public void Test_MyRepository_DeQueueTestProject()
    {
        // Add your unit test using MyRepository
        var r = new MyRepository();

        var commandMessage = r.DeQueueTestProject();
        Assert.AreEqual(commandMessage, new CommandMessage("What you want to compare"));
    }

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню