На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела:
1. Название темы - краткое описание кто/что против кого/чего
2. В первом сообщении - список параметров, по которым идет сравнение.
3. Старайтесь аргументировать свои высказывания. Фразы типа "Венда/Слюникс - ацтой" считаются флудом.
4. Давайте жить дружно и не доводить обсуждение до маразма и личных оскорблений.
Модераторы: Модераторы, Комодераторы
Страницы: (6) 1 2 [3] 4 5 ... Последняя » все  ( Перейти к последнему сообщению )  
> TDD vs не TDD
    Цитата D_KEY @
    Я может плаваю в теории, но в интеграционных тестах вместо моков уже должны быть реальные объекты, разве нет?

    Ну вот автор пишет что нет. Плюс есть мнение, и это не только у этого автора я читал, что в интеграционных тестах - моками имитируются все ситуации, которые могут быть. Реальный объект, не реальный объект, серверная атака, имитация ошибки, имитация с отключенным соединением и т.д. и т.п.

    Цитата D_KEY @
    А так все верно, да. Просто я бы предложил перед интеграционными тестами, делать еще ЮТ с моками, чтобы некоторые ошибки отлавливать раньше запуска интеграционных тестов.

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

    Цитата D_KEY @
    Вот если вместо "говорит о том, что модули сильно взаимосвязаны" написать "может говорить о том, что модули сильно взаимосвязаны", то я соглашусь

    Выдрал специально для тебя:
    Цитата

    What is a code smell?
    “A code smell is a surface indication that usually corresponds to a deeper problem in the system.” ~ Martin Fowler
    A code smell does not mean that something is definitely wrong, or that something must be fixed right away. It is a rule of thumb that should alert you to a possible opportunity to improve something.
    This text and its title in no way imply that all mocking is bad, or that you should never mock anything.
    Additionally, different types of code need different levels (and different kinds) of mocks. Some code exists primarily to facilitate I/O, in which case, there is little to do other than test I/O, and reducing mocks might mean your unit test coverage would be close to 0.
    If there is no logic in your code (just pipes and pure compositions), 0% unit test coverage might be acceptable, assuming your integration or functional test coverage is close to 100%. However, if there is logic (conditional expressions, assignments to variables, explicit function calls to units, etc…), you probably do need unit test coverage, and there may be opportunities to simplify your code and reduce mocking requirements.
    ---
    What is tight coupling?
    The need to mock in order to achieve unit isolation for the purpose of unit tests is caused by coupling between units. Tight coupling makes code more rigid and brittle: more likely to break when changes are required. In general, less coupling is desirable for its own sake because it makes code easier to extend and maintain. The fact that it also makes testing easier by eliminating the need for mocks is just icing on the cake.
    From this we can deduce that if we’re mocking something, there may be an opportunity to make our code more flexible by reducing the coupling between units. Once that’s done, you won’t need the mocks anymore.
    Coupling is the degree to which a unit of code (module, function, class, etc…) depends upon other units of code. Tight coupling, or a high degree of coupling, refers to how likely a unit is to break when changes are made to its dependencies. In other words, the tighter the coupling, the harder it is to maintain or extend the application. Loose coupling reduces the complexity of fixing bugs and adapting the application to new use-cases.
    ---
    What does composition have to do with mocking?
    Everything. The essence of all software development is the process of breaking a large problem down into smaller, independent pieces (decomposition) and composing the solutions together to form an application that solves the large problem (composition).
    Mocking is required when our decomposition strategy has failed.
    Mocking is required when the units used to break the large problem down into smaller parts depend on each other. Put another way, mocking is required when our supposed atomic units of composition are not really atomic, and our decomposition strategy has failed to decompose the larger problem into smaller, independent problems.
    When decomposition succeeds, it’s possible to use a generic composition utility to compose the pieces back together. Examples:
    ---
    When you use generic composition utilities, each element of the composition can be unit tested in isolation without mocking the others.
    The compositions themselves will be declarative, so they’ll contain zero unit-testable logic (presumably the composition utility is a third party library with its own unit tests).
    Under those circumstances, there’s nothing meaningful to unit test. You need integration tests, instead.
      Цитата D_KEY @
      Простой пример. Начал рефакторить или оптимизировать, ошибся в чем-то. Сломал.

      Зачем ты начал рефакторить то, что и так ясно отражает суть?
      Про оптимизацию я уже писал, но ты всё пропустил.

      Цитата D_KEY @
      Как раз обычно все эти быстрые стартапы часто активно используют эти "новые" хорошие инженерные практики

      Видел я как такой стартап использует эти практики. Ничем от херак-херак не отличается.

      Цитата D_KEY @
      Программисты такие, знаешь ли. Код правят. Делали фичу, рефакторили, оптимизировали - выбирай на свой вкус.

      Э нет, делать фичу — это новые требования и новая реализация. Рефакторят говнокод, а этот код чист, декларативен и понятен, идеален, его не нужно рефакторить =)
      Про оптимизации я уже писал.
      Кроме того, во многих сервисных проектах (особенно в этих ваших стартапах) проблемы производительности возникают обычно не в коде и не с первых годов жизни проекта.
        Цитата korvin @
        Зачем ты начал рефакторить то, что и так ясно отражает суть?
        Про оптимизацию я уже писал, но ты всё пропустил.

        Ну в твоем синтетическом примере может и не полез бы, а в реальном декларативном коде не вознкает потребности в рефакторинге?

        Цитата
        Видел я как такой стартап использует эти практики. Ничем от херак-херак не отличается.

        А хорошо получалось или было все нестабильно?

        Цитата
        Э нет, делать фичу — это новые требования и новая реализация.

        А, то есть мы полностью переписываем код каждый раз? :D

        Цитата
        Про оптимизации я уже писал.

        То, что если он недекларативен ради оптимизации, то тогда тесты имеет смысл писать?
        А что, декларативный код не оптимизируют?
          Цитата Wound @
          Но у тебя в ТЗ оговорено что класс должен и логировать и вычислять сумму.

          Логгинг в этом плане идеальный пример, т.к. логгером можно отслеживать процесс. Согласись, далеко не все функии умещаются в одну строку. И логгинг бывает разного уровня (debug, info, warn, error, fatal итд).

          Цитата Wound @
          Соответственно первый класс для вычисления суммы, второй для логирования, а третий агрегирует в себе эти два.

          Прикол в том, что 3-й класс делает систему неоправданно сложной.

          Цитата Wound @
          Вот автор и описывает - что такой подход ведет к запутанности, сильной зависимости, и соответственно к подводным камням и ошибкам.

          Весь прикол заключает в том, что если пользоваться терминологией автора этой статьи (т.е. UT не имеет мок-объектов, а то, что имеет мок-объекты - это интергационные тесты), то не существует систем, которые тестируются юнит-тестами :) Юнит-тестами можно протестировать только базовые компоненты, а продуктивный (aka полезный) код тестируется исключительно интеграционными тестами.
          Например, у нас есть модуль, который контролирует передачу RFID чипов. Каждый евент от RFID-антенны сохраняется в БД и передается от 0 до n раз на 5 других подсистем. И вся информация о правильном поведении идет из вне :) Ну и каких "юнит-тестах" (в понимании автора статьи) тут можно говорить?
            Цитата Fester @
            Логгинг в этом плане идеальный пример, т.к. логгером можно отслеживать процесс. Согласись, далеко не все функии умещаются в одну строку. И логгинг бывает разного уровня (debug, info, warn, error, fatal итд).

            Так и что с ним не так то? :-?

            Цитата Fester @
            Прикол в том, что 3-й класс делает систему неоправданно сложной.

            С чего вдруг сложной? В чем заключается сложность? Я не понимаю.

            Цитата Fester @
            Весь прикол заключает в том, что если пользоваться терминологией автора этой статьи (т.е. UT не имеет мок-объектов, а то, что имеет мок-объекты - это интергационные тесты), то не существует систем, которые тестируются юнит-тестами

            Ни автор, ни я не писали того, что выделенно жирным в твоей цитате.

            Цитата Fester @
            Юнит-тестами можно протестировать только базовые компоненты, а продуктивный (aka полезный) код тестируется исключительно интеграционными тестами.

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

            Цитата Fester @
            Например, у нас есть модуль, который контролирует передачу RFID чипов. Каждый евент от RFID-антенны сохраняется в БД и передается от 0 до n раз на 5 других подсистем. И вся информация о правильном поведении идет из вне Ну и каких "юнит-тестах" (в понимании автора статьи) тут можно говорить?

            Юнит тестами тестируют конкретные функции/методы, ты же привел какой то юзер кейс, который должен тестироваться интеграционными тестами.
            Тестируемая функция в ЮТ не должна не от чего зависеть, она должна быть автономной. Т.е. если у тебя в каком то 1 методе ошибка. То в юнит тестах - должен быть сфейлен ровно 1 UT из 1000 запущенных, даже если в программе у тебя куча мест, которые используют эту функцию/метод.
            Если же у тебя сфейлилось 50 юнит тестов, из за того, что в ровно 1 методе у тебя ошибка, значит с твоими юнит тестами что то не то, и их ценность стремится к нулю.
            Сообщение отредактировано: Wound -
              Цитата D_KEY @
              а в реальном декларативном коде не вознкает потребности в рефакторинге?

              Так я ж бомблю, что его почти не пишут, этот самый декларативный код, предпочитая обмазаться тестами и говнякать =)

              Добавлено
              Цитата D_KEY @
              А хорошо получалось или было все нестабильно?

              Всё было плохо.

              Цитата D_KEY @
              А, то есть мы полностью переписываем код каждый раз?

              Зачем каждый раз? Если ты про то, чтобы переиспользовать эту функцию в новой фиче, то если она подходит, можно и переиспользовать, но тогда две фичи будут от неё зависеть и менять её нельзя. Да и зачем? Если у тебя новые требование (новая формула, скажем, умножать нужно не на два, а на три), то естественно нужно написать новую функцию и использовать там, где нужно, а не трогать старую. Ты что, open-close принцип забыл?

              Цитата D_KEY @
              То, что если он недекларативен ради оптимизации, то тогда тесты имеет смысл писать?

              Ну да, если это нечитаемое байтоковыряние, конечно, надо.

              Цитата D_KEY @
              А что, декларативный код не оптимизируют?

              А что такое «оптимизация декларативного кода»?

              Добавлено
              К слову о моках и в чём с ними проблема.

              Допустим, есть у нас такой сервис:

              ExpandedWrap disabled
                public final class Transfer {
                 
                    public static final class Request {
                 
                        public final Account.ID source;
                        public final Account.ID destination;
                        public final Money amount;
                 
                        public Request(Account.ID source, Account.ID destination, Money amount) {
                            this.source = source;
                            this.destination = destination;
                            this.amount = amount;
                        }
                    }
                 
                    public static final class InsufficientFunds extends RuntimeException {}
                 
                    private final Accounts accounts;
                    private final Compliance compliance;
                    private final Transactions transactions;
                    private final Notifier notifier;
                 
                    public Transfer(Accounts accounts, Compliance compliance, Transactions transactions, Notifier notifier) {
                        this.accounts = accounts;
                        this.compliance = compliance;
                        this.transactions = transactions;
                        this.notifier = notifier;
                    }
                 
                    public void submit(Request r) {
                        final var source = accounts.load(r.source);
                        final var destination = accounts.load(r.destination);
                 
                        validate(source, destination, r.amount);
                        final var transaction = Transaction.fresh(r.source, r.destination, r.amount);
                        transactions.store(transaction);
                        notifyUsers(source, destination, transaction.id);
                    }
                 
                    private void validate(Account source, Account destination, Money amount) {
                        checkCompliance(source, destination, amount);
                        checkBalance(source, amount);
                    }
                 
                    private void checkCompliance(Account source, Account destination, Money amount) {
                        if (source.hasSameOwnerAs(destination)) {
                            return;
                        }
                        compliance.validateTransfer(source.owner, destination.owner, amount);
                    }
                 
                    private void checkBalance(Account source, Money amount) {
                        if (source.canWithdraw(amount)) {
                            return;
                        }
                        throw new InsufficientFunds();
                    }
                 
                    private void notifyUsers(Account source, Account destination, Transaction.ID transaction) {
                        notifier.notify(source.owner, transaction, "withdraw");
                        notifier.notify(destination.owner, transaction, "top up");
                    }
                }


              у него четыре внешние зависимости, вот одна из них:
              ExpandedWrap disabled
                public interface Accounts {
                 
                    Account load(Account.ID id);
                 
                    // more methods
                    // ...
                }

              три другие примерно такие же: интерфейсы с разными методами, я описал только те, что используются в сервисе Transfer.

              теперь для юнит-тестов мы мокаем эти интерфейсы, как обычно это делают, что-то вроде
              ExpandedWrap disabled
                final var accounts = Mockito.mock(Accounts);
                ...
                final var sourceAccount = // new account
                ...
                when(accounts.load(sourceAccount.id)).thenReturn(sourceAccount);
                ...
                subject.submit(new Transfer.Request(sourceAccount.id, ...));
                ...
                // assertions on mocked Transactions DB and Notifier?

              и тут возникают вопросы:
              – откуда мы знаем, что submit вообще вызовет accounts.load?
              – откуда мы знаем, что submit вызовет accounts.load с параметром sourceAccount.id?
              – откуда мы знаем, что submit не вызовет других методов accounts?
              Этого нет в публичном интерфейсе (здесь я имею ввиду публичные методы и конструкторы) класса Transfer, нет в его «контракте». Фактически это — деталь реализации.

              Т.е. наличие параметра Accounts в публичном конструкторе — это часть контракта, публичная внешняя зависимость, а как именно используется экземпляр Accounts — это деталь реализации и «знание» этой детали в тесте — прямое нарушение инкапсуляции.

              Теперь рассмотрим альтернативу:
              ExpandedWrap disabled
                public final class Transfer {
                 
                    public static final class Request {
                 
                        public final Money amount;
                        public final Lazy<Account> source;
                        public final Lazy<Account> destination;
                        public final Lazy<Void> checkCompliance;
                 
                        public Request(Money amount, Lazy<Account> source, Lazy<Account> destination, Lazy<Void> checkCompliance) {
                            this.amount = amount;
                            this.source = source;
                            this.destination = destination;
                            this.checkCompliance = checkCompliance;
                        }
                    }
                 
                    public static final class Response {
                 
                        public final Transaction transaction;
                        public final Notification[] notifications;
                 
                        private Response(Transaction transaction, Notification... notifications) {
                            this.transaction = transaction;
                            this.notifications = notifications;
                        }
                 
                        public static final class Notification {
                            public final User.ID user;
                            public final Transaction.ID transaction;
                            public final String message;
                            public Notification(User.ID user, Transaction.ID transaction, String message) {
                                this.user = user;
                                this.transaction = transaction;
                                this.message = message;
                            }
                        }
                    }
                 
                    public static final class InsufficientFunds extends RuntimeException {}
                 
                    public Response submit(Request r) {
                        validate(r);
                        return transaction(r);
                    }
                 
                    private void validate(Request r) {
                        checkCompliance(r);
                        checkBalance(r);
                    }
                 
                    private void checkCompliance(Request r) {
                        if (r.source.val().hasSameOwnerAs(r.destination.val())) {
                            return;
                        }
                        r.checkCompliance.val();
                    }
                 
                    private void checkBalance(Request r) {
                        if (r.source.val().canWithdraw(r.amount)) {
                            return;
                        }
                        throw new InsufficientFunds();
                    }
                 
                    private Response transaction(Request r) {
                        final var transaction = Transaction.fresh(r.source.val().id, r.destination.val().id, r.amount);
                        return new Response(
                                transaction,
                                new Response.Notification(r.source.val().owner, transaction.id, "withdraw"),
                                new Response.Notification(r.destination.val().owner, transaction.id, "top up")
                        );
                    }
                }

              Ни одной внешней зависимости с полным сохранением функциональности.

              Примерный тест:
              ExpandedWrap disabled
                final var sourceAccount = // new account
                ...
                final var response = subject.submit(new Transfer.Request(
                    money,
                    () -> sourceAccount,
                    () -> destinationAccount,
                    () -> { /* e.g. do nothing in this particular test case */ }
                ));
                 
                // assertions on response

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

                Так и не начнут же :)
                По крайней мере не раньше, чем меритократические тенденции возобладают в РФ :lol:

                Цитата
                Цитата D_KEY @
                А хорошо получалось или было все нестабильно?

                Всё было плохо.

                Ну значит или те, кто нахваливают TDD врут, или у тех, кого ты видил, было что-то не так сделано. Ну и вообще основная проблема-то может быть вообще в кадрах :D

                Цитата
                Если у тебя новые требование (новая формула, скажем, умножать нужно не на два, а на три), то естественно нужно написать новую функцию и использовать там, где нужно, а не трогать старую. Ты что, open-close принцип забыл?

                Я просто не могу смапить твой синтетический пример на реальный код :)

                Цитата

                А что такое «оптимизация декларативного кода»?

                Ну, скажем, у тебя из-за (слишком) ленивых вычислений в программе на haskell появляются моменты, когда память то почти свободна, то лавинообразно забивается и все начинает тупить.
                Соответственно, можно чуть иначе попытаться написать код и расход память станет стабильнее.
                  По мне так декларативный код - утопия.
                  Сообщение отредактировано: applegame -
                    Цитата Wound @
                    Так и что с ним не так то?

                    То, что логгинг интересен по ходу исполнения кода, а не "на уровень выше".

                    Цитата Wound @
                    С чего вдруг сложной? В чем заключается сложность? Я не понимаю.

                    Вводится дополнительный класс с дополнительной логикой.
                    Сравни:
                    ExpandedWrap disabled
                          interface ILogger
                          {
                            void Info (string message);
                          }
                       
                          interface ICalculator
                          {
                              int Sum(int a, int b);
                          }
                          
                          public class Calculator : ICalculator
                          {
                             ILogger _logger = null;
                             public Calculator (ILogger logger)
                             {
                                _logger = logger;
                             }
                          
                             public int Sum(int a, int b)
                             {
                                _logger?.Info ($"Sum({a},{b})");
                                return a + b;
                             }
                          }


                    и

                    ExpandedWrap disabled
                          interface ILogger
                          {
                            void Info (string message);
                          }
                       
                          interface ICalculatorWithIO
                          {
                              int Sum(int a, int b);
                          }
                       
                          interface ICalculator : ICalculatorWithIO
                          {
                          }
                       
                          public class Calculator : ICalculator
                          {
                             public int Sum(int a, int b)
                             {
                                return a + b;
                             }
                          }
                       
                          public class CalculatorWithLogger : ICalculatorWithIO
                          {
                             private ICalculator _calculator;
                             private readonly ILogger _logger;
                             public CalculatorWithLogger(ICalculator calc, ILogger logger)
                             {
                                 if (calc == null)
                                    throw new ArgumentNullException ("Calculator must be initialized.");
                       
                                 _calculator = calc;
                                 _logger = logger;
                             }
                          
                             public int Sum(int a, int b)
                             {
                                _logger?.Info ($"Call method Sum({a},{b})");
                                return _calculator.Sum(a, b);
                             }
                          }


                    И если в варианте с моком тестировать надо только одну функцию, то в предлагаемом тобой подходе тестировать надо 3 функции.


                    Цитата Wound @
                    Если у тебя функция и жнец и жрец и на дуде игрец, тогда это явно говорит о плохом дизайне в твоем приложении и о жестких зависимостях, от которых нужно избавляться.

                    Просто в реальном мире данные надо брать из многих разных источноков, сопоставлать эти данные и принимать какое-либо решение.

                    Цитата Wound @
                    Тестируемая функция в ЮТ не должна не от чего зависеть, она должна быть автономной. Т.е. если у тебя в каком то 1 методе ошибка. То в юнит тестах - должен быть сфейлен ровно 1 UT из 1000 запущенных, даже если в программе у тебя куча мест, которые используют эту функцию/метод.

                    Именно для этого и нужны моки :)
                    В примере выше тест для суммы выглядел бы так:
                    ExpandedWrap disabled
                      public void Sum_2plus2_4 ()
                      {
                         // Arrange
                         ILogger logger = Substitute.For<ILogger>();
                         ICalculator calc = new Calculator (logger);
                       
                         // Act
                         int res = calc.Sum (2, 2);
                       
                         // Assert
                         Assert::AreEqual (4, res);
                       
                         // тут же можно проверить, вызвался ли логгер с правильными данными
                         logger.Received(1).Info("Sum(2,2)");
                      }

                    как видишь, тут совершенно наплевать сколько там ошибок в логгере. Этот тест от кода логгера не зависит.
                      В жопу этот TDD, один хер реальное тестирование все равно происходит вручную в QA отделах. Есть конечно исключения, но в подавляющем большинстве не критических для жизни людей проектов все именно так.
                      Лично я пишу иногда тесты, но они как правило одноразовые. То есть потестировал, ошибки нашел, исправил, и все, валяются эти тесты годами и никогда не дают сбоев.
                      Сообщение отредактировано: applegame -
                        Цитата D_KEY @
                        По крайней мере не раньше, чем меритократические тенденции возобладают в РФ

                        Ну ладно, возвращаю тебе избирательное право =)

                        Цитата applegame @
                        По мне так декларативный код - утопия.

                        «Важны тенденции, а не абсолютные показатели» — Отто Фон БисмаркD_KEY


                        Цитата D_KEY @
                        Ну, скажем, у тебя из-за (слишком) ленивых вычислений в программе на haskell появляются моменты, когда память то почти свободна, то лавинообразно забивается и все начинает тупить.
                        Соответственно, можно чуть иначе попытаться написать код и расход память станет стабильнее.

                        Для этого нужно минимум изменений, которые никак не меняют логику.
                          Цитата Fester @
                          То, что логгинг интересен по ходу исполнения кода, а не "на уровень выше".

                          Логгинг интересен тогда, когда у тебя непонятный код, не покрытый UT, за который ты не ручаешься. Если у тебя все очевидно как работает - логирование нахрен и не нужно. Поэтому логгировать ты вполне себе можешь именно в том месте, где этому место, а не везде. Вот скажи, ты очевидные в реализации чистые функции, без сайд эффектов часто логируешь? И главное с какой целью?

                          Цитата Fester @
                          Вводится дополнительный класс с дополнительной логикой.

                          Нет там класса с дополнительной логикой, там есть обертка инкапсулирующая в себе 2 класса.

                          Цитата Fester @
                          Сравни:

                          И чем это лучше, расскажи пожалуйста? Плюс ко всему ты написал функцию с сайд эффектом.

                          Цитата Fester @
                          И если в варианте с моком тестировать надо только одну функцию, то в предлагаемом тобой подходе тестировать надо 3 функции.

                          Во втором варианте тестировать нужно тоже ровно 1 метод, без всяких моков. Неужели ты этого не понимаешь? Какой смысл тестировать например HttpClient/File/Logger/etc ?

                          Цитата Fester @
                          Просто в реальном мире данные надо брать из многих разных источноков, сопоставлать эти данные и принимать какое-либо решение.

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

                          Цитата Fester @
                          Именно для этого и нужны моки
                          В примере выше тест для суммы выглядел бы так:

                          Ага, только вот тебе автор как раз и пишет - что эти самые моки являются жесткой зависимостью, ты не можешь протестировать эту функцию без мока, в итоге мы приходим к тому, что тесты зачастую сложнее, чем само приложение, в итоге польза от таких UT стремится к нулю, а все тестирование сводится к тестированию моков. Ты статью то почитай, а :D
                            Цитата korvin @
                            «Важны тенденции, а не абсолютные показатели» — Отто Фон БисмаркD_KEY
                            Декларативное программирование красивое словечко не более того. Как коммунизм. Пишите декларативно:
                            ExpandedWrap disabled
                              <beautiful_life planet="earth"/>

                            Отличная программа я щетаю. Никаких рефакторингов, тестирования и прочего хлама. :lol: Осталось только написать The Browser, который сможет выполнить эту программу.
                            Сообщение отредактировано: applegame -
                              Цитата applegame @
                              и все, валяются эти тесты годами и никогда не дают сбоев.

                              Вот тоже с таким сталкивался.

                              D_KEY: во всяких этих стартапах зачастую рефакторинг и оптимизации проводят крайне редко, в основном, либо добавляют новые фичи, либо изменяют существующие, а это означает, что либо тесты правятся с каждым таким изменением, либо они валяются без реальной пользы, просто занимая лишнее время на билд проекта.
                                Цитата Fester @
                                // тут же можно проверить, вызвался ли логгер с правильными данными
                                   logger.Received(1).Info("Sum(2,2)");

                                Зачем это делать? Проверить правильно ли разработчики логгера его написали? :huh:
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0635 ]   [ 15 queries used ]   [ Generated: 6.05.24, 01:11 GMT ]