Conversation
YuriUfimtsev
left a comment
There was a problem hiding this comment.
И еще одно замечание: условие про отсутствие дублирования тестов на общее поведение для двух реализаций. Можно воспользоваться атрибутом TestCaseSource
Lazy/Lazy/FunctionsForTests.cs
Outdated
| /// </summary> | ||
| public static class FunctionsForTests | ||
| { | ||
| public static volatile int howMutchFunctionBeenCalled = 0; |
Lazy/Lazy/ILazy.cs
Outdated
| public interface ILazy<T> | ||
| { | ||
| /// <summary> | ||
| /// Getting the created object |
There was a problem hiding this comment.
Тогда уж
The class implements, Gets the created object
Lazy/Lazy/MultiThreadLazy.cs
Outdated
|
|
||
| public class MultiThreadLazy<T> : ILazy<T> | ||
| { | ||
| private Func<T> functionForLazy; |
Lazy/Lazy/MultiThreadLazy.cs
Outdated
| private Object objectLock = new (); | ||
| private volatile bool initialized = false; | ||
| private T resultSuppiler; | ||
| private Exception exceptionSuppiler = default; |
There was a problem hiding this comment.
К предупреждению компилятора стоит прислушаться, exceptionSupplier при инициализации класса null-ом становится
| /// Constructor for storing the object creation function | ||
| /// </summary> | ||
| public MultiThreadLazy(Func<T> function) | ||
| { |
There was a problem hiding this comment.
Тоже важное замечание компилятора, которое также отсылает к условию "supplier вправе вернуть null"
| { | ||
| [Test] | ||
| public void SimpleExample() | ||
| { |
There was a problem hiding this comment.
Тест с тремя+ Assert-ами не есть хорошо, особенно, если они все друг с другом не связаны. Надо разбить на более мелкие тесты с говорящими названиями
There was a problem hiding this comment.
Я понял, но тогда будут отдельные тесты для многопоточности и одно поточности, я не очень понимаю, как применить TestCaseSource, если запуск многопоточных тест и однопоточных сильно отличается
There was a problem hiding this comment.
Речь больше про то, чтобы разделить тесты на два типа:
- Тест(тесты) на корректную работу многопоточного Lazy и отсутствие гонок
- Тест(тесты) на корректную работу Lazy, когда к нему обращается всего один поток. И такого рода тесты надо запускать на двух вариантах Lazy, используя TestCaseSource для передачи объектов разных типов
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| Assert.That(FunctionsForTests.FunctionForLazyWithCounter(), Is.Not.EqualTo(singleThreadLazy.Get())); | ||
| FunctionsForTests.howMutchFunctionBeenCalled = 0; | ||
| var multiThreadLazy = new MultiThreadLazy<int>(() => FunctionsForTests.FunctionForLazyWithCounter()); | ||
| var arrayThreads = new Thread[Environment.ProcessorCount]; |
There was a problem hiding this comment.
Вдруг Environment.ProcessorCount вернет 1? :)
На такой случай лучше вручную потоков 10 создать, в крайнем случае будет симуляция многопоточности
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| } | ||
| catch (InvalidOperationException) | ||
| { | ||
| Assert.True(true); |
There was a problem hiding this comment.
Какая-то максимально бесполезная строчка :)
Никакой проверки в Assert не происходит, и в то же время если исключение не бросится, тест не упадет. Надо исправить
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| [Test] | ||
| public void ThreadRaceTest() | ||
| { | ||
| var multiThreadLazy = new SingleThreadLazy<int>(() => FunctionsForTests.FunctionForLazyWithCounter()); |
There was a problem hiding this comment.
Это проверка на гонку, проверяю, что при реализации в однопоточном режим использование многопоточности приводит к искажению результата, как по другому проверить, что нету гонок я не знаю
There was a problem hiding this comment.
Проверка на отсутствие гонок предполагает запуск многопоточного Lazy несколькими потоками и проверку, что все потоки получили один и тот же результат. Кстати, можно использовать ManualResetEvent для увеличения шанса одновременного запрашивания потоками значения Lazy.
В однопоточном режиме должно приводить к искажению, да, но это не часть функциональности системы, которую мы реализуем и хотим поддерживать, проверять тестами
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| value = multiThreadLazy.Get(); | ||
| lock (value) | ||
| { | ||
| if ((int)value != 1) |
There was a problem hiding this comment.
К этому тесту тоже много вопросов :)
- Неочевидно, чему должно быть равно value. Порядок тестов иметь значения не должен, а static-поле меняется в нескольких тестах. Поэтому, кстати, лучше его static-ом вообще не делать, а для каждого теста создавать свой объект с полем определенного значения. А то запустятся два теста и упадут из-за неупорядоченных проверок одного и того же поля на равенство разным значениям.
- Этот тест обречен на вечный успех
- Сравнение лучше делать более определенным. Если мы конкретное число ждем, то его и писать
There was a problem hiding this comment.
Тут скорее попытка поймать, что в однопоточной реализации при использовании многопоточности появляется гонка потоков, а сказать конкретно в какой момент value будет не равно 1 и чему именно
невозможно. Понимаю, что тест не самый хороший, но по-другому, как это проверить я не знаю
.
There was a problem hiding this comment.
Выше написал, но главное, что мы хотим проверить не наличие гонки в однопоточном режиме, а отсутствие гонок в многопоточном
Lazy/Lazy/ILazy.cs
Outdated
| namespace Lazy; | ||
|
|
||
| /// <summary> | ||
| /// The class implements, Gets the created object |
There was a problem hiding this comment.
| /// The class implements, Gets the created object | |
| /// The class implements an object whose function is called only once. |
Lazy/Lazy/ILazy.cs
Outdated
| public interface ILazy<T> | ||
| { | ||
| /// <summary> | ||
| /// Getting the created object |
There was a problem hiding this comment.
| /// Getting the created object | |
| /// Gets the created object. |
| { | ||
| resultSuppiler = supplier(); | ||
| supplier = null; | ||
| } |
There was a problem hiding this comment.
Компилятор остался неуслышанным :(
Lazy/Lazy/MultiThreadLazy.cs
Outdated
| if (exceptionSuppiler != default) | ||
| { | ||
| throw exceptionSuppiler; | ||
| } |
There was a problem hiding this comment.
Копипаст остался. Может ли exceptionSupplier != default и пройти мимо catch? Если нет, то нужна ли эта проверка, или можно кидать исключение в другом блоке?
| @@ -0,0 +1,46 @@ | |||
| namespace Lazy; | |||
Lazy/Lazy/FunctionsForTests.cs
Outdated
| /// </summary> | ||
| public class FunctionsForTests | ||
| { | ||
| public volatile int howMuchFunctionBeenCalled = 0; |
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| bool isExceptionWasThrown = false; | ||
| try | ||
| { | ||
| value = multiThreadLazy.Get(); | ||
| } | ||
| catch (InvalidOperationException) | ||
| { | ||
| isExceptionWasThrown = true; | ||
| } | ||
| Assert.True(isExceptionWasThrown); |
There was a problem hiding this comment.
| bool isExceptionWasThrown = false; | |
| try | |
| { | |
| value = multiThreadLazy.Get(); | |
| } | |
| catch (InvalidOperationException) | |
| { | |
| isExceptionWasThrown = true; | |
| } | |
| Assert.True(isExceptionWasThrown); | |
| Assert.Throws<InvalidOperationException>(() => multiThreadLazy.Get()); |
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
|
|
||
| [Test] | ||
| public void ExampleWithExceptionWithMultiThreadLazy() | ||
| { |
There was a problem hiding this comment.
На самом деле, от многопоточного теста нужна только проверка на отсутствие гонок, а проверка на корректность работы произойдет и в однопоточных тестах. Так что будет вполне достаточно трех тестов ниже
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| { | ||
| arrayThreads[i] = new Thread(() => | ||
| { | ||
| Assert.That(correctResult, Is.EqualTo(multiThreadLazy.Get())); |
There was a problem hiding this comment.
Assert.That в разных потоках -- не лучший вариант, как минимум из-за сложностей отладки. Лучше собрать значения потоков в массив и по нему пробежаться в конце в главном потоке, сравнивая значения с помощью Assert.That
Lazy/Lazy/FunctionsForTests.cs
Outdated
| /// Function that throws InvalidOperationException | ||
| /// </summary> | ||
| /// <returns></returns> | ||
| public int FunctionWithInvalidOperationException() |
There was a problem hiding this comment.
Функция себя не оправдывает, int и не собирается возвращать :)
Лучше вернуть int в конце, но чтобы компилятор не ругался, добавить строчку, выполнение которой вызовет исключение (типо деления на ноль)
Lazy/Lazy/MultiThreadLazy.cs
Outdated
| { | ||
| throw exceptionSuppiler; | ||
| supplier = null; | ||
| GC.Collect(); |
There was a problem hiding this comment.
Помнить про существование и работу сборщика мусора, конечно, важно, но время его вызова стоит оставлять на усмотрение рантайма
There was a problem hiding this comment.
Просто явно через GC.Collect(); его не вызывать, среда выполнения .NET сама это сделает, когда посчитает нужным
Lazy/Lazy/SingleThreadLazy.cs
Outdated
| supplier = null; | ||
| if (supplier == null) | ||
| { | ||
| resultSuppiler = default; |
There was a problem hiding this comment.
Нагляднее будет просто null написать. default для nullable типа в точности null и вернет
There was a problem hiding this comment.
И resultSupplier при инициализации и так null-ом станет, нет смысла его еще раз обнулять
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| arrayThreads[i] = new Thread(() => | ||
| { | ||
| Assert.That(result, Is.EqualTo(multiThreadLazy.Get())); | ||
| Assert.That(Equals(multiThreadLazy.Get(), 1)); |
There was a problem hiding this comment.
Есть Assert.That и Assert.Equals. Надо что-то одно выбрать.
Например, Assert.That(multiThreadLazy.Get(), Is.EqualTo(1));
Lazy/TestsForLazy/TestsForLazy.cs
Outdated
| arrayThreads[i] = new Thread(() => | ||
| { | ||
| Assert.That(correctResult, Is.EqualTo(multiThreadLazy.Get())); | ||
| Assert.That(Equals(multiThreadLazy.Get(), 1)); |
There was a problem hiding this comment.
Assert.That в разных потоках -- не исправлено.
There was a problem hiding this comment.
Хорошо, а как получить значения из потоков и класть их в массив?
| @@ -22,6 +22,7 @@ public int FunctionForLazyWithCounter() | |||
| /// <returns></returns> | |||
| public int FunctionWithInvalidOperationException() | |||
There was a problem hiding this comment.
Функция теперь свое название не оправдывает, exception другой.
И точно ли нужна отдельная переменная для единицы?
There was a problem hiding this comment.
Переменная нужна, т.к. при её отсутствии появляется error и сборка невозможна
YuriUfimtsev
left a comment
There was a problem hiding this comment.
В первом коммите "Часть ревью" исправили Assert.That(Equals(...)), а в следующих двух обратно вернули :)
И имя функции надо сменить. Либо указать DivideByZeroException, либо просто Exception. Но никак не InvalidOperationException

No description provided.