Насмешливые статические методы - PullRequest
66 голосов
/ 03 мая 2011

Недавно я начал использовать Moq для модульного тестирования. Я использую Moq для макетирования классов, которые мне не нужно тестировать.

Как вы обычно имеете дело со статическими методами?

public void foo(string filePath)
{
    File f = StaticClass.GetFile(filePath);
}

Как мог этот статический метод, StaticClass.GetFile() быть высмеянным?

P.S. Буду признателен за любые материалы для чтения, которые вы порекомендуете на Moq и Unit Testing.

Ответы [ 7 ]

43 голосов
/ 03 мая 2011

@Pure.Krome: хороший ответ, но я добавлю несколько деталей

@ Кевин: Вам нужно выбрать решение в зависимости от изменений, которые вы можете внести в код.
Если вы можетеизмените это, некоторые внедрения зависимости делают код более тестируемым.Если вы не можете этого сделать, вам нужна хорошая изоляция.
С помощью бесплатной моделирующей среды (Moq, RhinoMocks, NMock ...) вы можете использовать только делегаты, интерфейсы и виртуальные методы.Итак, для статических, закрытых и не виртуальных методов у вас есть 3 решения:

  • TypeMock Isolator (можно издеваться над всем, но это дорого)
  • JustMock от Telerik (новичок, дешевле, но еще не бесплатен)
  • Родинок от Microsoft (единственное бесплатное решение дляизоляция)

Я рекомендую Moles , потому что он бесплатный, эффективный и использует лямбда-выражения, такие как Moq.Только одна важная деталь: родинки дают окурки, а не издевательства.Таким образом, вы все равно можете использовать Moq для интерфейса и делегатов;)

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

37 голосов
/ 03 мая 2011

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

Вы также можете искать Google для получения дополнительной информации.

Также есть несколько вопросов, ранее задаваемых по StackOverflow здесь , здесь и здесь .

16 голосов
/ 13 июня 2014

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

Предположим, вы хотите смоделировать System.DateTime.Now статический метод. Сделайте это, например, так:

using (ShimsContext.Create())
{
    System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1);
    Assert.AreEqual(DateTime.Now.Year, 1837);
}

У вас есть похожее свойство для каждого статического свойства и метода.

6 голосов
/ 24 июля 2018

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

Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A<string>()))
    .With((string name) => /*Here return your mocked value for test*/);
var sut = new Service();
PoseContext.Isolate(() =>
    result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim);

Для дальнейшего чтения см. Здесь https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8

2 голосов
/ 14 сентября 2018

Мне понравилась Pose, но я не смог ее остановить, чтобы вызвать исключение InvalidProgramException, которое, похоже, является известной проблемой . Теперь я использую Smocks вот так:

Smock.Run(context =>
{
    context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

    // Outputs "2000"
    Console.WriteLine(DateTime.Now.Year);
});
1 голос
/ 21 сентября 2017

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

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

Чтобы это работало, вам необходимо иметь доступ к статическому методу, чтобы он не работал ни для каких внешних библиотек, таких как System.DateTime.

Вот пример, с которым я играл, где я создал пару статических методов, один с возвращаемым типом, который принимает два параметра, и один универсальный, который не имеет возвращаемого типа.

Основной статический класс:

public static class LegacyStaticClass
{
    // A static constructor sets up all the delegates so production keeps working as usual
    static LegacyStaticClass()
    {
        ResetDelegates();
    }

    public static void ResetDelegates()
    {
        // All the logic that used to be in the body of the static method goes into the delegates instead.
        ThrowMeDelegate = input => throw input;
        SumDelegate = (a, b) => a + b;
    }

    public static Action<Exception> ThrowMeDelegate;
    public static Func<int, int, int> SumDelegate;

    public static void ThrowMe<TException>() where TException : Exception, new()
        => ThrowMeDelegate(new TException());

    public static int Sum(int a, int b)
        => SumDelegate(a, b);
}

Юнит-тесты (xUnit и mustly)

public class Class1Tests : IDisposable
{
    [Fact]
    public void ThrowMe_NoMocking_Throws()
    {
        Should.Throw<Exception>(() => LegacyStaticClass.ThrowMe<Exception>());
    }

    [Fact]
    public void ThrowMe_EmptyMocking_DoesNotThrow()
    {
        LegacyStaticClass.ThrowMeDelegate = input => { };

        LegacyStaticClass.ThrowMe<Exception>();

        true.ShouldBeTrue();
    }

    [Fact]
    public void Sum_NoMocking_AddsValues()
    {
        LegacyStaticClass.Sum(5, 6).ShouldBe(11);
    }

    [Fact]
    public void Sum_MockingReturnValue_ReturnsMockedValue()
    {
        LegacyStaticClass.SumDelegate = (a, b) => 6;
        LegacyStaticClass.Sum(5, 6).ShouldBe(6);
    }

    public void Dispose()
    {
        LegacyStaticClass.ResetDelegates();
    }
}
0 голосов
/ 15 сентября 2017

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

Для этого я создал класс (назовем его Placeholder), один метод которого называется статическим методом StaticClass.GetFile.

public class Placeholder{  

    //some empty constructor

    public File GetFile(){

        File f = StaticClass.GetFile(filePath);
        return f;
    }
}

Затем, вместо вызова StaticClass.GetFile в foo, я создал экземпляр Placeholder и вызвал функцию GetFile.

public void foo(string filePath)
{
    Placeholder p = new Placeholder();
    File f = p.GetFile(filePath);
}

Теперь в модульных тестах вместо попытки StaticClass.GetFile я смог смоделировать нестатический метод GetFile из класса Placeholder.

...