2009-11-23 6 views
4

Скажем, у нас есть этот гипер простой класс hierchy:Модульное тестирование дочерних классов

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

public class MoreMath : SomeMath 
{ 
    public int Subtract(int x, int y) 
    { 
     return x - y; 
    } 
} 

Должен ли я писать тесты для метода Add, когда я пишу тесты для MoreMath класса? Или мне нужно только заботиться об этом методе, когда я тестирую класс SomeMath? В более общем смысле: следует ли тестировать все методы класса, или я должен тестировать только «новые» методы?

Я могу придумать некоторые причины для обеих сторон. Например, при тестировании всех методов вы будете тестировать одно и то же несколько раз, что не так хорошо и может стать утомительным. Но если вы не проверите все методы, изменение в SomeMath может сломать использование MoreMath? Который был бы тоже плохой. Полагаю, это, вероятно, немного зависит от дела. Например, если он расширяет класс, который я контролирую или нет. Но в любом случае я новичок в новинках, поэтому мне интересно узнать, что люди умнее, чем я думаю об этом :-)

+1

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

+0

Хорошая точка. Отмечено :) – Svish

ответ

0

Прежде всего, я думаю, что есть, по крайней мере, два фактора, которые могут повлиять на ваше решение, чтобы сделать одну или другую сторону:

  • Какова цель наследования?
  • Какова цель данного теста?

В сценариях TDD я хотел бы написать один тестовый пример для MoreMath, который проверяет, что он происходит от SomeMath, а затем рассмотрит все элементы, унаследованные от SomeMath, которые будут закрыты.

Однако это означает, что с точки зрения дизайна это важный аспект дизайна, который MoreMath происходит от SomeMath. Это определенно будет иметь место, если вы используете SomeMath полиморфным способом. Однако это не так, если вы просто используете inheritetance для повторного использования (не рекомендуется, хотя).

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

С точки зрения обеспечения качества (QA) каждый член каждого класса должен быть проверен строго. Это означает, что вы должны повторить тестовый код для каждого дочернего класса, даже если тестовый код будет таким же, потому что вам нужно убедиться, что виртуальный метод не был переопределен неожиданным образом. Однако, чтобы оставаться сухим, вы можете написать их как Параметрированные тесты, или, возможно, использовать такой инструмент, как Pex.

Лично я редко получаю фазу QA. Обычно тестовый набор, созданный во время TDD, является адекватной защитной сетью ... но все зависит от типа программного обеспечения, которое вы создаете.

+0

Сделал это ответ, так как он покрывал больше углов :) – Svish

5

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

+1

Звучит как хорошая практика :) – Svish

+0

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

1

Я бы только протестировал метод Add класса SomeMath. Если MoreMath наследует его и не делает абсолютно ничего нового, тогда написание тестов для обоих будет чистым дублирующимся кодом, не более того. Всегда хорошо быть немного прагматичным с этими вещами imho.

0

Я бы сделал свой тестовый класс для MoreMath наследованием от тестового класса для SomeMath, тем самым наследуя все тесты этого класса. Это означает, что мне нужно написать дополнительные тесты для новых функций, но все функции подкласса полностью протестированы.

1

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

public interface ICalculator 
{ 
    public int Add(int a, int b) 
} 

public class Calculator : ICalculator 
{ 
    public int Add(int a, int b) { return a + b; } 
} 

public class TestCalculatorInterface 
{ 
    public abstract SomeMath GetObjectToTest(); 

    [Test] 
    public void TestAdd() 
    { 
     var someMath = GetObjectToTest(); 
     ... 
    } 
}  

[TestFixture] 
public class TestCalculator: TestCalculatorInterface 
{ 
    public virtual Calculator GetObjectToTest() { return new Calculator(); } 
} 

Мы не имели бы [TestFixture] атрибут тестирования базового интерфейса, но имеют [Test] атрибут всех методов тестирования. Класс TestCalculator - это [TestFixture], но наследует все тесты из базового класса, который оставляет подкласс ответственным исключительно за предоставление объекта для проверки этого интерфейса.

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

+0

Я на самом деле сделал что-то подобное себе однажды :) – Svish

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