it-roy-ru.com

Насмешливые статические методы

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

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

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

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

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

63
Kevin Meredith

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

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

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

35
Pure.Krome

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

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

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

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

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

41
Jeco

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

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

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

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

14
pt12lol

Вы можете достичь этого с помощью 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

4
mr100

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

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

Чтобы это работало, вам нужно иметь доступ к статическому методу, чтобы он не работал ни для каких внешних библиотек, таких как 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();
    }
}
1
Joe_DM

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

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

    // Outputs "2000"
    Console.WriteLine(DateTime.Now.Year);
});
1
sirdank

Я знаю, что это немного поздно, но это обходное решение позволило мне смоделировать статический метод с использованием 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

0
Justin Borromeo