С#: Unittesting с частными статическими членами?

У меня есть класс с такой конструкцией:

private static Dictionary<Contract, IPriceHistoryManager> _historyManagers = new Dictionary<Contract, IPriceHistoryManager>();

и скажем, 2 метода вроде:

 public void AddSth()
 {
    _historManagers.Add(new Contract(), new PriceHistoryManager());
 }

 public int CountDic()
 {
    return _historyManagers.Count(); 
 }

Проблема: При запуске unittests нет слова "reset" в словаре и когда я создаю несколько unittests с отдельными экземплярами класса, тогда "CountDic" дает непредсказуемые результаты, и я не могу протестировать список.

Вопрос: Это вообще считается "плохим" подходом, и если да: как сделать это лучше/более unittestable? А если нет: как это сделать лучше всего?

спасибо.

Ответ 1

Не бойтесь выставлять публичные операции для целей тестирования. Перефразируемый от "Искусства модульного тестирования" Роя Ошероу: Когда Toyota строит автомобиль, есть доступные пункты тестирования. Когда Intel строит чип, есть точки тестирования. Есть интерфейсы к автомобилю или чипу, которые существуют только для тестирования. Почему мы не делаем то же самое для программного обеспечения? Может ли метод ResetHistory() полностью уничтожить ваш API?

Если этот ответ yes, тогда создайте метод, но сделайте метод internal. Затем вы можете использовать сборку InternalsVisibleTo, чтобы разоблачить кишки в вашей библиотеке unit test. У вас есть метод, доступный для вас, созданный на 100% для тестирования, но никаких изменений в вашем публичном API нет.

Ответ 2

В вашем примере CountDic не является непредсказуемым: он должен вернуть еще один, чем перед вызовом AddSth().

Итак:

[Test]
public void Test()
{
    var item = new ClassUnderTest();
    int initialCount = item.CountDic();

    item.AddSth();

    int finalCount = item.CountDic();

    Assert.That(finalCount == initialCount + 1);
}

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