2011-02-07 4 views
3

Я уже давно являюсь поклонником библиотеки Python по той простой причине, что комментарии могут быть полезны, но также полезны при утверждении правильного поведения. Недавно я наткнулся на (по-видимому) малоизвестный System.Diagnostics.ConditionalAttribute для .NET. Это можно легко использовать, чтобы вы могли определять тесты для ваших методов класса внутри самого класса. Вот простой пример:Должны ли модульные тесты быть объявлены встроенными?

using System.Diagnostics; 
using NUnit.Framework; 

namespace ClassLibrary1 
{ 
    public class Class1 
    { 
     public static int AddTwoNumbers(int x, int y) 
     { 
      return x + y; 
     } 

     [Conditional("DEBUG")] 
     [TestCase(1, 1, 2)] 
     [TestCase(1, 2, 3)] 
     [TestCase(2, 1, 3)] 
     [TestCase(11, 7, 18)] 
     public static void TestAddTwoNumbers(int x, int y, int sum) 
     { 
      int actual = AddTwoNumbers(x, y); 
      Assert.AreEqual(sum, actual); 
     } 
    } 
} 

Делая это, вы можете создать отладки сборки, которая будет запускать тесты и сборочное производство со всеми его раздели, подобно тому, как FAKE can build projects. Вопрос в том, должен ли ты? Это хорошая практика? Почему или почему нет?

Вы также обнаружите, что этот пример фактически не работает, как я ожидаю. Я не уверен, почему атрибут позволяет скомпилировать метод тестирования. Есть идеи о том, почему?

ответ

4

ConditionalAttribute не изменяется независимо от того, скомпилирован ли сам метод.Он изменяет ли вызовы на метод сгенерирован или нет.

Например, Debug.WriteLine применяет к нему Conditional("DEBUG") - но код по-прежнему присутствует. Дело в том, что код клиент, который содержит вызовы Debug.WriteLine, будет игнорировать эти вызовы при построении без символа препроцессора DEBUG.

условно компилировать весь метод, вы будете использовать:

#if DEBUG 
... 
#endif 

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

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

+0

Спасибо, Джон. Я только что осознал свою ошибку, ожидая, что метод не будет скомпилирован. –

+0

Отмечая это как ответ о том, что не тестировать фактический производственный код, он должен быть точкой торможения. –

+0

@Jon; Может ли следующее предложение изменить вашу оценку вообще: (1) поместить тестовые сценарии в строку с конструкцией «#if XXX» (2) во время сборки сборки с включенными тестами и исключенными (= prod) (3) использовать только сборку отладки для запуска содержащихся тестовых примеров со второй сборкой, которая не включает их. Преимущества: (1) вы можете иметь свои тестовые примеры в непосредственной близости от проверяемого кода (2) вы можете развернуть тестируемую сборку, которая не загромождена испытаниями – jerryjvl

2

(ИМО) Абсолютно нет.

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

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

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

В вашем случае область видимости - это один класс. Если вам нужно потянуть другие классы (макетные реализации и т. Д.), То вы бы их положили?

+0

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

+0

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

2

Для этого подхода будут плюсы и минусы.

С одной стороны, вы сможете протестировать внутренние устройства. Некоторые утверждают, что это хорошо. Другие утверждают, что вы должны тестировать только открытый интерфейс. Лично я считаю, что иногда приходится тестировать внутренности (если не по какой-то другой причине, кроме изолирования определенного поведения), но это то, что вы, вероятно, можете достичь с помощью InternalsVisibleTo.

С другой стороны, вы сможете протестировать внутренние устройства. Ваш тестовый код будет вести себя так, как если бы он принадлежал сборке. Мое личное мнение состоит в том, что оно не является частью приложения, и его не должно быть. Считайте это формой «разделения проблем». Тесты тестов и приложения выполняют те тесты, которые тестируют. Но тест должен знать как можно меньше о деталях реализации. Если тесты проходят внутри, им слишком легко узнать все подробные детали. Если детали реализации позже меняются, тесты становятся недействительными и должны быть перезаписаны.

Лично я предпочитаю внешнюю сборку. Это не только подтверждает, что мои тесты ТОЛЬКО тестируют программное обеспечение, но это заставляет меня задавать себе вопрос, как писать программное обеспечение, чтобы оно могло быть проверено внешним источником. Это приводит к улучшению дизайна программного обеспечения в целом. Я еще не пожалел об этом.

+0

Очень хорошие аргументы. Спасибо, что посмотрели на обе стороны. Я согласен, что разделение - хорошая идея. –

0

Я согласен с большинством других ответов: лучше проводить отдельные тесты устройства. Особенно примечательно то, что Джон говорит о том, как работает ConditionalAttribute: в частности, код все еще существует, его просто не вызывают.

Однако, я думаю, вы хотели бы Code Contracts. Они фактически используют перезаписывающий , который изменяет ваш код после его компиляции. Это позволяет легко настроить сборку Debug, которая включает в себя все виды проверок времени выполнения вместе с сборкой Release, которая не имеет ни одного из этих кодов. Вы также можете установить расширение VS, которое отображает предварительные и пост-условия как всплывающие окна в редакторе кода.

У меня есть короткий getting started post в моем блоге, где я прохожу через создание CC для библиотеки. Мой стиль - иметь сборку Debug (с полными проверками) и сборку Release (без проверок, но включая отдельную dll, содержащую предварительные условия).

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

+0

Я знаю и вроде Код Контракты. Это была просто идея получить тесты ближе к их источнику. –

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