Осуществление верификации
Для запуска проверки класса TestCurrencyTrader используем следующий код:
TestCurrencyTrader els = new TestCurrencyTrader(); :1s.InitializeExchangeRate() ;
Измененный проверочный код подразумевает создание экземпляра класса -eszCurrencyTrader и последующий вызов метода InitializeExchangeRate О . Но про¬верка ли это? В конце концов, метод InitializeExchangeRate () не имеет ни параме¬тров, ни возвращаемого значения. Считайте это посылкой письма по обычной почте. Зы не знаете наверняка, придет ли письмо, но вероятно, придет. Проверка "вероятно, ггр идет" — это действительно плохая идея.
Необходимо переместить код верификации из проверочной функции в класс TrstCurrencyTrader следующим образом:
;lass TestCurrencyTrader : CurrencyTrader
public void InitializeExchangeRate() I
ExchangeRate = 10 0.0;
if (ExchangeRate != 100.0)
{ throw new Exception("100 . 0 verification failed") ;
} }
Код, выделенный полужирным шрифтом, — это код верификации, используемый ИЯ проверки наличия у ExchangeRate того же значения, которое ему было присвоило.
На заметку. Проверки, которые мы используем, становятся все более сложными, и у вас возника¬ет вопрос: "Почему все делается так?" В этой книге мы создаем собственную инфраструктуру проверки. Но вы не обязаны поступать именно так. Для создания проверок вы могли бы ис¬пользовать инфраструктуру проверки типа NUNIT (http://www.nunit.org) или профессио¬нальные инструменты Microsoft Visual Studio Professional. Но здесь я хочу продемонстрировать, <ак использовать язык С# без инструментов проверки. Изучив, как записывать проверки само¬му и с самого начала, надеюсь, вы поймете, чего ожидать от инфраструктур проверки.
Использование условных операторов
Наличие кода верификации внутри класса приемлемо в контексте проверочного пасса TestCurrencyTrader. Однако проблема контролируемости все еще присутствует I тех классах, которые не предоставляют свое состояние.
Чтобы понять проблему, давайте вернемся к коду разогрева духовки. Давайте пере-~::;лем класс Oven так, чтобы включить верификацию, как здесь.
:lass Oven
private int _temperature;
public void SetTemperature( int temperature)
temperature = temperature;
if ( _temperature != 100.0) { throw new Exception( "100.0 verification failed");
}
}
public bool AreYouPreHeated () { // Проверить, соответствует ли температура духовки необходимой return false; } )
Код, выделенный полужирным шрифтом, иллюстрирует верификацию, очень пох: -жую на ту, что мы использовали в классе CurrencyTrader, который в данном случае проверяет параметр температуры на определенное значение. В то время как верифика¬ция полезна при проверке специфического значения, она не очень подходит для обще:: случая. Тот способ, которым написан код, допускает единственное значение для темпе¬ратуры — это 100, 0; все остальное приведет к исключению.
Чтобы обойти эту проблему, используем условные операторы (conditional statemerc языка С#, представляющие собой специальные ключевые слова, позволяющие раз¬работчику определить, откомпилирован ли фрагмент исходного кода. Следующее — это пример исходного кода, который включает условные операторы:
class TestCurrencyTrader : CurrencyTrader { public void InitializeExchangeRate () { ExchangeRate = 100.0;
#if INTEGRATEJTESTS if (ExchangeRate != 100.0) {
throw new Exception("100.0 verification failed"); }
#endif }
Условный оператор всегда начинается с символа #. за которым следует ключевое слово, например if. Между ключевыми словами #if и #endif находится код, компи¬лируемый условно. Это называется директивой препроцессора (preprocessor directive В этом примере происходит условная компиляция кода, содержащегося внутри блока
#if и #endif, если значение INTEGRATEJTESTS истинно.
Вы можете определить идентификатор компиляции, такой как INTEGRATE__TEST: в исходном коде или в IDE. В среде Visual C# Express можно определить идентификатс -
INTEGRATE__TESTS следующим образом.
1. Щелкните правой кнопкой мыши на своем проекте и в появившемся контекст¬ном меню выберите пункт Properties (Свойства).
2. Перейдите на вкладку Build (Построение).
3. Введите в поле Conditional Compilation Symbols (Символы условной компиляции значение INTEGRATE_TESTS.
Использование для верификации частичных классов
Условная компиляция полезна, когда вы хотите включать или удалять код, в зави¬симости от конфигурации. Однако некоторым программистам категорически не пс-нравится наличие кода условной компиляции внутри функции, поскольку это стало бь: кошмаром поддержки. Другое решение заключается в том, чтобы использовать ключе¬вое слово partial class вместе с операторами условной компиляции.
До сих пор во всех примерах, когда мы определяли класс, все его методы и код объ-
являлись внутри фигурных скобок класса. Используя частичные (partial) классы, мож-
но объявить класс в нескольких местах. Когда компилятор С# компилирует исходный
код, отдельные части класса будут собраны в единое определение класса. Для нашего
проверочного кода мы можем создать реализацию частичного класса и условно ком-
пилировать проверочную часть реализации класса. Ниже приведен измененный класс
Test CurrencyTrader, применяемый для проверки состояния, без предоставления к нему доступа извне.
:;r~ial class TestCurrencyTrader : CurrencyTrader public void InitializeExchangeRate() TxchangeRate = 100.0;
*if !INTEGRATE_TESTS
partial class TestCurrencyTrader : CurrencyTrader
public void VerifyExchangeRate(double value)
if (ExchangeRate != value) { throw new Exception("ExchangeRate verification failed");
}
}
#endif
Мтючевое слово partial предшествует ключевому слову class. Первая реализация
т;-::а TestCurrencyTrader — это пример непредоставления доступа к состоянию.
' рая реализация класса TestCurrencyTrader, которая объявлена в контексте опера-
словной компиляции, содержит метод VerifyExchangeRate (). Этот метод верифи-
_ ::: проверяет свойство ExchangeRate на специфическое значение.
*з заметку. Вы можете использовать частичные классы только в контексте одной сборки —«клю¬чевое слово partial нельзя использовать для нескольких сборок. Когда я говорю "одиночная : ::эка", я имею в виду откомпилированные части исходного кода .NET, представленного в та¬ге ". "На старт, внимание, марш!" Другими словами, если вы определяете частичный класс i :.'5лиотеке, то все части частичного класса должны быть определены в этой библиотеке.
Частичные классы существенно упрощают разделение функциональных возможно-
К : ~о разным файлам исходного кода, чтобы изменение одного файла исходного кода
■ мияло на другие файлы исходного кода. Этот пример демонстрирует использование
ганых классов для манипулирования внутренним состоянием класса без наруше-
~ правила: "Не предоставлять внутреннее состояние". Частичные классы можно так-
: пользовать в контексте генераторов объектного кода, где один файл исходного кода
1 гржит специальный код, а другой файл исходного кода содержит код генератора.