Как вы должны издеваться над классами, которыми вы не владеете в Objective C / Cocoa? (например, NSDate) - PullRequest
3 голосов
/ 18 февраля 2012

Есть правило, которое гласит

Только издевательские объекты, которыми вы владеете.

Мне кажется, я понимаю причину этого - насмешливые классы, предоставляемые фреймворками, могут привести к странному поведению.

Какая альтернатива?

Как насчет того, когда вам нужна фиктивная дата с использованием NSDate?

В прошлом я перенес метод date из NSDate в свой собственный класс - NSDateMock - но что-то подсказывает мне, что это действительно неправильно!

Одно решение - оболочка?

Создайте обертку вокруг NSDate, но тогда вам придется реализовать все ее методы.

Или вы бы просто реализовали те, которые использовали? Это кажется грязным способом сделать это.

Мой вопрос

Какой хороший способ издеваться над классами, которыми вы не владеете, например NSDate?

Обновление 1

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

Но возникает вопрос: в случае NSDate вы вводите класс-обертку в каждый класс, которому нужно знать дату ?! Конечно, нет ...

Обновление 2

На этот вопрос было несколько хороших ответов, но я все еще требую других ответов - наверняка, должен быть определенный способ сделать это? Я до сих пор не понимаю, как категории дадут мне фиктивный объект, которым я могу управлять.

Ответы [ 4 ]

3 голосов
/ 09 марта 2012

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

Я считаю, что частичные макеты являются запахом кода, поскольку они показывают, что ваш тестируемый класс (CUT) не тестируется -> время на рефакторинг.

Я занимался этим двумя способами в прошлом:

1.) Передайте DateProvider (который является интерфейсом) в конструктор CUT

interface DateProvider 
    date timenow()
end

Во время тестирования это ваш MockDateProvider, который вы можете изменить в своем тестовом классе. Я могу использовать открытое статическое поле, которое я могу изменить в своих тестах

class MockDateProvider :: DateProvider
   public static field fakeDate   
   date timenow () 
      return fakedate
   end
end   

В реальной системе просто используется метод создания даты.

class RealDateProvider :: DateProvider
   date timenow()
      return LibraryDateMaker.newDate()
   end
end

Я мог бы создать 2 конструктора, один из которых использует этот интерфейс, а другой использует RealDateProvider без каких-либо производственных классов, передающих объект.

Это предпочтительный ОО способ ведения дел. Я думаю!

2.) Создайте собственный поставщик статических дат, который можно переопределить для поведения.

Вместо LibraryDateMaker.newDate () вы создаете статический ConfigurableDateMaker.newDate (). Он использует те же объекты, что и 1, но имеет установщик, позволяющий вам при необходимости изменить поведение провайдера макета. По умолчанию реальное.

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

Вкратце, CUT вызывает ConfigurableDateMaker.newDate (). Который по умолчанию возвращает реальную дату, но в вашем тестовом классе вы можете настроить поведение на использование макета перед вызовом CUT.

class ConfigurableDateMaker
  public static DateProvider provider
   static date timenow()
      return provider.newDate()
   end
   // add the 2 provider classes as inner classes in here
end

Надеюсь, в этом есть какой-то смысл.

1 голос
/ 19 февраля 2012

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

Вы можете создать удобный метод в своем тестовом классе для генерации необходимых вам дат:

-(NSDate *)dateFromString:(NSString *)dateString {
    // short style is 2/20/12 10:05 AM
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterShortStyle;
    formatter.timeStyle = NSDateFormatterShortStyle;
    return [formatter dateFromString:dateString];
}

-(void)testTimer {
    NSDate *startTime = [self dateFromString:@"2/20/12 11:58 PM"];
    NSDate *endTime = [self dateFromString:@"2/21/12 12:02 AM"];
    // pass dates into your timer method
}

В качестве альтернативы, если вам нужно протестировать метод, который получает текущее время напрямую, вы можетепредоставьте тонкую обертку вокруг [NSDate date]:

-(NSDate *)now {
    return [NSDate date];
}

-(void)startTimer {
    self.startTime = [self now];
}

-(void)stopTimer {
    NSDate *endTime = [self now];
    // ... do something 
}

Затем, в своем тесте, вы дразните свою тонкую обертку с частичной насмешкой:

-(void)testStopTimer {
    Timer *timer = [[Timer alloc] init];
    timer.startTime = [self dateFromString:@"2/20/12 11:58 PM"];

    id mockTimer = [OCMockObject partialMockForObject:timer];
    [[[mockTimer stub] andReturn:[self dateFromString:@"2/21/12 12:01 AM"] now];

    [timer stopTimer];

    // verify expected behavior...
}
1 голос
/ 23 февраля 2012

Я не уверен, что это то, что вы пытаетесь сделать, но когда я столкнулся с подобными ситуациями, я сделал что-то вроде следующего:

  1. Всякий раз, когда у меня есть класс, который зависит от некоторого класса, который я, возможно, захочу позже высказать, Я инкапсулирую доступ к этому классу в методе моего класса. Например, вместо вызывая [NSDate date], чтобы получить текущее время, я бы реализовал метод с именем currentTime. Реализация этого метода просто вернет [NSDate time].

  2. Я выполняю свой тест для объекта «частичного макета», созданного из моего тестируемого объекта, заглушение метода currentTime для возврата моего предпочтительного значения теста вместо Реальная реализация currentTime с момента запуска.

Я использую OCMock Framework для всего этого, так что это выглядит примерно так:

NSDate *testDate = // put whatever you want here
id mockTestObject = [OCMock partialMockForObject:testObject];
[mockTestObject stub] andReturn:testDate] currentDate];

[mockTestObject doSomethingThatUsersCurrentTime];
[mockTestObject verify];

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

1 голос
/ 19 февраля 2012

Вместо этого можно создать категорию NSDate и реализовать дополнительный метод, такой как

[NSDate mockDate];

Нет необходимости создавать целый новый класс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...