Статические методы: когда и когда нет - PullRequest
8 голосов
/ 20 октября 2010

Я новичок в TDD и DDD, и у меня есть один простой вопрос, касающийся статических методов в целом.Большинство гуру TDD одним словом говорят, что статические методы плохие (и что мы должны забыть о создании тонны статических утилит, которые мы (um или I) использовали раньше, поскольку они не тестируемы. Я понимаю, почему онине поддается тестированию (отличную статью с разъяснениями можно найти здесь для тех, кто заинтересован, но я думаю, что я единственный нуб здесь :(), но мне было интересно, есть ли хорошее и чистое руководство по использованию статики из TDDточка зрения?

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

Редактировать: во время поиска ответа я обнаружил 2 другие хорошие темы, касающиеся использования статики (хотя это и не касается TDD), которые, я думаю, хорошо читаются для тех, кто интересуется (включая меня).

Ответы [ 3 ]

11 голосов
/ 20 октября 2010

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

Статические методы являются тестируемыми. Возьмите этот метод в качестве примера:

public static int Add(int x, int y)
{
    return x + y;
}

Вы можете проверить это, проверив, что возвращаемое значение соответствует ожидаемому на основании переданных аргументов.

Когда статические методы становятся проблематичными при тестировании, это когда вам нужно ввести макет.

Допустим, у меня есть некоторый код, который вызывает статический метод File.Delete(). Чтобы протестировать мой код, не полагаясь на файловую систему, я хотел бы заменить / смоделировать этот вызов тестовой версией, которая просто проверяет, что он был вызван из тестируемого кода. Это легко сделать, если бы у меня был экземпляр объекта, для которого вызывался Delete(). Большинство (все?) Фреймворк-фреймворков не может имитировать статические методы, поэтому использование статического метода в моем коде вынуждает меня тестировать его по-другому (обычно путем вызова реального статического метода).

Чтобы протестировать что-то подобное, я бы представил интерфейс:

interface IFileDeleter
{
    void Delete(string file);
}

Мой код будет тогда брать экземпляр объекта, который реализует этот интерфейс (либо в вызове метода, либо в качестве параметра в конструкторе), а затем вызывать его метод Delete(), чтобы выполнить удаление:

void MyMethod(string file)
{
    // do whatever...
    deleter.Delete(file);
}

Чтобы проверить это, я могу сделать макет интерфейса IFileDeleter и просто проверить, что был вызван его метод Delete(). Это устраняет необходимость иметь настоящую файловую систему как часть теста.

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

8 голосов
/ 21 октября 2010

Конечно, стоит избегать статики, но когда вы не можете или работаете с устаревшим кодом, доступна следующая опция.Исходя из ответа adrianbanks выше, допустим, у вас есть следующий код (извинения, его на Java, как я не знаю C #):

public void someMethod() {
   //do somethings
   File.delete();
   //do some more things
}

вы можете реорганизовать File.delete () в его собственныйметод, подобный следующему:

public void someMethod() {
   //do somethings
   deleteFile();
   //do some more things
}

//protected allows you to override in a subclass
protected void deleteFile() { 
   File.delete();
}

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

//Keep all the original functionality, but stub out the file delete functionality to 
//prevent it from using the real thing and while you're at it, keep a record that the
//method was called.
public class MockClass extends TheRealClass {
    boolean fileDeleteCalled = false;

    @Override
    protected void deleteFile()
        //don't actually delete the file, 
        //just record that the method to do so was called
        fileDeleteCalled = true;
    }

    public boolean fileDeleteCalled() { 
        return fileDeleteCalled; 
    }
}

и, наконец, в вашем подразделенииtest:

//This would normally be instantiated in the @Before method
private MockClass unitUnderTest = new MockClass();

@Test
public void testFileGetsDeleted(){
    assertFalse(unitUnderTest.fileDeleteCalled());
    unitUnderTest.someMethod();
    assertTrue(unitUnderTest.fileDeleteCalled());
}

Теперь вы выполнили все функции someMethod () без фактического удаления файла, и у вас все еще есть возможность увидеть, был ли вызван этот метод.

7 голосов
/ 20 октября 2010

В общем случае, если метод:

  • Медленный
  • Длинный
  • Содержит сложную логику
  • Использует файловую систему
  • Соединяется с базой данных
  • Вызывает веб-сервис

, затем избегает его статического состояния.(См. Ответ @adrianbanks для превосходного обсуждения причин этого и альтернатив.)

По сути, делайте его статичным, только если это удобный метод короткой памяти (как многие методы расширения).

...