2014-09-24 2 views
2

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

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

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

Первый вопрос: Является ли это хорошей практикой в ​​TDD? Я спрашиваю об этом, потому что этот класс имеет 6 зависимостей до сих пор, и он становится очень повторяющимся, а также тесты становятся довольно длинными, поскольку я должен издеваться над всеми зависимостями для каждого теста. То, что я делаю, это просто копировать и вставлять каждый раз и просто изменять проверяемую ссылку зависимостей.

Второй вопрос: мой способ создания учетной записи выполняет такие действия, как проверка пройденной модели, вставка данных в 3 разные таблицы или четвертую таблицу, если присутствует определенный набор значений и отправка электронной почты. Что я должен здесь проверить? Я до сих пор тестировал, проверяет, будет ли выполняться проверка модели, если метод Add для каждого репозитория будет вызван, и в этом случае я использую метод обратного вызова Moq для изделенного репозитория для сравнения каждого добавляемого свойства в репозиторий против те, которые я прошел по модели.

Что-то вроде:

userRepository 
     .Setup(r => r.Add(It.IsAny<User>())) 
     .Callback<User>(u => 
      { 
       Assert.AreEqual(model.Email, u.Email); 
       Assert.IsNotNull(u.PasswordHash); 
       //... 
      }) 
     .Verifiable(); 

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

+2

«Это хорошая TDD?» это действительно неправильный вопрос; более полезный вопрос: «Это работает для меня и моего проекта?» Методологии, подобные TDD, несмотря на то, что фанатики хотели бы, чтобы вы думали, означает конец, а не конец сами по себе. – Casey

+0

-1 Это не вопрос TDD, это вопрос дизайна модульного тестирования. – gurun

+0

Вот мои 2 цента. Я не буду раскрывать ваши зависимости как свойства. Я видел такие вещи, которые злоупотребляли разработчики. в прошлом, когда они используют зависимость, которая подвергает зависимость в своих классах (совсем не совсем).Если вам становится сложно собирать свой класс SUT, вы можете использовать фабричный метод для создания класса SUT или лучше использовать шаблон тестового построителя. – Andrew

ответ

4

Целью тестирования является поиск ошибок.

У вас действительно будет ошибка, где свойство существует, но не инициализируется значением из конструктора?

public class NoNotReally { 
    private IMyDependency1 _myDependency; 
    public IMyDependency1 MyDependency {get {return _myDependency;}} 

    public NoNotReally(IMyDependency dependency) { 
     _myDependency = null; // instead of dependency. Really? 
    } 
} 

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

В моем примере зависимость будет существовать, потому что это необходимо, а не потому, что какой-то искусственный модульный тест требовал, чтобы он был там.

1

Обычно в модульных тестах (особенно в TDD) вы не будете тестировать каждое утверждение в тестируемом классе. Основная цель модульных тестов TDD состоит в том, чтобы проверить бизнес-логику класса, а не информацию инициализации.

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

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

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

3

Вы говорите, что запись этих тестов повторяется. Я говорю, что вы чувствуете большую пользу TDD. На самом деле это не программное обеспечение с меньшими ошибками и не написание лучшего программного обеспечения, потому что TDD не гарантирует (по крайней мере, не по своей сути). TDD заставляет вас думать о дизайнерских решениях и принимать проектные решения. . Время. (И уменьшите время отладки.) Если вы чувствуете боль при выполнении TDD, это обычно потому, что дизайнерское решение возвращается, чтобы укусить вас. Затем пришло время переключиться на вашу рефакторинговую шляпу и улучшить дизайн.

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

Что касается проверки того, установлены ли свойства. Если я правильно вас понял, вы выставили эти свойства только ради тестирования? В этом случае я бы посоветовал это сделать. Предположим, у вас есть класс с параметром конструктора и есть тест, который утверждает construtor должен бросить на нуль аргументы:

public class MyClass 
{ 
    public MyClass(MyDependency dependency) 
    { 
     if (dependency == null) 
     { 
      throw new ArgumentNullException("dependency"); 
     } 
    } 
} 

[Test] 
public void ConstructorShouldThrowOnNullArgument() 
{ 
    Assert.Catch<ArgumentNullException>(() => new MyClass(null)); 
} 

(класс TestFixture опущен)

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

[Test] 
public void TestSomeBusinessFunctionality() 
{ 
    MyDependency mockedDependency; 

    // setup mock 
    // mock calls on mockedDependency 

    MyClass myClass = new MyClass(mockedDependency); 

    var result = myClass.DoSomethingOrOther(); 

    // assertions on result 
    // if necessary assertion on calls on mockedDependency 
} 

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

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

Это может привести к чрезмерной инженерии! Поэтому следите за этим, и у вас возникнет чувство, когда нужно противостоять стремлению к большей косвенности. ;)

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

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