Как установить ErrorCode для ManagementException?

Я хочу обработать исключение ManagementException только для определенного ErrorCode, и у меня возникли проблемы с написанием для него модульного теста. Обычно я бы написал тест примерно так:

Searcher search = MockRepository.GenerateMock<Searcher>(); 
// wrapper for ManagementObjectSearcher

...

search.Expect(s => s.Get()).Throw(new ManagementException());

...

Однако это не устанавливает ErrorCode на тот, который мне нужен, в частности, действительно, ManagementException не имеет конструктора, который устанавливает это значение.

Как это может быть сделано?

(Обратите внимание, что я использую RhinoMocks в качестве своего фиктивного фреймворка, но предполагаю, что он не зависит от фреймворка; все, что мне нужно знать, это как создать ManagementException с определенным значением ErrorCode. Также я нашел несколько ссылок на метод System.Management.ManagementException.ThrowWithExtendedInfo(ManagementStatus errorCode) онлайн, но это не общедоступно).


person jpoh    schedule 18.06.2009    source источник


Ответы (4)


Наименьшее усилие для преодоления этого препятствия было бы статическим вспомогательным / служебным методом, который использует отражение для взлома слота в требуемом коде ошибки. Используя самый лучший Reflector, я вижу, что есть частное поле «errorCode», которое устанавливается только через внутренние ctors, определенные в ManagementException. Так :)

public static class EncapsulationBreaker
   {
      public static ManagementException GetManagementExceptionWithSpecificErrorCode(ManagementStatus statusToBeStuffed)
      {
         var exception = new ManagementException();
         var fieldInfo = exception.GetType().GetField("errorCode", 
            BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.DeclaredOnly);
         fieldInfo.SetValue(exception, statusToBeStuffed);
         return exception;
      }
   }

Проверено, что это работает

[Test]
      public void TestGetExceptionWithSpecifiedErrorCode()
      {
         var e = EncapsulationBreaker.GetManagementExceptionWithSpecificErrorCode(ManagementStatus.BufferTooSmall);
         Assert.AreEqual(ManagementStatus.BufferTooSmall, e.ErrorCode);
      }

Хотя обычно я не одобряю размышления в тестах, это один из редких случаев, когда это необходимо/полезно.
HTH

person Gishu    schedule 06.07.2009

Получите класс от ManagementException и скройте реализацию кода ошибки своей собственной. Пусть ваш макет вернет этот класс.

person Robert    schedule 06.07.2009

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

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

person Yishai    schedule 06.07.2009

Я бы подклассировал ManagementException и в подклассе переопределил геттер ErrorCode (если обычные уровни защиты мешают вам сделать это, возможно, самоанализ может приблизить вас). Любой код, который обрабатывает ManagementException, но никогда не слышал о вашем конкретном подклассе, должен обрабатывать ваш подкласс "как если бы" это был ManagementException, который вы пытаетесь смоделировать для целей тестирования, в конце концов.

Изменить: вполне возможно, что ErrorCode просто нельзя переопределить (я ненавижу языки, которые НАСТОЛЬКО жесткие, что они могут перестать тестировать таким образом, но не могут отрицать их существование ;-). В этом случае вас может спасти внедрение зависимостей — DI — один из моих любимых шаблонов для тестирования.

Цель DI при тестировании состоит в том, чтобы отделить тестируемый код от жестких предположений, которые препятствовали бы тестируемости — и это именно то, что мы имеем здесь, хотя и в необычной форме. Ваш тестируемый код в настоящее время выполняет, скажем, x.ErrorCode для получения кода ошибки исключения x. Очень хорошо, вместо этого он должен делать getErrorCode(x), где getErrorCode — делегат, который обычно просто выполняет return x.ErrorCode; и у него должен быть установщик для делегата getErrorCode, чтобы в целях тестирования вы могли изменить его на делегата, который выполняет return 23 (или любое другое значение кода ошибки, которое вы хотите имитировать для тестирования).

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

person Alex Martelli    schedule 06.07.2009
comment
Это то, что RhinoMocks сделал бы, если бы я издевался над ManagementException. Однако ErrorCode не является переопределяемым свойством. Не могли бы вы уточнить, что вы подразумеваете под самоанализом? - person jpoh; 06.07.2009
comment
В собственном .NET это msdn.microsoft.com/en-us/ library/system.reflection.aspx - но если есть свойства, которые нельзя переопределить таким образом, внедрение зависимостей - ваша последняя и лучшая надежда - позвольте мне отредактировать мой ответ соответствующим образом, чтобы объяснить. - person Alex Martelli; 06.07.2009