Тестирование методов Interactor, которые выполняют несколько функций в чистой архитектуре

Я читал о модульных тестах и ​​Чистая архитектура и попытался реализовать что-то, что включало бы эти две вещи.

Насколько я понимаю, чистая архитектура структурирована так, чтобы методы объекта Interactor можно было тестировать по модулю.

Но когда вариант использования выглядит примерно так: «Создайте файл, содержимое которого вычисляется из некоторых данных в каком-либо формате», я запутываюсь, потому что он не унитарный (есть вычисление содержимого файла и создание файла, которые являются в варианте использования)

Вот какой-то псевдокод, иллюстрирующий мою ситуацию:

/* We are in an Interactor (i.e. UseCaseObject)
 * This method 1)computes fileContent and 2)writes it into a file. 
 */
public void CreateFileFromData(someDataInSomeFormat) {
    var parsedData = SomeParser.Parse(someDataInSomeFormat);

    string fileContent = ???; 

    WriteFile(fileContent); 
}

Мои вопросы следующие:

  1. Должен ли метод, определенный в Interactor, быть унитарным? (например, делай только одно)
  2. Должен ли метод, определенный в Interactor, проходить модульное тестирование? (Я вижу функцию, унитарную или нет, как тестируемую единицу, поправьте меня, если это неверно)
  3. Какой класс должен выполнять вычисление fileContent в чистой архитектуре?

person Minh-Tâm TRAN    schedule 19.03.2018    source источник


Ответы (2)


Вы не говорите, откуда данные для вычислений будут «загружены», но, например, допустим, что данные будут считаны из другого файла.

Ваш интерактор будет иметь три зависимости
- читать файл
- вычислять данные для нового файла
- записывать файл

public class Interactor
{
    public Interactor(IReader reader, ICalculator calculator, IWriter writer)
    { }

    public void DoJob()
    {
        var data = reader.Read();
        var calculatedData = calculator.Calculate(data);
        writer.Write(calculatedData);
    }
}

При таком подходе Interactor будет нести ответственность за «объединение» шагов, необходимых для выполнения задачи.

Вы можете просто протестировать Interactor, высмеяв все зависимости.

Где:
IReader и IWriter - шлюзы
ICalculator - детали реализации UseCase, которые используются Interactor

Должен ли метод, определенный в Interactor, быть унитарным? (например, делай только одно)

Метод должен делать одно - выполнять задачу, относящуюся к варианту использования. Если задача требует использования шлюзов (внешних ресурсов) или задача слишком сложна, чтобы сохранить ее в одном методе - вы введете все необходимые блоки в виде зависимостей, а ответственность за взаимодействие будет заключаться в том, чтобы «склеить» их вместе.

Должен ли метод, определенный в Interactor, проходить модульное тестирование? (Я вижу функцию, унитарную или нет, как тестируемую единицу, поправьте меня, если это неверно)

Абстрагируйте только шлюзы (внешние ресурсы) - тогда вы можете протестировать всю логику интерактора. Если вы сначала напишете тест - вы напишете тесты, и вся логика может быть в одной функции (это может / должен быть уродливый спагетти-код, который заставляет тесты проходить). Затем, когда вы увидите полную картину реализации, вы можете начать перемещать персонал, перемещая вещи в специальные классы.

Какой класс должен выполнять вычисление fileContent в чистой архитектуре?

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

person Fabio    schedule 19.03.2018
comment
Спасибо за ваш ответ. Однако он не имеет отношения к чистой архитектуре, которой мои вопросы особенно присущи. Тот факт, что вы упоминаете, где будут «загружены» данные, похоже, указывает на то, что ваш ответ выходит за рамки чистой архитектуры, поскольку последняя обрабатывает такие вещи через определенные понятия, такие как сущности и шлюзы. Не могли бы вы показать более четко, как ваш ответ вписывается в чистую архитектуру? - person Minh-Tâm TRAN; 19.03.2018
comment
Спасибо ! Намного больше о том, с чем у меня возникли трудности :) И последнее: хотя вы ответили на мой третий вопрос, можете ли вы также уточнить ответ на все 3 вопроса в своем ответе для будущих читателей? Насколько я понимаю, это должно быть: [1: Нет] [2: Да] [3: деталь реализации Interactor, реализующего интерфейс] Еще раз спасибо! - person Minh-Tâm TRAN; 19.03.2018
comment
Если подумать, после прочтения этого, кажется, что хороший модульный тест - это тест, который не сломается, если я изменю внутри функции. Насколько я понимаю, ваш подход создаст тест, который сломается в тот момент, когда я решу изменить внутреннюю часть функции Interactor! (см. очень похожий пример, приведенный в моей ссылке) - person Minh-Tâm TRAN; 19.03.2018
comment
@ Minh-TâmTRAN - проверьте обновленный ответ. Вам нужно абстрагироваться (имитировать) только зависимости, которые замедляют тесты (внешние ресурсы). - person Fabio; 19.03.2018
comment
Я понимаю вашу точку зрения и считаю, что вы правы на 100%. Однако, вдобавок и по другой теме, насколько я понимаю, макет отличается от заглушки, потому что он может проверить, был ли использован метод или нет. Таким образом, тест, использующий этот макет, будет связан с именем используемого метода (что имеет место в коде внутри ссылки, которой я поделился, поиск // проверка того, что было вызвано), и, другими словами, если вы измените внутреннюю часть проверенный метод, проверка из макета не удастся, что приведет к нарушению теста по той единственной причине, что внутренняя часть функции изменилась - что плохо - person Minh-Tâm TRAN; 19.03.2018
comment
@ Minh-TâmTRAN, в модульных тестах нужно имитировать только шлюзы. Шлюзы обычно меняются редко, важнее не использовать имитации / заглушки / фейки для бизнес-логики вашего кода. Бизнес-логика часто меняется, и вы чаще ее реорганизуете. - person Fabio; 19.03.2018
comment
То, что я прочитал в этой ветке, помогло мне и научило меня целому ряду вещей (что привело к 2 дням, потраченным на всевозможные чтения), и, оглядываясь назад, я нашел ответ на свои вопросы. Набирая свой ответ, я понял, что все это содержится в вашем. Мне жаль, что мои идеи так долго не созрели, чтобы я смог прочитать ваш ответ так, как я его сейчас читаю. Ваш ответ отмечу как ответ :) - person Minh-Tâm TRAN; 21.03.2018

Одним из основных аспектов чистой архитектуры является то, что вся бизнес-логика приложения находится в методах Interactor. Это означает, что вы также хотите сосредоточить основное внимание на тестировании Interactors, обычно с использованием модульных тестов и приемочных тестов низкого уровня.

При разработке методов ur Interactor вы все равно должны следовать SRP: должна быть только одна причина для изменения. U также может комбинировать Interactors, чтобы следовать SRP.

Если вычисление содержимого файла является бизнес-логикой приложения для u, это должно быть в методе Interactor.

Для более подробного обсуждения Interactors, пожалуйста, посмотрите мой пост: https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/

person plainionist    schedule 19.03.2018
comment
В конструктивных целях, и это абсолютно не отзыв о вашем ответе, а, скорее, о моем вопросе, теперь, когда я нашел ответ на свою проблему (большое спасибо всем вашим ссылкам здесь и в вашей статье), я понимаю, что на самом деле я видел WriteFile () только как основной и завершающий шаг моего варианта использования, и это заставило меня пересмотреть, где разместить вычисления; в то время как на самом деле должно было быть наоборот, код WriteFile нужно было убрать из Interactor. Очевидно, вы не могли догадаться обо всем этом, я просто говорю это на случай, если это поможет вам определить реальный вопрос в других сообщениях :) - person Minh-Tâm TRAN; 21.03.2018