2016-05-16 13 views
3

Я хочу написать модульные тесты для класса, который реализует IDisposable. Класс имеет множество частных полей, которые также реализуют IDisposable. В своем тесте я хочу проверить, что, когда я звоню Dispose(), он правильно вызывает Dispose() во всех своих IDisposable-полях. По сути, я хочу, чтобы мой модульный тест выглядел так:Как выполнить модульное тестирование Dispose() с использованием отражения?

var o = new ObjectUnderTest(); 
o.Dispose(); 
Assert.IsFalse(ObjectHasUndisposedDisposables(o)); 

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

Кто-нибудь это пробовал?

EDIT - Я не хочу, чтобы вводить Disposables в тестируемый класс.

+0

'' o.IsDisposed == true''? в вашем случае, вероятно: '' Assert.IsTrue (o.IsDispoed); '' –

+1

Я рекомендую использовать контейнер инъекции зависимостей. Управление одноразовыми зависимостями - это только одно преимущество. Если ваш класс создает больше зависимостей, которые ему нужно отслеживать и удалять, то это не зависит от абстракций. С DI ваш класс просто зависит от 'IWhatever'. Он не знает и не заботится о том, является ли этот класс доступным. –

ответ

6

Единственный способ проверить поведение, которое вы ищете без какого-либо рефакторинга в своем коде, - использовать инструменты ткачества кода. Например; Типовой изолятор, MsFakes и т. Д. ...

Следующий фрагмент кода показывает способ проверить поведение с помощью MsFakes:

[TestMethod] 
public void TestMethod1() 
{ 
    var wasCalled = false; 
    using (ShimsContext.Create()) 
    { 
     ForMsFakes.Fakes.ShimDependency.AllInstances.Dispose = dependency => 
     { 
      wasCalled = true; 
     }; 

     var o = new ObjectUnderTest(); 

     o.Dispose(); 
    } 

    Assert.IsTrue(wasCalled); 
} 

public class Dependency : IDisposable 
{ 
    public void Dispose() {} 
} 

public class ObjectUnderTest: IDisposable 
{ 
    private readonly Dependency _d = new Dependency(); 

    public void Dispose() 
    { 
     _d.Dispose(); 
    } 
} 
+0

Недостатком этого является не общий. Насколько я знаю, нет автоматического способа создания прокладки во время выполнения, поэтому вам нужно будет вручную генерировать «ShimXxxxxx» для каждого одноразового типа в классе. И если класс обновляется позднее с новым одноразовым типом, модульный тест не проверяет его. Это отнимает половину значения модульного теста, поскольку оно не защищает вас при добавлении новых типов. Однако есть работа вокруг ... –

+0

Do 'var fieldCount = typeof (ClassUnderTest) .GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where (x => typeof (IDisposable) .IsAssignableFrom (x.FieldType)) Count(). Assert.AreEqual (KnownFieldCount, fieldCount, «Число известных полей не соответствует классу. Обновите модульный тест с новыми классами dispose по мере необходимости»); 'как один из ваших тестов. Это приведет к сбою теста, если кто-то обновит класс с другим количеством одноразовых предметов, однако он не указывает, удаляете ли вы один из них и добавляете его. –

5

Нет общего способа справиться с этим. интерфейс IDisposeable не требует, чтобы вы отслеживали, был ли вызов dispose или нет.

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

[TestMethod] 
    public void TestAllDisposeablesAreDisposed() 
    { 
     var fooMock = new Mock<IFoo>(); 
     fooMock.Setup(x=>x.Dispose()) 
      .Verifiable("Dispose never called on Foo"); 

     var barMock = new Mock<IBar>(); 
     barMock.Setup(x => x.Dispose()) 
      .Verifiable("Dispose never called on Bar"); 

     using (var myClass = new MyClass(fooMock.Object, barMock.Object)) 
     { 
     } 

     fooMock.Verify(); 
     barMock.Verify(); 
    } 
+0

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

+0

Кроме того, я не против обертывать все одноразовые утилиты, которые я использую в другом интерфейсе (скажем, ITrackedDisposable), который отслеживает, когда они были удалены. – user6118784

2

Вы пытаетесь проверить, работает ли ваше IDisposable поведения в пределах данного класса, или вы хотите, чтобы проверить, является ли или нет на самом деле вызывает то, что управляет вашим одноразовым класс IDisposable? Это действительно, если вы не уверены, что его звонят, и вы хотите быть уверенным. (Я написал тесты для этого.)

Для этого вы можете создать простой класс, как это:

public class DisposeMe : IDisposable 
{ 
    public bool Disposed {get;set;} 
    public void Dispose() 
    { 
     Disposed = true; 
    } 
} 

Тогда вы можете просто утверждать, что Disposed верно, когда вы ожидаете, что утилизированы.

Например, я использовал это, когда создаю классы из контейнера DI, и хочу подтвердить, что, когда класс, созданный контейнером, освобождается, его удаленные зависимости будут удалены. Я не могу отслеживать это в конкретных конкретных классах, но я могу проверить, что контейнер DI делает то, что, как я полагаю, должен делать. (Это не тест, который я бы написал снова и снова. Как только я подтвердил ожидаемое поведение Виндзора, например, я не собираюсь писать для него единичные тесты.)

+0

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

+0

@ пользователь6118784 отражающий что? Каждый раз, когда вы хотите использовать отражение, замените его на «сделать все поля общедоступными». Таким образом, ваше предложение становится «Кажется, что * создание всех полей public * было бы более чистым способом достижения этого и могло бы повторно использоваться снова и снова на любом IDisposable». Как сделать общедоступные поля решить вашу проблему знания, когда кто-то назвал .Dispose() в одном из этих полей? Вы должны перехватить вызов, чтобы иметь возможность его обнаружить, и, не выходя из действительно экзотических вещей, таких как переписывание IL, вы должны использовать инъекцию для этого. –

+0

Чтобы ответить на ваши вопросы: 1) Отражение частных полей объекта ObjectUnderTest. 2) Если бы я опубликовал одноразовые поля, мой модульный тест мог бы легко протестировать, чтобы увидеть, были ли они удалены - я бы обернул все свои одноразовые вещи чем-то вроде ITrackedDisposable (который отслеживает, когда на них вызывается Dispose()), чтобы достичь это. – user6118784

0

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

E.g.

public class ObjectUnderTest : IDisposable 
{ 
    private IObjectNeedingDisposal _foo; 

    public IObjectNeedingDisposal Foo 
    { 
     get { return _foo ?? (_foo = new ObjectNeedingDisposal()); } 
     set { _foo = value; } 
    } 

    public void MethodUsingDisposable() 
    { 
     Foo.DoStuff(); 
    } 

    public void Dispose() 
    { 
     Foo.Dispose(); 
    } 
} 

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

+0

Да, я тоже рассматривал этот подход, но на самом деле мне не нравится добавлять публичные вещи в классы только для целей модульного тестирования. Это кажется неуклюжим, и это также код, который нельзя использовать повторно. Я знаю, что я хочу сделать, это может быть сделано с отражением - я надеялся, что кто-то уже написал это в хорошем многоразовом модуле, чтобы спасти меня от усилий :-). – user6118784

+0

Я согласен с тем, что это не идеально, используя свойства таким образом, я предпочитаю передавать их в конструкторе. Я знаю, что вы можете использовать отражение, чтобы получить частные поля - http://stackoverflow.com/questions/95910/find-a-private-field-with-reflection, но даже тогда вы бы пропустили это событие и не смогли бы сказать после того, как был вызван метод Disposed. –

 Смежные вопросы

  • Нет связанных вопросов^_^