Создание модульных тестов для обеспечения неизменности - PullRequest
6 голосов
/ 24 марта 2011

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

// "This class is immutable, don't change this when adding new features to it."

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

(Использование C # 2.0 и NUnit, если это важно для кого-либо).

Ответы [ 4 ]

10 голосов
/ 07 марта 2014

Пример для подтверждения моего комментария о том, как вы можете использовать FieldInfo.IsInitOnly рекурсивно для проверки на неизменность.

Могут быть и другие особые случаи, например, как я обработал string, но это будетя даю только ложные отрицания, то есть скажу, что что-то изменчиво, а не наоборот.

Логика такова, что каждое поле должно быть readonly и само должно быть неизменным типом.Обратите внимание, что он не справится с самоссылочными типами или циклическими ссылками.

using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ImmutableTests
{
    [TestClass]
    public class AssertImmutableTests
    {
        [TestMethod]
        public void Is_int_immutable()
        {
            Assert.IsTrue(Immutable<int>());
        }

        [TestMethod]
        public void Is_string_immutable()
        {
            Assert.IsTrue(Immutable<string>());
        }

        [TestMethod]
        public void Is_custom_immutable()
        {
            Assert.IsTrue(Immutable<MyImmutableClass>());
        }

        [TestMethod]
        public void Is_custom_mutable()
        {
            Assert.IsFalse(Immutable<MyMutableClass>());
        }

        [TestMethod]
        public void Is_custom_deep_mutable()
        {
            Assert.IsFalse(Immutable<MyDeepMutableClass>());
        }

        [TestMethod]
        public void Is_custom_deep_immutable()
        {
            Assert.IsTrue(Immutable<MyDeepImmutableClass>());
        }

        [TestMethod]
        public void Is_propertied_class_mutable()
        {
            Assert.IsFalse(Immutable<MyMutableClassWithProperty>());
        }

        private static bool Immutable<T>()
        {
            return Immutable(typeof(T));
        }

        private static bool Immutable(Type type)
        {
            if (type.IsPrimitive) return true;
            if (type == typeof(string)) return true;
            var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            var isShallowImmutable = fieldInfos.All(f => f.IsInitOnly);
            if (!isShallowImmutable) return false;
            var isDeepImmutable = fieldInfos.All(f => Immutable(f.FieldType));
            return isDeepImmutable;
        }
    }

    public class MyMutableClass
    {
        private string _field;
    }

    public class MyImmutableClass
    {
        private readonly string _field;
    }

    public class MyDeepMutableClass
    {
        private readonly MyMutableClass _field;
    }

    public class MyDeepImmutableClass
    {
        private readonly MyImmutableClass _field;
    }

    public class MyMutableClassWithProperty
    {
        public string Prop { get; set; }
    }
}
8 голосов
/ 24 марта 2011

Вы можете проверить, что класс запечатан, и с помощью проверки отражения, что каждое поле доступно только для чтения (используя FieldInfo.IsInitOnly).

Конечно, это обеспечивает только мелкая неизменность - это не помешает кому-то поместить туда поле List<int>, а затем изменить содержимое списка.

3 голосов
/ 24 марта 2011

В этом посте есть некоторые идеи. Хотя, похоже, нет верного пути.

Как узнать, является ли класс неизменным в C #

2 голосов
/ 24 марта 2011

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

Одной из таких проверок является проверка на неизменность. Например, у меня есть маркерный интерфейс IImmutable, и NDepend дает сбой моей сборки, если какой-либо тип имеет этот интерфейс, но является изменяемым, используя следующий запрос:

WARN IF Count > 0 IN SELECT TYPES WHERE 
  Implement "MyCompany.MyAssemblies.Dto.IImmutable" AND
  !IsImmutable

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

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

См. здесь для получения дополнительной информации о том, что он на самом деле делает и как.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...