Насмешливые методы, которые вызывают другие методы. Все еще поражают базу данных. Могу ли я избежать этого? - PullRequest
2 голосов
/ 15 июня 2010

Было решено написать несколько модульных тестов, используя moq и т. Д. Это много унаследованного кода c #

(это вне моего контроля, поэтому я не могу ответить на этот вопрос)

Теперь, как вы справляетесь со сценарием, когда вы не хотите получать доступ к базе данных, но вы косвенно по-прежнему обращаетесь к базе данных?

Это то, что я собрал, это не настоящий код, но дает вам представление.

Как бы вы справились с такого рода сценарием?

По сути, вызов метода на макете интерфейса все еще вызывает dal, поскольку внутри этого метода есть другие методы, не являющиеся частью этого интерфейса? Надеюсь, это понятно

         [TestFixture]
            public class Can_Test_this_legacy_code
            {
                [Test]
                public void Should_be_able_to_mock_login()
                {
                    var mock = new Mock<ILoginDal>();
                    User user;
                    var userName = "Jo";
                    var password = "password";
                    mock.Setup(x => x.login(It.IsAny<string>(), It.IsAny<string>(),out user));

                    var bizLogin = new BizLogin(mock.Object);
                    bizLogin.Login(userName, password, out user);
                }
            }

            public class BizLogin
            {
                private readonly ILoginDal _login;

                public BizLogin(ILoginDal login)
                {
                    _login = login;
                }

                public void Login(string userName, string password, out User user)
                {
                    //Even if I dont want to this will call the DAL!!!!!
                    var bizPermission = new BizPermission();
                    var permissionList = bizPermission.GetPermissions(userName);

                    //Method I am actually testing
                    _login.login(userName,password,out user);
                }
            }
            public class BizPermission
            {
                public List<Permission>GetPermissions(string userName)
                {
                    var dal=new PermissionDal();
                    var permissionlist= dal.GetPermissions(userName);
                    return permissionlist;
                }
            }

            public class PermissionDal
            {
                public List<Permission> GetPermissions(string userName)
                {
                    //I SHOULD NOT BE GETTING HERE!!!!!!
                    return new List<Permission>();
                }
            }

            public interface ILoginDal
            {
                void login(string userName, string password,out User user);
            }

            public interface IOtherStuffDal
            {
                List<Permission> GetPermissions();
            }

            public class Permission
            {
                public int Id { get; set; }
                public string Name { get; set; }
            }

Есть предложения? Я упускаю очевидное? Это Нестабильный код?

Очень, очень благодарен за любые предложения.

1 Ответ

5 голосов
/ 15 июня 2010

Как и сейчас, BizLogin не тестируется, так как он непосредственно создает экземпляр BizPermission, который, в свою очередь, создает экземпляр PermissionDal, который затем попадает в БД.

Лучшим решением будет рефакторинг BizLogin для замены прямой реализации BizPermission вызовом фабрики (метода) или Внедрение зависимостей . Из вашего поста мне не ясно, можете ли вы реорганизовать код - если это так, это предпочтительное решение.

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

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

Как заменить файлы классов тестовыми реализациями

(с использованием терминологии Java - я надеюсь, что это достаточно ясно и для C # ...) Основная идея состоит в том, что среда выполнения ищет классы на пути к классам и загружает первое подходящее определение класса, которое находит на пути к классам. Таким образом, вы можете создать фиктивную реализацию BizPermission в исходной папке вашего модульного теста, в том же пакете и с тем же интерфейсом, что и в оригинале. Затем скомпилируйте его, например, в папка test-classes (тогда как ваш производственный код скомпилирован, например, classes). Теперь, если вы настроили свой путь к классу теста таким образом, чтобы test-classes предшествовал classes, среда выполнения загрузит поддельный класс BizPermission при запуске ваших тестов, а не исходный, когда BizLogin попытается создать экземпляр этого класса.

Пример рефакторинга для использования фабричного метода

public class BizLogin
{
    private readonly ILoginDal _login;

    public BizLogin(ILoginDal login)
    {
        _login = login;
    }

    protected BizPermission getBizPermission()
    {
        return new BizPermission();
    }

    public void Login(string userName, string password, out User user)
    {
        var bizPermission = getBizPermission();
        var permissionList = bizPermission.GetPermissions(userName);

        //Method I am actually testing
        _login.login(userName,password,out user);
    }
}

В тестовом коде:

public class FakeBizPermission implements BizPermission
{
    public List<Permission>GetPermissions(string userName)
    {
        // produce and return fake permission list
    }
}

public class BizLoginForTest
{
    public BizLoginForTest(ILoginDal login)
    {
        super(login);
    }

    protected BizPermission getBizPermission()
    {
        return new FakeBizPermission();
    }
}

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

Альтернативой может быть внедрение полного фабричного объекта, как упомянуто в комментарии Джеффа. Это потребовало бы немного больше изменений кода (возможно, в том числе на клиентах BizLogin), так что это немного более навязчиво.

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

...