2008-09-04 2 views
8

Итак, я читал блог тестирования Google, и он говорит, что глобальное состояние плохое и затрудняет запись тестов. Я считаю, что мой код трудно проверить прямо сейчас. Итак, как мне избежать глобального состояния?Как я могу избежать глобального состояния?

Самые большие вещи, которые я использую глобальное состояние (как я понимаю), - это управление ключевыми частями информации между нашей средой разработки, принятия и производства. Например, у меня есть статический класс с именем «Globals» со статическим членом, называемым «DBConnectionString». Когда приложение загружается, он определяет, какую строку подключения загружать, и заполняет Globals.DBConnectionString. Я загружаю пути к файлам, имена серверов и другую информацию в классе Globals.

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

Есть ли способ управлять информацией о состоянии? (Или я неправильно понимаю глобальное состояние?)

ответ

10

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

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

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

1

Отличный первый вопрос.

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

Проблема, которую вы описываете, не выглядит как глобальное состояние. По крайней мере, не изменяемое состояние. Скорее, то, что вы описываете, похоже на то, что часто называют «проблемой конфигурации», и имеет ряд решений. Если вы используете Java, вам может понадобиться взглянуть на легкие инъекционные рамки, например Guice. В Scala это обычно решается с помощью implicits. На некоторых языках вы сможете загрузить другую программу для настройки своей программы во время выполнения. Так мы использовали для настройки серверов, написанных на Smalltalk, и я использую оконный менеджер, написанный в Haskell, который называется Xmonad, конфигурационный файл которого представляет собой еще одну программу Haskell.

2

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

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

public interface ISettingsProvider 
{ 
    string ConnectionString { get; } 
} 

public class TestSettings : ISettingsProvider 
{   
    public string ConnectionString { get { return "testdatabase"; } }; 
} 

public class DataStuff 
{ 
    private ISettingsProvider settings; 

    public DataStuff(ISettingsProvider settings) 
    { 
     this.settings = settings; 
    } 

    public void DoSomething() 
    { 
     // use settings.ConnectionString 
    } 
} 

В реальности вы больше всего вероятно, читаются из файлов конфигурации в вашей реализации.Если вы задумаетесь о том, что полноценная инфраструктура DI с возможностью замены конфигураций - это путь, но я думаю, что это, по крайней мере, лучше, чем использование Globals.ConnectionString.

0

Пример внедрения зависимостей в настройке MVC, здесь идет:

index.php

$container = new Container(); 
include_file('container.php'); 

container.php

container.add("database.driver", "mysql"); 
container.add("database.name","app"); 

...

$container.add(new Database($container->get('database.driver', "database.name")), 'database'); 
$container.add(new Dao($container->get('database')), 'dao'); 
$container.add(new Service($container->get('dao'))); 
$container.add(new Controller($container->get('service')), 'controller'); 

$container.add(new FrontController(),'frontController'); 

index.php продолжается здесь:

$frontController = $container->get('frontController'); 
$controllerClass = $frontController->getController($_SERVER['request_uri']); 
$controllerAction = $frontController->getAction($_SERVER['request_uri']); 
$controller = $container->get('controller'); 
$controller->$action(); 

И там у вас есть, контроллер зависит от объекта обслуживания слоя, который зависит от дао (объект доступа к данным) объект, который зависит от объекта базы данных с зависит драйвером базы данных, имя и т.д.