├── .gitignore ├── LICENSE ├── README.md ├── cs ├── Challenge │ ├── Challenge.csproj │ ├── IWordsStatistics.cs │ ├── IncorrectImplementations │ │ └── DoNotOpen.cs │ ├── Infrastructure │ │ ├── ChallengeHelpers.cs │ │ ├── Firebase.cs │ │ ├── FirebaseActions.cs │ │ ├── ImplementationStatus.cs │ │ ├── IncorrectImplementationAttribute.cs │ │ ├── IncorrectImplementation_TestsBase.cs │ │ ├── IncorrectImplementations_Tests.cs │ │ └── Program.cs │ ├── Readme.md │ ├── Solved │ │ ├── WordsStatistics.cs │ │ ├── WordsStatistics_Tests.cs │ │ └── codes.txt │ ├── WordCount.cs │ ├── WordsStatistics.cs │ ├── WordsStatistics_Tests.cs │ ├── YourName.cs │ └── history │ │ ├── bootcamp-1-2017-08 │ │ ├── leaderboard-1h-beforeOpen.html │ │ ├── leaderboard.html │ │ └── testing-challenge-20170809-export.json │ │ ├── innopolis-2016-10 │ │ ├── before-open-1h.json │ │ └── leaderboard.html │ │ ├── intern-16 │ │ ├── before-open.json │ │ ├── final.json │ │ ├── leaderboard_before-open.html │ │ └── leaderboard_final.html │ │ ├── intern-17-1 │ │ ├── 1h-before-open.html │ │ └── 1h-before.json │ │ ├── kampus-2016-11 │ │ ├── 1h-before-open.html │ │ └── 1h-before-open.json │ │ ├── kontur-2016-07 │ │ ├── before-open.json │ │ ├── final.json │ │ └── leaderboard_before-open.html │ │ ├── kontur-2016-10 │ │ ├── after-open-1h20mjson │ │ └── leaderboard-after-open-1h20m.html │ │ └── shpora-2016 │ │ ├── 1h20m.json │ │ ├── 50m.json │ │ └── leaderboard-50m.html ├── HomeExercises │ ├── HomeExercises.csproj │ ├── NumberValidatorTests.cs │ ├── ObjectComparison.cs │ └── README.md ├── Samples │ ├── AAA │ │ └── Zip_Should.cs │ ├── Antipatterns │ │ ├── Stack1_Tests.cs │ │ ├── Stack2_Tests.cs │ │ ├── Stack3_Tests.cs │ │ └── Stack4_Tests.cs │ ├── Parametrized │ │ └── Double_Should.cs │ ├── Samples.csproj │ ├── Samples.csproj.DotSettings │ ├── Specifications │ │ └── Stack_Specification.cs │ ├── TestDataBuilder │ │ ├── TestDataBuilder_Sample_Tests.cs │ │ ├── TestUserBuilder.cs │ │ ├── TestUsers.cs │ │ └── User.cs │ └── data.txt ├── testing.sln ├── testing.sln.DotSettings └── tests-templates.DotSettings ├── faq.pptx ├── feedback.url ├── index.html ├── java ├── .gitignore ├── Readme.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ └── java │ │ └── ru │ │ └── kontur │ │ └── courses │ │ ├── WordCount.java │ │ ├── WordStatisticImpl.java │ │ ├── WordStatistics.java │ │ └── donotopen │ │ ├── WordStatistics01.java │ │ ├── WordStatistics02.java │ │ ├── WordStatistics03.java │ │ ├── WordStatistics04.java │ │ ├── WordStatistics123.java │ │ ├── WordStatistics998.java │ │ ├── WordStatistics999.java │ │ ├── WordStatisticsC.java │ │ ├── WordStatisticsCR.java │ │ ├── WordStatisticsE.java │ │ ├── WordStatisticsE2.java │ │ ├── WordStatisticsE3.java │ │ ├── WordStatisticsE4.java │ │ ├── WordStatisticsEN1.java │ │ ├── WordStatisticsEN2.java │ │ ├── WordStatisticsL2.java │ │ ├── WordStatisticsL3.java │ │ ├── WordStatisticsL4.java │ │ ├── WordStatisticsQWE.java │ │ └── WordStatisticsSTA.java │ └── test │ └── java │ └── ru │ └── kontur │ └── courses │ ├── IncorrectImplementationTest.java │ ├── WordStatisticFactory.java │ ├── WordStatisticsTest.java │ ├── homework │ ├── NumberValidator.java │ ├── NumberValidatorTest.java │ ├── ObjectComparisonTest.java │ ├── Person.java │ ├── README.md │ └── TsarRegistry.java │ ├── samples │ ├── AAATest.java │ ├── AntiPatterns.java │ ├── Parametrized.java │ ├── SpecificationTest.java │ └── builder │ │ ├── TestBuilderTest.java │ │ ├── TestUserBuilder.java │ │ ├── TestUsers.java │ │ └── User.java │ └── solved │ └── WordStatisticsSolved.java ├── js ├── .babelrc ├── .gitignore ├── .idea │ ├── js.iml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations │ │ ├── Samples_AAA.xml │ │ ├── Samples_Antipatterns.xml │ │ ├── Samples_Parametrized.xml │ │ ├── Samples_Specifications.xml │ │ ├── Samples_TestDataBuilder_Tests.xml │ │ └── Words_Statistics_Tests.xml │ ├── vcs.xml │ └── watcherTasks.xml ├── .vscode │ └── settings.json ├── Mocha.xml ├── package-lock.json ├── package.json └── src │ ├── challenge │ ├── .solved │ │ └── wordsStatistics.test.js │ ├── incorrectImplementations │ │ ├── doNotOpen.js │ │ └── wordsStatistics.incorrect.test.js │ ├── index.js │ ├── infrastructure │ │ ├── challenger.js │ │ ├── consoleWriter.js │ │ ├── reporters │ │ │ ├── correctImplementationReporter.js │ │ │ ├── failedTestsCollector.js │ │ │ ├── incorrectImplementationsReporter.js │ │ │ └── index.js │ │ └── resultPoster.js │ ├── wordsStatistics.js │ ├── wordsStatistics.test.js │ └── yourName.js │ ├── lib │ ├── argumentNullError.js │ ├── stack.js │ ├── stringHelpers.js │ └── zip.js │ └── samples │ ├── AAA │ └── zip_should.test.js │ ├── antipatterns │ ├── stack1.test.js │ ├── stack2.test.js │ ├── stack3.test.js │ └── stack4.test.js │ ├── parametrized │ └── number_should.test.js │ ├── specifications │ └── stack.spec.js │ └── testDataBuilder │ ├── sampleTests.js │ ├── testUserBuilder.js │ ├── testUsers.js │ └── user.js ├── leaderboard-README.md ├── leaderboard.html ├── python ├── README.md ├── challenge │ ├── .solved │ │ ├── implementation_comments.md │ │ └── test_word_statistics.py │ ├── fb.py │ ├── incorrect_implementation │ │ └── do_not_open.py │ ├── list_tests │ │ ├── test_correct_implementation.py │ │ └── test_incorrect_implementation │ │ │ ├── test_123.py │ │ │ ├── test_998.py │ │ │ ├── test_999.py │ │ │ ├── test_C.py │ │ │ ├── test_CR.py │ │ │ ├── test_E.py │ │ │ ├── test_E2.py │ │ │ ├── test_E3.py │ │ │ ├── test_E4.py │ │ │ ├── test_EN1.py │ │ │ ├── test_EN2.py │ │ │ ├── test_L2.py │ │ │ ├── test_L3.py │ │ │ ├── test_L4.py │ │ │ ├── test_O1.py │ │ │ ├── test_O2.py │ │ │ ├── test_O3.py │ │ │ ├── test_O4.py │ │ │ ├── test_QWE.py │ │ │ └── test_STA.py │ ├── run_tests.py │ ├── test_words_statistics.py │ ├── word_statistics.py │ └── your_name.py ├── lib │ └── stack.py ├── requirements.txt ├── samples │ ├── antipatterns │ │ ├── test_stack_1.py │ │ ├── test_stack_2.py │ │ ├── test_stack_3.py │ │ └── test_stack_4.py │ ├── data.txt │ ├── parametrized │ │ └── test_float.py │ ├── specifications │ │ └── test_stack_specification.py │ └── test_data_builder │ │ ├── conftest.py │ │ ├── test_user_builder.py │ │ ├── user.py │ │ └── user_builder.py └── settings.zip ├── testing-c#.pptx ├── testing-java.pptx └── testing.pptx /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 kontur-intern-2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Тестирование 2 | 3 | Это блок о написании правильных и полезных тестов. 4 | 5 | Пройдя блок, ты: 6 | 7 | - Узнаешь паттерны создания тестов: 8 | - каноническую структуру теста AAA 9 | - правила именования тестов, чтобы они работали как спецификация 10 | - Познакомишься с антипаттернами, которые приводят к хрупкости, сложности и трудночитаемости 11 | - Получишь опыт тестирования "чёрного ящика" и "белого ящика" 12 | - Поймёшь, когда лучше работают тесты, а когда code review 13 | - Почувствуешь пользу от написания тестов 14 | 15 | 16 | ## Необходимые знания 17 | 18 | Понадобится знание C#, JS, Java или Python. 19 | 20 | 21 | ## Самостоятельная подготовка 22 | 23 | ### C# 24 | 1. Познакомься с NUnit, если ещё не знаком, научись подключать его к проекту через NuGet 25 | 2. Изучи возможности синтаксиса NUnit по этому [примеру](https://github.com/nunit/nunit-csharp-samples/blob/master/syntax/AssertSyntaxTests.cs) или по [документации](https://github.com/nunit/docs/wiki/NUnit-Documentation) 26 | 3. Научись запускать тесты из Visual Studio с помощью ReSharper по [инструкции](https://www.jetbrains.com/resharper/features/unit_testing.html) 27 | 4. Изучи возможности синтаксиса [FluentAssertions](https://fluentassertions.com/introduction) 28 | 5. Установи .NET Framework 4.8 29 | 30 | ### JS 31 | 1. Познакомься с Mocha, если ещё не знаком, научись подключать его через npm (Yarn) 32 | 2. Изучи возможности синтаксиса [Mocha](https://mochajs.org/), [ChaiJS](https://www.chaijs.com/api/bdd/) 33 | 3. Научись запускать тесты в терминале (`npm test` или `yarn test`), из WebStorm по [инструкции](https://www.jetbrains.com/help/webstorm/testing.html) или другой любимой JavaScript IDE 34 | 4. Если пока плохо знаком с Node.js и ES6, то начни с Шага 1 этого [туториала](https://github.com/kontur-courses/frontend-starter-tutorial) 35 | 36 | ### Java 37 | 1. Познакомься с JUnit, если ещё не знаком, научись его подключать через Gradle 38 | 2. Изучи возможности синтаксиса, [документация](https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html) 39 | 3. Научись запускать тесты JUnit5 в IDE 40 | 4. Изучи возможности синтаксиса [AssertJ](https://assertj.github.io/doc/) 41 | 42 | ### Python 43 | 1. Познакомься c pytest, если ещё не знаком ([документация](https://docs.pytest.org/en/7.3.x/)) 44 | 2. Научись запускать тесты из PyCharm по [инструкции](https://www.jetbrains.com/help/pycharm/performing-tests.html#run-tests-in-parallel) 45 | 3. Почитай о том, что такое фикстуры в pytest (например, на [хабре](https://habr.com/ru/articles/448786/)) 46 | 4. Установи с помощью pip в окружение библиотеки из python/requirements.txt - используй команду `pip install -r python/requirements.txt` 47 | 48 | ## Очная встреча 49 | 50 | ~ 3 часа. 51 | 52 | 53 | ## Закрепление материала 54 | 55 | 1. Спецзадание __Ретротестирование__ 56 | Вспомни одну-две решенные задачи. Какие тесты пригодились бы, если бы решение надо было дополнить или переписать? 57 | 2. Спецзадание __Test infection__ 58 | Решив задачу по программированию, напиши на нее модульные тесты. 59 | -------------------------------------------------------------------------------- /cs/Challenge/Challenge.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net48 6 | false 7 | Challenge 8 | 7.3 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /cs/Challenge/IWordsStatistics.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Challenge 4 | { 5 | public interface IWordsStatistics 6 | { 7 | void AddWord(string word); 8 | IEnumerable GetStatistics(); 9 | } 10 | } -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/ChallengeHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Challenge.Infrastructure 6 | { 7 | public static class ChallengeHelpers 8 | { 9 | public static Type[] GetIncorrectImplementationTypes() 10 | { 11 | return GetInheritorsOf() 12 | .Where(t => t.HasAttribute()) 13 | .OrderBy(t => t.Name.Length).ThenBy(t => t.Name) 14 | .ToArray(); 15 | } 16 | 17 | public static IncorrectImplementation_TestsBase[] GetIncorrectImplementationTests() 18 | { 19 | return GetInheritorsOf() 20 | .Select(Activator.CreateInstance) 21 | .Cast() 22 | .ToArray(); 23 | } 24 | 25 | public static bool HasAttribute(this Type method) where TAttribute : Attribute 26 | { 27 | return method.GetCustomAttributes(typeof(TAttribute), true).Any(); 28 | } 29 | 30 | public static Type[] GetInheritorsOf() 31 | { 32 | var baseType = typeof(T); 33 | return Assembly.GetExecutingAssembly().GetTypes() 34 | .Where(baseType.IsAssignableFrom) 35 | .Where(t => t != baseType && !t.IsAbstract && !t.IsInterface) 36 | .ToArray(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/Firebase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FireSharp; 3 | using FireSharp.Config; 4 | 5 | namespace Challenge.Infrastructure 6 | { 7 | public static class Firebase 8 | { 9 | private static FirebaseConfig BuildConfig() 10 | { 11 | const string Url = "https://testing-challenge.firebaseio.com"; 12 | const string Realm = "word-statistics"; 13 | var dateKey = DateTime.Now.Date.ToString("yyyyMMdd"); 14 | 15 | var config = new FirebaseConfig 16 | { 17 | BasePath = $"{Url}/{Realm}/{dateKey}" 18 | }; 19 | return config; 20 | } 21 | 22 | public static FirebaseClient CreateClient() 23 | { 24 | return new FirebaseClient(BuildConfig()); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/FirebaseActions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Challenge.Infrastructure 6 | { 7 | [TestFixture] 8 | public class FirebaseActions 9 | { 10 | [Test, Explicit] 11 | public void ClearLeaderboard() 12 | { 13 | using (var client = Firebase.CreateClient()) 14 | { 15 | var response = client.Get(""); 16 | var jObject = (JObject)JsonConvert.DeserializeObject(response.Body); 17 | foreach (var pair in jObject) 18 | { 19 | client.Delete(pair.Key); 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/ImplementationStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Challenge.Infrastructure 2 | { 3 | public class ImplementationStatus 4 | { 5 | public ImplementationStatus(string name, string[] fails) 6 | { 7 | Name = name; 8 | Fails = fails; 9 | } 10 | 11 | public readonly string Name; 12 | public readonly string[] Fails; 13 | } 14 | } -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/IncorrectImplementationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Challenge.Infrastructure 4 | { 5 | public class IncorrectImplementationAttribute : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/IncorrectImplementation_TestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Challenge.IncorrectImplementations; 3 | using NUnit.Framework; 4 | 5 | namespace Challenge.Infrastructure 6 | { 7 | public abstract class IncorrectImplementation_TestsBase : WordsStatistics_Tests 8 | { 9 | public override IWordsStatistics CreateStatistics() 10 | { 11 | string ns = typeof(WordsStatisticsC).Namespace; 12 | var implTypeName = ns + "." + GetType().Name.Replace("_Tests", ""); 13 | var implType = GetType().Assembly.GetType(implTypeName); 14 | if (implType == null) 15 | Assert.Fail("no type {0}", implTypeName); 16 | return (IWordsStatistics)Activator.CreateInstance(implType); 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /cs/Challenge/Infrastructure/IncorrectImplementations_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Challenge.Infrastructure 6 | { 7 | [TestFixture] 8 | public class GenerateIncorrectTests 9 | { 10 | [Test] 11 | public void Generate() 12 | { 13 | var impls = ChallengeHelpers.GetIncorrectImplementationTypes(); 14 | var code = string.Join(Environment.NewLine, 15 | impls.Select(imp => $"public class {imp.Name}_Tests : {nameof(IncorrectImplementation_TestsBase)} {{}}") 16 | ); 17 | Console.WriteLine(code); 18 | } 19 | 20 | [Test] 21 | public void CheckAllTestsAreInPlace() 22 | { 23 | var implTypes = ChallengeHelpers.GetIncorrectImplementationTypes(); 24 | var testedImpls = ChallengeHelpers.GetIncorrectImplementationTests() 25 | .Select(t => t.CreateStatistics()) 26 | .ToArray(); 27 | 28 | foreach (var impl in implTypes) 29 | { 30 | Assert.NotNull(testedImpls.SingleOrDefault(t => t.GetType().FullName == impl.FullName), 31 | "Single implementation of tests for {0} not found. Regenerate tests with test above!", impl.FullName); 32 | } 33 | } 34 | } 35 | 36 | #region Generated with test above 37 | 38 | public class WordsStatisticsC_Tests : IncorrectImplementation_TestsBase { } 39 | public class WordsStatisticsE_Tests : IncorrectImplementation_TestsBase { } 40 | public class WordsStatisticsCR_Tests : IncorrectImplementation_TestsBase { } 41 | public class WordsStatisticsE2_Tests : IncorrectImplementation_TestsBase { } 42 | public class WordsStatisticsE3_Tests : IncorrectImplementation_TestsBase { } 43 | public class WordsStatisticsE4_Tests : IncorrectImplementation_TestsBase { } 44 | public class WordsStatisticsL2_Tests : IncorrectImplementation_TestsBase { } 45 | public class WordsStatisticsL3_Tests : IncorrectImplementation_TestsBase { } 46 | public class WordsStatisticsL4_Tests : IncorrectImplementation_TestsBase { } 47 | public class WordsStatisticsO1_Tests : IncorrectImplementation_TestsBase { } 48 | public class WordsStatisticsO2_Tests : IncorrectImplementation_TestsBase { } 49 | public class WordsStatisticsO3_Tests : IncorrectImplementation_TestsBase { } 50 | public class WordsStatisticsO4_Tests : IncorrectImplementation_TestsBase { } 51 | public class WordsStatistics123_Tests : IncorrectImplementation_TestsBase { } 52 | public class WordsStatistics998_Tests : IncorrectImplementation_TestsBase { } 53 | public class WordsStatistics999_Tests : IncorrectImplementation_TestsBase { } 54 | public class WordsStatisticsEN1_Tests : IncorrectImplementation_TestsBase { } 55 | public class WordsStatisticsEN2_Tests : IncorrectImplementation_TestsBase { } 56 | public class WordsStatisticsQWE_Tests : IncorrectImplementation_TestsBase { } 57 | public class WordsStatisticsSTA_Tests : IncorrectImplementation_TestsBase { } 58 | 59 | #endregion 60 | } -------------------------------------------------------------------------------- /cs/Challenge/Readme.md: -------------------------------------------------------------------------------- 1 | # Задание Challenge 2 | 3 | 1. Изучи интерфейс IWordsStatistics и его референсную реализацию. 4 | В файле DoNotOpen.cs (не открывай его!) находится еще некоторое количество реализаций, каждая из которых содержит некоторую ошибку. 5 | 6 | 2. В файле WordsStatistics_Tests добавь тесты, проверяющие корректность реализации интерфейса IWordsStatistics. 7 | Требования к реализации IWordsStatistics восстанови по документации и по референсной реализации в классе WordsStatistics. 8 | 9 | Референсная реализация должна проходить все ваши тесты. 10 | Каждая некорректная реализация должна падать хотя бы на одном тесте. 11 | 12 | Проверить эти два условия можно запустив проект как exe-файл. 13 | -------------------------------------------------------------------------------- /cs/Challenge/Solved/WordsStatistics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Challenge.Solved 6 | { 7 | public class WordsStatistics : IWordsStatistics 8 | { 9 | protected readonly IDictionary statistics 10 | = new Dictionary(); 11 | 12 | public virtual void AddWord(string word) 13 | { 14 | if (word == null) throw new ArgumentNullException(nameof(word)); 15 | if (string.IsNullOrWhiteSpace(word)) return; 16 | if (word.Length > 10) 17 | word = word.Substring(0, 10); 18 | int count; 19 | statistics[word.ToLower()] = 1 + (statistics.TryGetValue(word.ToLower(), out count) ? count : 0); 20 | } 21 | 22 | public virtual IEnumerable GetStatistics() 23 | { 24 | return statistics 25 | .Select(WordCount.Create) 26 | .OrderByDescending(wordCount => wordCount.Count) 27 | .ThenBy(wordCount => wordCount.Word); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /cs/Challenge/Solved/codes.txt: -------------------------------------------------------------------------------- 1 | E - Null, Empty, Whitespaces 2 | CR - Whitespaces 3 | C - Case 4 | O - Order 5 | L - Length 6 | 123 - Hash 7 | 998 - Add Different Performance 8 | 999 - Add Same Performance 9 | QWE - All chars lower case 10 | STA - Static 11 | EN - To cache or not to cache -------------------------------------------------------------------------------- /cs/Challenge/WordCount.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Challenge 4 | { 5 | public struct WordCount 6 | { 7 | public WordCount(string word, int count) 8 | { 9 | Word = word; 10 | Count = count; 11 | } 12 | 13 | public string Word { get; set; } 14 | public int Count { get; set; } 15 | 16 | public static WordCount Create(KeyValuePair pair) 17 | { 18 | return new WordCount(pair.Key, pair.Value); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /cs/Challenge/WordsStatistics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Challenge 6 | { 7 | /** 8 | * 9 | * Частотный словарь добавленных слов. 10 | * Слова сравниваются без учета регистра символов. 11 | * Порядок — по убыванию частоты слова. 12 | * При одинаковой частоте — в лексикографическом порядке. 13 | * 14 | */ 15 | public class WordsStatistics : IWordsStatistics 16 | { 17 | protected readonly IDictionary statistics 18 | = new Dictionary(); 19 | 20 | public virtual void AddWord(string word) 21 | { 22 | if (word == null) throw new ArgumentNullException(nameof(word)); 23 | if (string.IsNullOrWhiteSpace(word)) return; 24 | if (word.Length > 10) 25 | word = word.Substring(0, 10); 26 | int count; 27 | statistics[word.ToLower()] = 1 + (statistics.TryGetValue(word.ToLower(), out count) ? count : 0); 28 | } 29 | 30 | public virtual IEnumerable GetStatistics() 31 | { 32 | return statistics 33 | .Select(WordCount.Create) 34 | .OrderByDescending(wordCount => wordCount.Count) 35 | .ThenBy(wordCount => wordCount.Word); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /cs/Challenge/WordsStatistics_Tests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace Challenge 5 | { 6 | [TestFixture] 7 | public class WordsStatistics_Tests 8 | { 9 | public virtual IWordsStatistics CreateStatistics() 10 | { 11 | // меняется на разные реализации при запуске exe 12 | return new WordsStatistics(); 13 | } 14 | 15 | private IWordsStatistics wordsStatistics; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | wordsStatistics = CreateStatistics(); 21 | } 22 | 23 | [Test] 24 | public void GetStatistics_IsEmpty_AfterCreation() 25 | { 26 | wordsStatistics.GetStatistics().Should().BeEmpty(); 27 | } 28 | 29 | [Test] 30 | public void GetStatistics_ContainsItem_AfterAddition() 31 | { 32 | wordsStatistics.AddWord("abc"); 33 | wordsStatistics.GetStatistics().Should().Equal(new WordCount("abc", 1)); 34 | } 35 | 36 | [Test] 37 | public void GetStatistics_ContainsManyItems_AfterAdditionOfDifferentWords() 38 | { 39 | wordsStatistics.AddWord("abc"); 40 | wordsStatistics.AddWord("def"); 41 | wordsStatistics.GetStatistics().Should().HaveCount(2); 42 | } 43 | 44 | 45 | // Документация по FluentAssertions с примерами : https://github.com/fluentassertions/fluentassertions/wiki 46 | } 47 | } -------------------------------------------------------------------------------- /cs/Challenge/YourName.cs: -------------------------------------------------------------------------------- 1 | namespace Challenge 2 | { 3 | public static class YourName 4 | { 5 | /// 6 | /// Ваши фамилии через пробел. Например, "Egorov Shagalina" 7 | /// 8 | public const string Authors = ""; 9 | } 10 | } -------------------------------------------------------------------------------- /cs/Challenge/history/bootcamp-1-2017-08/leaderboard-1h-beforeOpen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /cs/Challenge/history/bootcamp-1-2017-08/leaderboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /cs/Challenge/history/bootcamp-1-2017-08/testing-challenge-20170809-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "Egorov" : { 3 | "implementations" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], 4 | "time" : "2017-08-09T06:47:31.0687744Z" 5 | }, 6 | "Sadykov" : { 7 | "implementations" : [ 12, 3, 0, 1, 0, 1, 5, 0, 0, 2, 3, 2, 8, 0, 0, 0, 0, 0, 0, 0 ], 8 | "time" : "2017-08-09T07:47:31.9107676Z" 9 | }, 10 | "Shelomentsev" : { 11 | "implementations" : [ 3, 2, 1, 1, 0, 1, 2, 3, 1, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0 ], 12 | "time" : "2017-08-09T07:47:15.6943934Z" 13 | }, 14 | "Vasileva" : { 15 | "implementations" : [ 1, 4, 3, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 ], 16 | "time" : "2017-08-09T07:43:46.1250475Z" 17 | }, 18 | "Березникер" : { 19 | "implementations" : [ 5, 3, 1, 1, 0, 2, 4, 2, 1, 1, 1, 1, 3, 2, 0, 0, 0, 0, 0, 0 ], 20 | "time" : "2017-08-09T07:46:45.8987259Z" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cs/Challenge/history/innopolis-2016-10/before-open-1h.json: -------------------------------------------------------------------------------- 1 | {" ":[1,1,1,-1,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0],"Aleksey DESKTOP-L4LVEEV":[1,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Andrey Maslov DESKTOP-EE8TCE0":[2,1,2,0,0,0,0,3,1,2,0,1,1,0,1,0,0,0,0,0],"Aydar DESKTOP-K9QLAH5":[2,1,3,0,0,0,0,0,0,0,0,0,2,0,1,0,0,0,0,0],"Master DESKTOP-7I27A5P":[0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],"Samsung WINCTRL-E33OGAJ":[0,2,3,1,0,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0],"Theo -> Master DESKTOP-7I27A5P":[1,1,3,0,0,0,0,0,0,0,2,0,2,2,3,0,0,0,0,0],"VladVin VLAD":[1,2,4,1,1,0,1,1,0,1,0,1,2,0,1,1,1,0,0,0],"Zloj DESKTOP-AVFOCV7":[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ilnaz ASUS-ZENBOOK-UX":[0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0],"kiselev ANTONKISELE4C2D":[0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"nprok SARCASM":[1,2,6,2,0,2,0,3,2,1,0,1,3,0,1,0,0,0,0,0],"pe PC00GL7D":[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"regis XOMAK-BOOK":[4,2,11,1,0,0,0,1,3,0,0,0,0,0,1,1,1,0,0,0],"Артем OREHOUSE-PC":[2,2,6,1,0,0,0,1,2,1,2,2,4,2,1,0,0,0,0,0],"ДНС DEXP":[2,2,4,1,1,0,1,2,1,1,1,0,2,1,3,0,0,0,0,0]} -------------------------------------------------------------------------------- /cs/Challenge/history/innopolis-2016-10/leaderboard.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 |
12 | 13 | 39 | 40 | -------------------------------------------------------------------------------- /cs/Challenge/history/intern-16/before-open.json: -------------------------------------------------------------------------------- 1 | {"Acer ACER-ПК":[1,1,4,0,1,0,1,1,1,1,1,0,2,1,0,0,0,0,0,0],"Anatoli GAIA":[1,2,4,0,1,0,1,1,0,0,1,1,3,1,1,0,0,0,0,0],"Anna MICROSOFT-PC":[1,3,6,2,1,0,2,1,1,0,1,1,2,1,1,0,0,0,0,0],"Artem DESKTOP-LQ6ECV6":[3,3,6,1,1,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0],"Klei KLEINOTE":[1,2,2,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0],"Lexa27 LEXA27-PC":[1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0],"Mikhail MIKHAILPC":[1,1,4,1,1,0,1,2,1,0,1,0,1,1,1,0,0,0,0,0],"PhonkX PHONKXPC":[1,1,5,0,1,0,1,1,2,1,1,0,1,1,1,0,0,0,0,0],"User MSI":[4,0,4,0,1,0,1,0,0,0,1,1,2,1,1,0,0,0,0,0],"VasyaSavincov VASYA-MACBOOK":[1,1,1,1,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0],"epeshk ASUS-PC":[3,2,10,1,1,0,1,2,3,1,0,1,1,0,2,0,0,0,0,0],"pavel_ PAVEL":[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"pe PC00GL7D":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"torte TORTE-PC":[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],"uc-ser NV-UCH-010":[3,1,7,1,1,0,1,1,3,0,1,1,2,1,1,0,0,0,0,0],"Администратор ADMIN-CQSG17RPT":[1,2,5,1,1,0,1,1,1,1,0,0,1,0,1,0,0,0,0,0],"Александр ALEXANDER-HP":[1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,0,0,0,0],"Аркадий ARKADY-MACBOOK":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Евгений HP":[2,1,3,1,1,1,1,3,3,1,1,1,2,1,1,0,0,0,1,0],"Николай DIMA-PC":[3,3,7,2,1,0,1,3,0,0,2,2,4,2,2,1,0,0,0,0]} -------------------------------------------------------------------------------- /cs/Challenge/history/intern-16/final.json: -------------------------------------------------------------------------------- 1 | {"Acer ACER-ПК":[1,2,5,0,1,0,1,1,1,1,1,1,2,1,1,0,0,0,0,0],"Anatoli GAIA":[2,2,5,0,1,0,1,1,1,0,1,1,3,1,1,0,0,0,0,0],"Anna MICROSOFT-PC":[2,3,9,2,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,1],"Artem DESKTOP-LQ6ECV6":[5,8,11,6,1,2,1,5,2,3,0,0,0,0,0,0,0,0,1,0],"Klei KLEINOTE":[1,2,4,1,1,1,1,1,2,1,1,0,1,1,1,1,1,0,1,0],"Lexa27 LEXA27-PC":[1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0],"Mikhail MIKHAILPC":[1,2,5,1,1,0,1,3,1,1,1,1,2,1,1,0,0,0,0,0],"Onotole WORK-PC":[0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],"PhonkX PHONKXPC":[1,2,5,0,1,0,1,1,1,1,1,1,2,1,1,0,0,0,0,0],"Samoilov Sergei DESKTOP-2A7QJJU":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"User MSI":[4,1,5,1,1,0,1,1,1,0,1,1,2,1,2,0,0,0,0,0],"VasyaSavincov VASYA-MACBOOK":[1,1,3,1,1,1,1,2,0,1,1,1,2,1,1,0,0,0,0,0],"epeshk ASUS-PC":[10,5,18,4,1,1,1,2,9,1,1,1,2,1,2,1,1,1,4,1],"pavel_ PAVEL":[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"pe PC00GL7D":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"torte TORTE-PC":[0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],"uc-ser NV-UCH-010":[3,3,4,2,2,1,2,4,4,2,4,1,4,4,2,1,1,1,1,1],"Администратор ADMIN-CQSG17RPT":[2,2,10,1,1,1,1,2,1,2,2,1,3,2,2,1,1,1,1,1],"Александр ALEXANDER-HP":[1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,0,0,0,0],"Аркадий ARKADY-MACBOOK":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Евгений HP":[2,1,5,1,1,1,1,3,3,1,1,1,2,1,1,2,1,0,1,0],"Николай DIMA-PC":[4,3,9,2,1,0,1,2,0,0,2,2,4,2,2,2,1,1,1,0]} -------------------------------------------------------------------------------- /cs/Challenge/history/intern-16/leaderboard_before-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 40 | 41 | -------------------------------------------------------------------------------- /cs/Challenge/history/intern-16/leaderboard_final.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 40 | 41 | -------------------------------------------------------------------------------- /cs/Challenge/history/intern-17-1/1h-before-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 91 | 92 | -------------------------------------------------------------------------------- /cs/Challenge/history/intern-17-1/1h-before.json: -------------------------------------------------------------------------------- 1 | { 2 | "Babkin Rastorguev" : { 3 | "implementations" : [ 2, 1, 1, 1, 1, 1, 4, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 ], 4 | "time" : "2017-03-16T07:00:01.8509458Z" 5 | }, 6 | "Bessonov Karmanov" : { 7 | "implementations" : [ 1, 2, 1, 1, 0, 1, 2, 1, 0, 0, 2, 0, 2, 0, 0, 0, 1, 0, 0, 0 ], 8 | "time" : "2017-03-16T06:58:47.0157047Z" 9 | }, 10 | "Egorov" : { 11 | "implementations" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], 12 | "time" : "2017-03-16T06:00:15.635945Z" 13 | }, 14 | "Gachegov Kursanova" : { 15 | "implementations" : [ 2, 2, 1, 1, 0, 1, 1, 2, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0 ], 16 | "time" : "2017-03-16T06:59:05.3923935Z" 17 | }, 18 | "Gilemzyanov Akentev" : { 19 | "implementations" : [ 2, 1, 1, 1, 0, 1, 3, 2, 2, 2, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0 ], 20 | "time" : "2017-03-16T06:59:02.6359296Z" 21 | }, 22 | "Koshara Starkov" : { 23 | "implementations" : [ 1, 2, 2, 1, 1, 1, 3, 1, 0, 1, 1, 1, 1, 0, 0, 0, 2, 1, 0, 0 ], 24 | "time" : "2017-03-16T06:59:14.613288Z" 25 | }, 26 | "Pavlenko Varlakov" : { 27 | "implementations" : [ 6, 2, 1, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 2, 0 ], 28 | "time" : "2017-03-16T06:58:55.1785871Z" 29 | }, 30 | "Shirokov Luppov" : { 31 | "implementations" : [ 1, 4, 3, 1, 0, 1, 2, 2, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 ], 32 | "time" : "2017-03-16T06:55:59.5150345Z" 33 | }, 34 | "Zelenin Fettser" : { 35 | "implementations" : [ 2, 1, 0, 2, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ], 36 | "time" : "2017-03-16T06:58:19.7362143Z" 37 | }, 38 | "Ермак Тушов" : { 39 | "implementations" : [ 11, 0, 0, 0, 0, 0, 5, 2, 0, 2, 0, 2, 3, 0, 0, 0, 3, 3, 0, 0 ], 40 | "time" : "2017-03-16T06:53:52.6088896Z" 41 | }, 42 | "Квантовый Механик" : { 43 | "implementations" : [ 1, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0 ], 44 | "time" : "2017-03-16T06:40:55.2188505Z" 45 | }, 46 | "Мустакимов Лысенко" : { 47 | "implementations" : [ 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 2, 0, 0, 0 ], 48 | "time" : "2017-03-16T08:56:20.776176Z" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cs/Challenge/history/kampus-2016-11/1h-before-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 42 | 43 | -------------------------------------------------------------------------------- /cs/Challenge/history/kampus-2016-11/1h-before-open.json: -------------------------------------------------------------------------------- 1 | {"":{"implementations":[1,2,2,1,1,1,2,1,2,1,1,1,1,0,0,0,5,0,0,0],"time":"2016-11-03T07:24:09.4469179Z"},"":{"implementations":[2,2,1,1,0,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0],"time":"2016-11-03T07:25:44.2986939Z"},"":{"implementations":[3,1,1,2,0,2,1,0,1,1,1,1,1,1,1,0,0,0,1,0],"time":"2016-11-03T07:24:24.4310823Z"},"Alkapov Gudonis":{"implementations":[1,1,1,1,0,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0],"time":"2016-11-03T07:27:51.8420616Z"},"Ankudinov Savenkov":{"implementations":[2,3,2,1,0,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0],"time":"2016-11-03T07:27:50.3386257Z"},"Dobrovolskiy Boklazhenko":{"implementations":[1,2,1,1,0,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0],"time":"2016-11-03T07:23:21.9593147Z"},"Galiakhmetov khasanov":{"implementations":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"time":"2016-11-03T07:27:16.2132981Z"},"Kiselev Sozonov":{"implementations":[1,1,1,1,0,1,1,0,0,1,1,1,1,0,0,0,1,0,0,0],"time":"2016-11-03T07:27:09.8717424Z"},"Romass Gulin":{"implementations":[1,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,1,0,0,0],"time":"2016-11-03T07:07:05.827739Z"},"Ехлаков Наймушин":{"implementations":[1,1,1,1,1,1,2,1,1,2,2,2,4,0,0,0,0,0,0,1],"time":"2016-11-03T07:26:32.1004636Z"}} -------------------------------------------------------------------------------- /cs/Challenge/history/kontur-2016-07/before-open.json: -------------------------------------------------------------------------------- 1 | {"dgoleo K1412003":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lda KONTURBOOK":[1,1,4,1,1,0,1,2,5,2,1,1,2,1,2,1,1,0,0,0],"pe PC00GL7D":[0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],"ponkin_mikhail R9VY6CG":[2,2,14,1,1,0,1,1,5,1,1,1,2,1,1,0,0,0,0,0],"professor NV-UCH-010":[3,2,2,1,1,0,1,1,6,0,0,2,3,0,2,0,0,0,0,0],"tretyakov K1304010":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"uc-ser NV-UCH-010":[2,2,2,1,1,2,1,3,2,2,0,1,1,0,1,0,0,0,0,0]} -------------------------------------------------------------------------------- /cs/Challenge/history/kontur-2016-07/final.json: -------------------------------------------------------------------------------- 1 | {"dgoleo K1412003":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lda KONTURBOOK":[2,1,5,1,1,1,1,3,5,2,1,1,2,1,3,1,1,0,1,0],"pe PC00GL7D":[0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],"ponkin_mikhail R9VY6CG":[3,2,16,1,1,1,1,1,5,1,1,1,2,1,1,0,0,0,1,0],"professor NV-UCH-010":[5,2,3,1,1,1,1,2,7,1,1,2,3,1,2,1,1,1,1,0],"tretyakov K1304010":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"uc-ser NV-UCH-010":[5,2,5,1,1,2,1,3,4,2,0,1,2,0,1,0,0,0,1,0]} -------------------------------------------------------------------------------- /cs/Challenge/history/kontur-2016-07/leaderboard_before-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 40 | 41 | -------------------------------------------------------------------------------- /cs/Challenge/history/kontur-2016-10/after-open-1h20mjson: -------------------------------------------------------------------------------- 1 | {"Ananas ANANAS-PC":[2,1,5,1,2,1,1,2,2,2,1,1,2,2,2,1,1,0,2,0],"User USER-PC":[2,2,4,1,1,1,1,3,1,1,1,1,1,2,1,1,2,1,3,0],"avlasov K1510000038":[5,4,13,3,1,1,1,2,4,1,1,3,4,1,3,1,2,1,1,1],"damir K1510000031":[3,3,7,1,2,1,2,4,2,1,1,1,2,1,2,0,0,0,1,0],"gelverpl K1607N0080373":[0,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0],"glushkov K1606036":[3,1,12,1,1,1,3,2,1,4,4,1,6,2,2,0,2,0],"mak K1607N0080377":[3,2,7,1,1,1,1,2,1,2,1,0,2,1,1,0,2,0],"smv K1607N0080375":[2,1,5,1,1,1,1,2,2,2,1,3,4,1,3,1,1,0,1,0],"t1mmaas TIMURKHAZAM41C7":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"vkochev K1508000047":[5,1,11,1,1,1,1,2,1,1,1,2,4,1,2,1,0,0,1,0],"zudwa K1510000036":[2,1,7,1,1,1,1,5,3,2,1,2,5,1,4,0,0,0,1,0]} -------------------------------------------------------------------------------- /cs/Challenge/history/kontur-2016-10/leaderboard-after-open-1h20m.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 40 | 41 | -------------------------------------------------------------------------------- /cs/Challenge/history/shpora-2016/1h20m.json: -------------------------------------------------------------------------------- 1 | {"word-statistics":{"Akulin, Zakharov":{"implementations":[1,2,6,1,1,1,1,2,1,2,1,1,2,1,3,0,0,0,0,0]},"Alexander Satov & Artem Ryzhkin":{"implementations":[2,2,6,2,2,0,2,2,1,2,2,1,3,2,2,0,0,0,0,0]},"Evgeny Peshkov & Artem Borzov":{"implementations":[4,4,30,3,1,1,1,1,2,1,1,2,3,2,3,1,3,1,2,1]},"Liapustin Khapov":{"implementations":[3,1,12,1,2,1,2,3,2,1,2,4,7,2,7,0,0,0,0,0]},"Nevolin, Pliskovsky":{"implementations":[2,6,8,5,1,1,1,1,1,1,0,1,1,0,1,0,0,0,1,0]},"SivukhinAndRyabinin":{"implementations":[5,4,14,2,1,1,1,3,5,2,2,5,7,2,5,1,1,0,1,0]},"Smirnov Ivan, Tolstov Anton":{"implementations":[5,2,15,1,1,1,1,1,1,1,1,1,3,1,2,0,0,0,1,0]},"Vostretsov Belov":{"implementations":[2,3,9,2,1,1,1,2,1,1,1,1,3,1,2,1,0,0,1,0]},"fedyanin timerkhanov":{"implementations":[2,2,5,1,1,1,1,5,4,1,1,2,5,1,2,1,1,0,1,0]},"Дубровин, Трофимов":{"implementations":[2,1,5,1,1,0,1,1,1,1,1,1,2,1,1,2,0,0,1,0]}}} -------------------------------------------------------------------------------- /cs/Challenge/history/shpora-2016/50m.json: -------------------------------------------------------------------------------- 1 | {"word-statistics":{"Akulin, Zakharov":{"implementations":[1,2,6,1,1,0,1,1,0,1,0,1,2,0,2,0,0,0,0,0]},"Alexander Belev":{"implementations":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"Alexander Satov & Artem Ryzhkin":{"implementations":[2,2,4,2,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0]},"Evgeny Peshkov & Artem Borzov":{"implementations":[4,4,25,3,1,0,1,1,2,1,1,2,3,2,3,1,2,0,2,1]},"Liapustin Khapov":{"implementations":[3,1,10,1,1,0,1,2,1,0,2,3,6,2,6,0,0,0,0,0]},"Magnius":{"implementations":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"Nevolin, Pliskovsky":{"implementations":[1,6,7,5,1,0,1,1,1,1,0,1,1,0,1,0,0,0,0,0]},"SivukhinAndRyabinin":{"implementations":[4,3,13,2,1,0,1,3,4,2,0,0,0,0,0,0,0,0,1,0]},"Smirnov Ivan, Tolstov Anton":{"implementations":[4,1,11,1,1,0,1,1,1,1,1,0,2,1,1,0,0,0,0,0]},"Test":{"implementations":[0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"Vostretsov Belov":{"implementations":[1,3,6,2,1,1,1,2,0,1,1,1,3,1,2,1,0,0,0,0]},"Vostretsov Mikhail":{"implementations":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"epeshk&qoter@crash":{"implementations":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"fedyanin timerkhanov":{"implementations":[1,2,3,1,1,1,1,3,4,0,1,2,4,1,2,0,0,0,0,0]},"kirill":{"implementations":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"maxim akulin":{"implementations":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"pavel egorov":{"implementations":[0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0]},"Дубровин, Трофимов":{"implementations":[1,2,3,1,1,0,1,1,1,1,0,1,1,0,1,1,0,0,0,0]}}} -------------------------------------------------------------------------------- /cs/Challenge/history/shpora-2016/leaderboard-50m.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 44 | 45 | -------------------------------------------------------------------------------- /cs/HomeExercises/HomeExercises.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | HomeExercises 7 | ObjectComparison 8 | 8 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /cs/HomeExercises/NumberValidatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | 6 | namespace HomeExercises 7 | { 8 | public class NumberValidatorTests 9 | { 10 | [Test] 11 | public void Test() 12 | { 13 | Assert.Throws(() => new NumberValidator(-1, 2, true)); 14 | Assert.DoesNotThrow(() => new NumberValidator(1, 0, true)); 15 | Assert.Throws(() => new NumberValidator(-1, 2, false)); 16 | Assert.DoesNotThrow(() => new NumberValidator(1, 0, true)); 17 | 18 | Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0")); 19 | Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0")); 20 | Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0")); 21 | Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("00.00")); 22 | Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-0.00")); 23 | Assert.IsTrue(new NumberValidator(17, 2, true).IsValidNumber("0.0")); 24 | Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("+0.00")); 25 | Assert.IsTrue(new NumberValidator(4, 2, true).IsValidNumber("+1.23")); 26 | Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("+1.23")); 27 | Assert.IsFalse(new NumberValidator(17, 2, true).IsValidNumber("0.000")); 28 | Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("-1.23")); 29 | Assert.IsFalse(new NumberValidator(3, 2, true).IsValidNumber("a.sd")); 30 | } 31 | } 32 | 33 | public class NumberValidator 34 | { 35 | private readonly Regex numberRegex; 36 | private readonly bool onlyPositive; 37 | private readonly int precision; 38 | private readonly int scale; 39 | 40 | public NumberValidator(int precision, int scale = 0, bool onlyPositive = false) 41 | { 42 | this.precision = precision; 43 | this.scale = scale; 44 | this.onlyPositive = onlyPositive; 45 | if (precision <= 0) 46 | throw new ArgumentException("precision must be a positive number"); 47 | if (scale < 0 || scale >= precision) 48 | throw new ArgumentException("precision must be a non-negative number less or equal than precision"); 49 | numberRegex = new Regex(@"^([+-]?)(\d+)([.,](\d+))?$", RegexOptions.IgnoreCase); 50 | } 51 | 52 | public bool IsValidNumber(string value) 53 | { 54 | // Проверяем соответствие входного значения формату N(m,k), в соответствии с правилом, 55 | // описанным в Формате описи документов, направляемых в налоговый орган в электронном виде по телекоммуникационным каналам связи: 56 | // Формат числового значения указывается в виде N(m.к), где m – максимальное количество знаков в числе, включая знак (для отрицательного числа), 57 | // целую и дробную часть числа без разделяющей десятичной точки, k – максимальное число знаков дробной части числа. 58 | // Если число знаков дробной части числа равно 0 (т.е. число целое), то формат числового значения имеет вид N(m). 59 | 60 | if (string.IsNullOrEmpty(value)) 61 | return false; 62 | 63 | var match = numberRegex.Match(value); 64 | if (!match.Success) 65 | return false; 66 | 67 | // Знак и целая часть 68 | var intPart = match.Groups[1].Value.Length + match.Groups[2].Value.Length; 69 | // Дробная часть 70 | var fracPart = match.Groups[4].Value.Length; 71 | 72 | if (intPart + fracPart > precision || fracPart > scale) 73 | return false; 74 | 75 | if (onlyPositive && match.Groups[1].Value == "-") 76 | return false; 77 | return true; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /cs/HomeExercises/ObjectComparison.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace HomeExercises 5 | { 6 | public class ObjectComparison 7 | { 8 | [Test] 9 | [Description("Проверка текущего царя")] 10 | [Category("ToRefactor")] 11 | public void CheckCurrentTsar() 12 | { 13 | var actualTsar = TsarRegistry.GetCurrentTsar(); 14 | 15 | var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, 16 | new Person("Vasili III of Russia", 28, 170, 60, null)); 17 | 18 | // Перепишите код на использование Fluent Assertions. 19 | Assert.AreEqual(actualTsar.Name, expectedTsar.Name); 20 | Assert.AreEqual(actualTsar.Age, expectedTsar.Age); 21 | Assert.AreEqual(actualTsar.Height, expectedTsar.Height); 22 | Assert.AreEqual(actualTsar.Weight, expectedTsar.Weight); 23 | 24 | Assert.AreEqual(expectedTsar.Parent!.Name, actualTsar.Parent!.Name); 25 | Assert.AreEqual(expectedTsar.Parent.Age, actualTsar.Parent.Age); 26 | Assert.AreEqual(expectedTsar.Parent.Height, actualTsar.Parent.Height); 27 | Assert.AreEqual(expectedTsar.Parent.Parent, actualTsar.Parent.Parent); 28 | } 29 | 30 | [Test] 31 | [Description("Альтернативное решение. Какие у него недостатки?")] 32 | public void CheckCurrentTsar_WithCustomEquality() 33 | { 34 | var actualTsar = TsarRegistry.GetCurrentTsar(); 35 | var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, 36 | new Person("Vasili III of Russia", 28, 170, 60, null)); 37 | 38 | // Какие недостатки у такого подхода? 39 | Assert.True(AreEqual(actualTsar, expectedTsar)); 40 | } 41 | 42 | private bool AreEqual(Person? actual, Person? expected) 43 | { 44 | if (actual == expected) return true; 45 | if (actual == null || expected == null) return false; 46 | return 47 | actual.Name == expected.Name 48 | && actual.Age == expected.Age 49 | && actual.Height == expected.Height 50 | && actual.Weight == expected.Weight 51 | && AreEqual(actual.Parent, expected.Parent); 52 | } 53 | } 54 | 55 | public class TsarRegistry 56 | { 57 | public static Person GetCurrentTsar() 58 | { 59 | return new Person( 60 | "Ivan IV The Terrible", 54, 170, 70, 61 | new Person("Vasili III of Russia", 28, 170, 60, null)); 62 | } 63 | } 64 | 65 | public class Person 66 | { 67 | public static int IdCounter = 0; 68 | public int Age, Height, Weight; 69 | public string Name; 70 | public Person? Parent; 71 | public int Id; 72 | 73 | public Person(string name, int age, int height, int weight, Person? parent) 74 | { 75 | Id = IdCounter++; 76 | Name = name; 77 | Age = age; 78 | Height = height; 79 | Weight = weight; 80 | Parent = parent; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /cs/HomeExercises/README.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание 2 | 3 | ## Сравнение объектов 4 | 5 | Изучите тест в классе ObjectComparison. 6 | Затем изучите [документацию FluentAssertions](http://fluentassertions.com/documentation.html). 7 | 8 | Перепишите тест с использованием наиболее подходящего метода FluentAssertions так чтобы: 9 | 10 | * тест продолжал работать, 11 | * его читаемость возрасла, 12 | * он стал расширяем: добавление свойст в класс Person должно приводить к минимуму изменений в тестах. 13 | 14 | В комментариях поясните, чем ваше решение лучше решения в методе CheckCurrentTsar_WithCustomEquality. 15 | 16 | ## Рефакторинг тестов 17 | 18 | Изучите код теста в классе NumberValidatorTests. 19 | 20 | Перепишите тест так, чтобы 21 | 22 | * найти и удалить повторяющиеся проверки, 23 | * найти недостающие проверки, 24 | * при падении теста было без стек-трейса понятно на каких данных код не работает, 25 | * одна упавшая проверка не блокировала прохождение остальных проверок. 26 | -------------------------------------------------------------------------------- /cs/Samples/AAA/Zip_Should.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Samples.AAA 6 | { 7 | [TestFixture] 8 | public class Zip_Should 9 | { 10 | [Test] 11 | public void GiveResultOfSameSize_OnEqualSizeArrays() 12 | { 13 | var arr1 = new[] { 1 }; 14 | var arr2 = new[] { 2 }; 15 | 16 | var result = arr1.Zip(arr2, Tuple.Create); 17 | 18 | CollectionAssert.AreEqual(new[] { Tuple.Create(1, 2) }, result); 19 | } 20 | 21 | [Test] 22 | public void BeEmpty_WhenBothInputsAreEmpty() 23 | { 24 | var arr1 = new int[0]; 25 | var arr2 = new int[0]; 26 | 27 | var result = arr1.Zip(arr2, Tuple.Create); 28 | 29 | CollectionAssert.IsEmpty(result); 30 | } 31 | 32 | [Test] 33 | public void BeEmpty_WhenFirstIsEmpty() 34 | { 35 | var arr1 = new int[0]; 36 | var arr2 = new[] { 1, 2 }; 37 | 38 | var result = arr1.Zip(arr2, Tuple.Create); 39 | 40 | CollectionAssert.IsEmpty(result); 41 | } 42 | 43 | [Test] 44 | public void BeEmpty_WhenSecondIsEmpty() 45 | { 46 | var arr1 = new[] { 1, 2 }; 47 | var arr2 = new int[0]; 48 | 49 | var result = arr1.Zip(arr2, Tuple.Create); 50 | 51 | CollectionAssert.IsEmpty(result); 52 | } 53 | 54 | [Test] 55 | public void HaveLengthOfSecond_WhenFirstContainsMoreElements() 56 | { 57 | var arr1 = new[] {1, 3, 5, 7}; 58 | var arr2 = new[] {2, 4}; 59 | 60 | var result = arr1.Zip(arr2, Tuple.Create); 61 | 62 | CollectionAssert.AreEqual(new[] 63 | { 64 | Tuple.Create(1, 2), 65 | Tuple.Create(3, 4) 66 | }, result); 67 | } 68 | 69 | [Test] 70 | public void HaveLengthOfFirst_WhenSecondContainsMoreElements() 71 | { 72 | var arr1 = new[] { 1, 3 }; 73 | var arr2 = new[] { 2, 4, 6, 8 }; 74 | 75 | var result = arr1.Zip(arr2, Tuple.Create); 76 | 77 | CollectionAssert.AreEqual(new[] 78 | { 79 | Tuple.Create(1, 2), 80 | Tuple.Create(3, 4) 81 | }, result); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /cs/Samples/Antipatterns/Stack1_Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Samples.Antipatterns 7 | { 8 | [TestFixture, Explicit] 9 | public class Stack1_Tests 10 | { 11 | [Test] 12 | public void Test() 13 | { 14 | var lines = File.ReadAllLines(@"C:\work\edu\testing-course\Patterns\bin\Debug\data.txt") 15 | .Select(line => line.Split(' ')) 16 | .Select(line => new { command = line[0], value = line[1] }); 17 | 18 | var stack = new Stack(); 19 | foreach (var line in lines) 20 | { 21 | if (line.command == "push") 22 | stack.Push(line.value); 23 | else 24 | Assert.AreEqual(line.value, stack.Pop()); 25 | } 26 | } 27 | 28 | #region Почему это плохо? 29 | /* 30 | ## Антипаттерн Local Hero 31 | 32 | Тест не будет работать на машине другого человека или на Build-сервере. 33 | Да и у того же самого человека после Clean Solution / переустановки ОС / повторного Clone репозитория / ... 34 | 35 | ## Решение 36 | 37 | Тест не должен зависеть от особенностей локальной среды. 38 | Если нужна работа с файлами, то либо включите файл в проект и настройте в свойствах его копирование в OutputDir, 39 | либо поместите его в ресурсы. 40 | 41 | var lines = File.ReadAllLines(@"data.txt") 42 | var lines = Resources.data.Split(new []{"\r\n"}, StringSplitOptions.RemoveEmptyEntries) 43 | */ 44 | #endregion 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cs/Samples/Antipatterns/Stack2_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Samples.Antipatterns 7 | { 8 | [TestFixture, Explicit] 9 | public class Stack2_Tests 10 | { 11 | [Test] 12 | public void TestPushPop() 13 | { 14 | var stack = new Stack(); 15 | stack.Push(10); 16 | stack.Push(20); 17 | stack.Push(30); 18 | while (stack.Any()) 19 | Console.WriteLine(stack.Pop()); 20 | } 21 | 22 | #region Почему это плохо? 23 | /* 24 | ## Антипаттерн Loudmouth 25 | 26 | Тест не является автоматическим. Если он сломается, никто этого не заметит. 27 | 28 | ## Мораль 29 | 30 | Вместо вывода на консоль, используйте Assert-ы. 31 | */ 32 | #endregion 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cs/Samples/Antipatterns/Stack3_Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Samples.Antipatterns 6 | { 7 | [TestFixture, Explicit] 8 | public class Stack3_Tests 9 | { 10 | [Test] 11 | public void Test() 12 | { 13 | var stack = new Stack(); 14 | Assert.IsFalse(stack.Any()); 15 | stack.Push(1); 16 | stack.Pop(); 17 | Assert.IsFalse(stack.Any()); 18 | stack.Push(1); 19 | stack.Push(2); 20 | stack.Push(3); 21 | Assert.AreEqual(3, stack.Count); 22 | stack.Pop(); 23 | stack.Pop(); 24 | stack.Pop(); 25 | Assert.IsFalse(stack.Any()); 26 | for (var i = 0; i < 1000; i++) 27 | stack.Push(i); 28 | for (var i = 1000; i > 0; i--) 29 | Assert.AreEqual(i - 1, stack.Pop()); 30 | } 31 | 32 | #region Почему этот тест плохой? 33 | /* 34 | ## Антипаттерн Freeride 35 | 36 | 1. Непонятна область его ответственности. Складывается впечатление, что он тестирует все, однако он это делает плохо. 37 | Он дает ложное чувство, что все протестировано. Хотя, например, этот тест не проверяет много важных случаев. 38 | 39 | 2. Таким тестам как-правило невозможно придумать внятное название. 40 | 41 | 3. Если что-то упадет в середине теста, будет сложно разобраться что именно пошло не так и сложно отлаживать — нужно жонглировать точками останова. 42 | 43 | 4. Такой тест не работает как документация. По этому сценарию непросто восстановить требования к тестируемому объекту. 44 | 45 | ## Мораль 46 | 47 | Каждый тест должен тестировать одно конкретное требование. Это требование должно отражаться в названии теста. 48 | Если вы не можете придумать название теста, у вас Free Ride! 49 | */ 50 | #endregion 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cs/Samples/Antipatterns/Stack4_Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Samples.Antipatterns 6 | { 7 | [TestFixture, Explicit] 8 | public class Stack4_Tests 9 | { 10 | [Test] 11 | public void TestPop() 12 | { 13 | var stack = new Stack(new[] { 1, 2, 3, 4, 5 }); 14 | var result = stack.Pop(); 15 | Assert.AreEqual(5, result); 16 | Assert.IsTrue(stack.Any()); 17 | Assert.AreEqual(4, stack.Count); 18 | Assert.AreEqual(4, stack.Peek()); 19 | Assert.AreEqual(new[] { 4, 3, 2, 1 }, stack.ToArray()); 20 | } 21 | 22 | #region Почему это плохо? 23 | /* 24 | ## Антипаттерн Overspecification 25 | 26 | 1. Непонятна область ответственности. Сложно придумать название. Не так плохо, как FreeRide, но плохо. 27 | 28 | 2. Изменение API роняет сразу много подобных тестов, создавая много рутинной работы по их починке. 29 | 30 | 3. Если все тесты будут такими, то при появлении бага, падают они большой компанией. 31 | 32 | 33 | ## Мораль 34 | 35 | Сфокусируйтесь на проверке одного конкретного требования в каждом тесте. 36 | Не старайтесь проверить "за одно" какое-то требование сразу во всех тестах — это может выйти боком. 37 | 38 | Признак возможной проблемы — более одного Assert на метод. 39 | */ 40 | #endregion 41 | } 42 | } -------------------------------------------------------------------------------- /cs/Samples/Parametrized/Double_Should.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Globalization; 3 | using NUnit.Framework; 4 | 5 | namespace Samples.Parametrized 6 | { 7 | [TestFixture] 8 | public class Double_Should 9 | { 10 | [Test, TestCaseSource(nameof(DivideTestCases))] 11 | public double Divide(double a, double b) 12 | { 13 | return a / b; 14 | } 15 | 16 | public static IEnumerable DivideTestCases 17 | { 18 | get 19 | { 20 | yield return new TestCaseData(12.0, 3.0).Returns(4); 21 | yield return new TestCaseData(12.0, 2.0).Returns(6); 22 | yield return new TestCaseData(12.0, 4.0).Returns(3); 23 | } 24 | } 25 | 26 | [TestCase("123", ExpectedResult = 123, TestName = "integer")] 27 | [TestCase("1.1", ExpectedResult = 1.1, TestName = "fraction")] 28 | [TestCase("1.1e1", ExpectedResult = 1.1e1, TestName = "scientific with positive exp")] 29 | [TestCase("1.1e-1", ExpectedResult = 1.1e-1, TestName = "scientific with negative exp")] 30 | [TestCase("-0.1", ExpectedResult = -0.1, TestName = "negative fraction")] 31 | public double Parse_WithInvariantCulture(string input) 32 | { 33 | return double.Parse(input, CultureInfo.InvariantCulture); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /cs/Samples/Samples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | Samples 7 | 8 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /cs/Samples/Samples.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | No -------------------------------------------------------------------------------- /cs/Samples/Specifications/Stack_Specification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | 5 | namespace Samples.Specifications 6 | { 7 | [TestFixture] 8 | public class Stack_Specification 9 | { 10 | [Test] 11 | public void Constructor_CreatesEmptyStack() 12 | { 13 | // ReSharper disable once CollectionNeverUpdated.Local 14 | var stack = new Stack(); 15 | 16 | Assert.AreEqual(0, stack.Count); 17 | } 18 | 19 | [Test] 20 | public void Constructor_PushesItemsToEmptyStack() 21 | { 22 | var stack = new Stack(new[] { 1, 2, 3 }); 23 | 24 | Assert.AreEqual(3, stack.Count); 25 | Assert.AreEqual(3, stack.Pop()); 26 | Assert.AreEqual(2, stack.Pop()); 27 | Assert.AreEqual(1, stack.Pop()); 28 | Assert.AreEqual(0, stack.Count); 29 | } 30 | 31 | [Test] 32 | public void ToArray_ReturnsItemsInPopOrder() 33 | { 34 | var stack = new Stack(new[] { 1, 2, 3 }); 35 | 36 | Assert.AreEqual(new[] { 3, 2, 1 }, stack.ToArray()); 37 | } 38 | 39 | [Test] 40 | public void Push_AddsItemToStackTop() 41 | { 42 | var stack = new Stack(new[] { 1, 2, 3 }); 43 | 44 | stack.Push(42); 45 | 46 | CollectionAssert.AreEqual(new[] { 42, 3, 2, 1 }, stack.ToArray()); 47 | } 48 | 49 | [Test] 50 | public void Pop_OnEmptyStack_Fails() 51 | { 52 | var stack = new Stack(); 53 | 54 | Assert.Throws(() => stack.Pop()); 55 | } 56 | 57 | [Test] 58 | public void Pop_ReturnsLastPushedItem() 59 | { 60 | var stack = new Stack(new[] { 1, 2, 3 }); 61 | 62 | stack.Push(42); 63 | 64 | Assert.AreEqual(42, stack.Pop()); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /cs/Samples/TestDataBuilder/TestDataBuilder_Sample_Tests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Samples.TestDataBuilder 4 | { 5 | [TestFixture] 6 | public class TestDataBuilder_Sample_Tests 7 | { 8 | [Test] 9 | public void WorksWithObjectMother() 10 | { 11 | var user = TestUsers.ARegularUser(); 12 | var adminUser = TestUsers.AnAdmin(); 13 | // Если в тестах нужно много комбинаций параметров, будет комбинаторный взрыв. 14 | } 15 | 16 | [Test] 17 | public void WorksWithBuilder() 18 | { 19 | var user = TestUserBuilder.AUser().Build(); 20 | var adminUser = TestUserBuilder.AUser().InAdminRole().Build(); 21 | // Такой код не ломается, при смене сигнатуры конструктора User. 22 | // Это важно, если таких тестов много. 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /cs/Samples/TestDataBuilder/TestUserBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Samples.TestDataBuilder 2 | { 3 | public class TestUserBuilder 4 | { 5 | public const string DEFAULT_NAME = "John Smith"; 6 | public const string DEFAULT_ROLE = "ROLE_USER"; 7 | public const string DEFAULT_PASSWORD = "42"; 8 | private string name = DEFAULT_NAME; 9 | private string? password = DEFAULT_PASSWORD; 10 | private string role = DEFAULT_ROLE; 11 | private string? login; 12 | 13 | private TestUserBuilder() 14 | { 15 | } 16 | 17 | public static TestUserBuilder AUser() 18 | { 19 | return new TestUserBuilder(); 20 | } 21 | 22 | public TestUserBuilder WithName(string newName) 23 | { 24 | name = newName; 25 | return this; 26 | } 27 | 28 | public TestUserBuilder WithLogin(string? newLogin) 29 | { 30 | login = newLogin; 31 | return this; 32 | } 33 | 34 | public TestUserBuilder WithPassword(string? newPassword) 35 | { 36 | password = newPassword; 37 | return this; 38 | } 39 | 40 | public TestUserBuilder WithNoPassword() 41 | { 42 | password = null; 43 | return this; 44 | } 45 | 46 | public TestUserBuilder InUserRole() => InRole("ROLE_USER"); 47 | 48 | public TestUserBuilder InAdminRole() => InRole("ROLE_ADMIN"); 49 | 50 | public TestUserBuilder InRole(string newRole) 51 | { 52 | this.role = newRole; 53 | return this; 54 | } 55 | 56 | public TestUserBuilder But() => 57 | AUser() 58 | .InRole(role) 59 | .WithName(name) 60 | .WithPassword(password) 61 | .WithLogin(login); 62 | 63 | public User Build() => new User(name, login, password, role); 64 | 65 | public static User ARegularUser() => AUser().Build(); 66 | 67 | public static User AnAdmin() => AUser() 68 | .WithName("Neo") 69 | .WithLogin("neo") 70 | .InAdminRole() 71 | .Build(); 72 | } 73 | } -------------------------------------------------------------------------------- /cs/Samples/TestDataBuilder/TestUsers.cs: -------------------------------------------------------------------------------- 1 | namespace Samples.TestDataBuilder 2 | { 3 | public class TestUsers 4 | { 5 | public static User ARegularUser() 6 | { 7 | return new User("Triniti", "tri", "asdasd", "ROLE_USER"); 8 | } 9 | 10 | public static User AnAdmin() 11 | { 12 | return new User("Agent Smith", "smith", "qweqwe", "ROLE_ADMIN"); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /cs/Samples/TestDataBuilder/User.cs: -------------------------------------------------------------------------------- 1 | namespace Samples.TestDataBuilder 2 | { 3 | public class User 4 | { 5 | private readonly string? login; 6 | private readonly string name; 7 | private readonly string? password; 8 | private readonly string role; 9 | 10 | public User(string name, string? login, string? password, string role) 11 | { 12 | this.name = name; 13 | this.login = login; 14 | this.password = password; 15 | this.role = role; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /cs/Samples/data.txt: -------------------------------------------------------------------------------- 1 | push 1 2 | pop 1 3 | push 2 4 | push 3 5 | pop 3 6 | pop 2 -------------------------------------------------------------------------------- /cs/testing.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.13 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{E7A56C48-8E36-465B-9F8E-67BC8525CFE5}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HomeExercises", "HomeExercises\HomeExercises.csproj", "{4F9FBCCA-43E0-431B-944D-834D16AD18F9}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Challenge", "Challenge\Challenge.csproj", "{BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {E7A56C48-8E36-465B-9F8E-67BC8525CFE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {E7A56C48-8E36-465B-9F8E-67BC8525CFE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {E7A56C48-8E36-465B-9F8E-67BC8525CFE5}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {E7A56C48-8E36-465B-9F8E-67BC8525CFE5}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {4F9FBCCA-43E0-431B-944D-834D16AD18F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {4F9FBCCA-43E0-431B-944D-834D16AD18F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {4F9FBCCA-43E0-431B-944D-834D16AD18F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {4F9FBCCA-43E0-431B-944D-834D16AD18F9}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BB8C2EFB-6AE9-45BD-8468-829D5AB79DCB}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /cs/testing.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | Tab 3 | False 4 | Tab 5 | False 6 | Tab 7 | False 8 | Tab 9 | False 10 | Tab 11 | False 12 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 13 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> 14 | True 15 | True 16 | True 17 | True 18 | True 19 | True 20 | True 21 | True 22 | True -------------------------------------------------------------------------------- /faq.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontur-courses/testing/9120c2429edab93c7653b63ee5df8c950bf1fbc3/faq.pptx -------------------------------------------------------------------------------- /feedback.url: -------------------------------------------------------------------------------- 1 | [{000214A0-0000-0000-C000-000000000046}] 2 | Prop3=19,2 3 | [InternetShortcut] 4 | IDList= 5 | URL=http://bit.ly/kontur-courses-feedback 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 19 | 20 | 21 |
22 | 23 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle/ 3 | 4 | target/ 5 | out/ 6 | build/ 7 | *private.resources 8 | *private.properties 9 | *local.properties 10 | *.iml 11 | *.log 12 | *.xlsx# -------------------------------------------------------------------------------- /java/Readme.md: -------------------------------------------------------------------------------- 1 | # Задание Challenge 2 | 3 | 1. Изучи интерфейс WordsStatistics и его референсную реализацию. 4 | В пакете donotopen (не открывай его!) находится еще некоторое количество реализаций, каждая из которых содержит некоторую ошибку. 5 | 6 | 2. В файле WordsStatisticsTests добавь тесты, проверяющие корректность реализации интерфейса WordsStatistics. 7 | Требования к реализации WordsStatistics восстанови по документации и по референсной реализации в классе WordsStatisticsImpl. 8 | 9 | Референсная реализация должна проходить все ваши тесты. 10 | Каждая некорректная реализация должна падать хотя бы на одном тесте. 11 | 12 | Проверить что каждая некорректная реализация падает хотя бы на одном тесте, можно запустив IncorrectImplementationTest 13 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' 11 | testImplementation "org.junit.jupiter:junit-jupiter-params" 12 | testImplementation "org.junit.platform:junit-platform-launcher" 13 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' 14 | } 15 | 16 | compileJava.options.encoding = 'UTF-8' 17 | compileTestJava.options.encoding = 'UTF-8' 18 | 19 | test { 20 | useJUnitPlatform() 21 | } -------------------------------------------------------------------------------- /java/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontur-courses/testing/9120c2429edab93c7653b63ee5df8c950bf1fbc3/java/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /java/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /java/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/WordCount.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses; 2 | 3 | import java.util.Objects; 4 | 5 | public class WordCount { 6 | private String word; 7 | private int count; 8 | 9 | public WordCount(String word, int count) { 10 | this.word = word; 11 | this.count = count; 12 | } 13 | 14 | public String getWord() { 15 | return word; 16 | } 17 | 18 | public void setWord(String word) { 19 | this.word = word; 20 | } 21 | 22 | public int getCount() { 23 | return count; 24 | } 25 | 26 | public void setCount(int count) { 27 | this.count = count; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | WordCount wordCount = (WordCount) o; 35 | return count == wordCount.count && Objects.equals(word, wordCount.word); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(word, count); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/WordStatisticImpl.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses; 2 | 3 | import java.util.Comparator; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.stream.Collectors; 8 | 9 | public class WordStatisticImpl implements WordStatistics { 10 | protected final Map statistics = new HashMap<>(); 11 | 12 | @Override 13 | public void addWord(String word) { 14 | if (word == null) throw new IllegalArgumentException(); 15 | if (word.isBlank()) return; 16 | if (word.length() > 10) 17 | word = word.substring(0, 10); 18 | String lowerWord = word.toLowerCase(); 19 | 20 | int count = statistics.getOrDefault(lowerWord, 0); 21 | statistics.put(lowerWord, 1 + count); 22 | } 23 | 24 | @Override 25 | public List getStatistics() { 26 | return statistics.entrySet().stream() 27 | .map(it -> new WordCount(it.getKey(), it.getValue())) 28 | .sorted(Comparator 29 | .comparing(WordCount::getCount) 30 | .reversed() 31 | .thenComparing(WordCount::getWord) 32 | ) 33 | .toList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/WordStatistics.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses; 2 | 3 | import java.util.List; 4 | 5 | public interface WordStatistics { 6 | void addWord(String word); 7 | List getStatistics(); 8 | } 9 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics01.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatisticImpl; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class WordStatistics01 extends WordStatisticImpl { 11 | @Override 12 | public List getStatistics() { 13 | return statistics.entrySet().stream().map(it -> new WordCount(it.getKey(), it.getValue())) 14 | .sorted(Comparator.comparing(WordCount::getWord)).toList(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics02.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatisticImpl; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class WordStatistics02 extends WordStatisticImpl { 11 | @Override 12 | public List getStatistics() { 13 | return statistics.entrySet().stream().map(it -> new WordCount(it.getKey(), it.getValue())) 14 | .sorted(Comparator.comparingInt(WordCount::getCount)).toList(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics03.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatisticImpl; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.StreamSupport; 10 | 11 | public class WordStatistics03 extends WordStatisticImpl { 12 | @Override 13 | public List getStatistics() { 14 | var statistics = super.getStatistics(); 15 | return StreamSupport.stream(statistics.spliterator(), false).sorted( 16 | Comparator.comparing(WordCount::getWord) 17 | ).toList(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics04.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatisticImpl; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.StreamSupport; 10 | 11 | public class WordStatistics04 extends WordStatisticImpl { 12 | @Override 13 | public List getStatistics() { 14 | var statistics = super.getStatistics(); 15 | return StreamSupport.stream(statistics.spliterator(), false) 16 | .sorted((left, right) -> Integer.compare(right.getCount(), left.getCount())) 17 | .sorted(Comparator.comparing(WordCount::getWord)).toList(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics123.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.IntStream; 10 | 11 | public class WordStatistics123 implements WordStatistics { 12 | private final int MAX_SIZE = 1237; 13 | 14 | private final int[] statistics = new int[MAX_SIZE]; 15 | private final String[] words = new String[MAX_SIZE]; 16 | 17 | @Override 18 | public void addWord(String word) { 19 | if (word == null) throw new IllegalArgumentException(); 20 | if (word.isBlank()) return; 21 | if (word.length() > 10) 22 | word = word.substring(0, 10); 23 | String lowerWord = word.toLowerCase(); 24 | 25 | var index = Math.abs(lowerWord.hashCode() % MAX_SIZE); 26 | statistics[index]++; 27 | words[index] = lowerWord; 28 | } 29 | 30 | @Override 31 | public List getStatistics() { 32 | return IntStream.range(0, Math.min(words.length, statistics.length)) 33 | .mapToObj(it -> new WordCount(words[it], statistics[it])) 34 | .filter(it -> it.getCount() > 0) 35 | .sorted((left, right) -> Integer.compare(right.getCount(), left.getCount())) 36 | .sorted(Comparator.comparing(WordCount::getWord)).toList(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics998.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.*; 7 | import java.util.stream.Collectors; 8 | 9 | public class WordStatistics998 implements WordStatistics { 10 | protected final List statistics = new ArrayList<>(); 11 | 12 | @Override 13 | public void addWord(String word) { 14 | if (word == null) throw new IllegalArgumentException(); 15 | if (word.isBlank()) return; 16 | if (word.length() > 10) 17 | word = word.substring(0, 10); 18 | String lowerWord = word.toLowerCase(); 19 | var wordCount = statistics.stream().filter(it -> it.getWord().equals(lowerWord)).findFirst(); 20 | if (wordCount.isPresent()) { 21 | statistics.remove(wordCount.get()); 22 | } else { 23 | wordCount = Optional.of(new WordCount(lowerWord, 0)); 24 | } 25 | statistics.add(new WordCount(wordCount.get().getWord(), wordCount.get().getCount() - 1)); 26 | 27 | statistics.sort((a, b) -> a.getCount() == b.getCount() ? a.getWord().compareTo(b.getWord()) : a.getCount() - b.getCount()); 28 | } 29 | 30 | @Override 31 | public List getStatistics() { 32 | return statistics.stream().map(it -> new WordCount(it.getWord(), -it.getCount())).toList(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatistics999.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.*; 7 | import java.util.stream.Collectors; 8 | 9 | public class WordStatistics999 implements WordStatistics { 10 | private final Set usedWords = new HashSet<>(); 11 | private final List statistics = new ArrayList<>(); 12 | 13 | @Override 14 | public void addWord(String word) { 15 | if (word == null) throw new IllegalArgumentException(); 16 | if (word.isBlank()) return; 17 | if (word.length() > 10) 18 | word = word.substring(0, 10); 19 | word = word.toLowerCase(); 20 | if (usedWords.contains(word)) { 21 | final var matchedWord = word; 22 | var stat = statistics.stream().filter(it -> it.getWord().equals(matchedWord)).findFirst().orElse(null); 23 | statistics.remove(stat); 24 | statistics.add(new WordCount(stat.getWord(), stat.getCount() + 1)); 25 | } else { 26 | statistics.add(new WordCount(word, 1)); 27 | usedWords.add(word); 28 | } 29 | } 30 | 31 | @Override 32 | public List getStatistics() { 33 | return statistics.stream().sorted((left, right) -> Integer.compare(right.getCount(), left.getCount())) 34 | .sorted(Comparator.comparing(WordCount::getWord)).toList(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsC.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsC extends WordStatisticImpl { 6 | @Override 7 | public void addWord(String word) { 8 | if (word == null) throw new IllegalArgumentException(); 9 | if (word.isBlank()) return; 10 | if (word.length() > 10) word = word.substring(0, 10); 11 | if (!statistics.containsKey(word.toLowerCase())) 12 | statistics.put(word, 0); 13 | 14 | var count = statistics.get(word); 15 | count++; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsCR.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | public class WordStatisticsCR implements WordStatistics { 12 | private final Map statistics = new HashMap<>(); 13 | 14 | @Override 15 | public void addWord(String word) { 16 | if (word == null) throw new IllegalArgumentException(); 17 | if (word.isEmpty()) return; 18 | if (word.length() > 10) 19 | word = word.substring(0, 10); 20 | String lowerWord = word.toLowerCase(); 21 | 22 | int count = statistics.getOrDefault(lowerWord, 0); 23 | statistics.put(lowerWord, 1+count); 24 | } 25 | 26 | @Override 27 | public List getStatistics() { 28 | return statistics.entrySet().stream().map(it -> new WordCount(it.getKey(), it.getValue())).sorted((left, right) -> { 29 | if (left.getCount() < right.getCount()) { 30 | return 1; 31 | } else if (left.getCount() > right.getCount()) { 32 | return -1; 33 | } else { 34 | return left.getWord().compareTo(right.getWord()); 35 | } 36 | }).toList(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsE.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsE extends WordStatisticImpl { 6 | @Override 7 | public void addWord(String word) { 8 | if (word == null) throw new IllegalArgumentException(); 9 | if (word.length() > 10) word = word.substring(0, 10); 10 | var lowerWord = word.toLowerCase(); 11 | 12 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsE2.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsE2 extends WordStatisticImpl { 6 | @Override 7 | public void addWord(String word) { 8 | if (word == null || word.isBlank()) return; 9 | if (word.length() > 10) word = word.substring(0, 10); 10 | 11 | var lowerWord = word.toLowerCase(); 12 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsE3.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsE3 extends WordStatisticImpl { 6 | 7 | @Override 8 | public void addWord(String word) { 9 | if (word == null) throw new IllegalArgumentException(); 10 | if (word.length() > 10) word = word.substring(0, 10); 11 | if (word.isBlank()) return; 12 | 13 | var lowerWord = word.toLowerCase(); 14 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsE4.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsE4 extends WordStatisticImpl { 6 | 7 | @Override 8 | public void addWord(String word) { 9 | if (word.length() == 0 || word.isBlank()) return; 10 | if (word.length() > 10) word = word.substring(0, 10); 11 | if (word.isBlank()) return; 12 | 13 | var lowerWord = word.toLowerCase(); 14 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsEN1.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | public class WordStatisticsEN1 implements WordStatistics { 12 | protected Map statistics = new HashMap<>(); 13 | 14 | @Override 15 | public void addWord(String word) { 16 | if (word == null) throw new IllegalArgumentException(); 17 | if (word.isBlank()) return; 18 | if (word.length() > 10) 19 | word = word.substring(0, 10); 20 | String lowerWord = word.toLowerCase(); 21 | 22 | int count = statistics.getOrDefault(lowerWord, 0); 23 | statistics.put(lowerWord, 1 + count); 24 | } 25 | 26 | @Override 27 | public List getStatistics() { 28 | var temp = statistics; 29 | statistics = new HashMap<>(); 30 | 31 | return temp.entrySet().stream().map(it -> new WordCount(it.getKey(), it.getValue())).sorted((left, right) -> { 32 | if (left.getCount() < right.getCount()) { 33 | return 1; 34 | } else if (left.getCount() > right.getCount()) { 35 | return -1; 36 | } else { 37 | return left.getWord().compareTo(right.getWord()); 38 | } 39 | }).toList(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsEN2.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatisticImpl; 5 | 6 | import java.util.List; 7 | 8 | public class WordStatisticsEN2 extends WordStatisticImpl { 9 | private List result; 10 | 11 | @Override 12 | public List getStatistics() { 13 | return result != null ? result : super.getStatistics(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsL2.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsL2 extends WordStatisticImpl { 6 | @Override 7 | public void addWord(String word) { 8 | if (word == null) throw new IllegalArgumentException(); 9 | if (word.isBlank()) return; 10 | var lowerWord = word.toLowerCase(); 11 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsL3.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsL3 extends WordStatisticImpl { 6 | @Override 7 | public void addWord(String word) { 8 | if (word == null) throw new IllegalArgumentException(); 9 | if (word.isBlank()) return; 10 | if (word.length() > 10) word = word.substring(0, 10); 11 | else if (word.length() > 5) word = word.substring(0, word.length() - 2); 12 | 13 | var lowerWord = word.toLowerCase(); 14 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsL4.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordStatisticImpl; 4 | 5 | public class WordStatisticsL4 extends WordStatisticImpl { 6 | @Override 7 | public void addWord(String word) { 8 | if (word == null) throw new IllegalArgumentException(); 9 | if (word.isBlank()) return; 10 | 11 | if (word.length() - 1 > 10) word = word.substring(0, 10); 12 | 13 | var lowerWord = word.toLowerCase(); 14 | statistics.put(lowerWord, 1 + statistics.getOrDefault(lowerWord, 0)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsQWE.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collector; 10 | import java.util.stream.Collectors; 11 | 12 | public class WordStatisticsQWE implements WordStatistics { 13 | protected final Map statistics = new HashMap<>(); 14 | 15 | @Override 16 | public void addWord(String word) { 17 | if (word == null) throw new IllegalArgumentException(); 18 | if (word.isBlank()) return; 19 | if (word.length() > 10) 20 | word = word.substring(0, 10); 21 | String lowerWord = toLower(word); 22 | 23 | int count = statistics.getOrDefault(lowerWord, 0); 24 | statistics.put(lowerWord, 1 + count); 25 | } 26 | 27 | @Override 28 | public List getStatistics() { 29 | return statistics.entrySet().stream().map(it -> new WordCount(it.getKey(), it.getValue())).sorted((left, right) -> { 30 | if (left.getCount() < right.getCount()) { 31 | return 1; 32 | } else if (left.getCount() > right.getCount()) { 33 | return -1; 34 | } else { 35 | return left.getWord().compareTo(right.getWord()); 36 | } 37 | }).toList(); 38 | } 39 | 40 | private char toLower(char c) { 41 | if ("QWERTYUIOPLJKHGFDSAZXCVBNM".contains("" + c)) 42 | return (char) (c - 'D' - 'd'); 43 | else if ("ЙЦУКЕНГШЩЗФЫВАПРОЛДЯЧСМИТЬ".contains("" + c)) 44 | return (char) (c - 'Я' + 'я'); 45 | return c; 46 | } 47 | 48 | private String toLower(String s) { 49 | return s.chars().mapToObj(c -> (char) c).map(this::toLower).collect( 50 | Collector.of( 51 | StringBuilder::new, 52 | StringBuilder::append, 53 | StringBuilder::append, 54 | StringBuilder::toString)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /java/src/main/java/ru/kontur/courses/donotopen/WordStatisticsSTA.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.donotopen; 2 | 3 | import ru.kontur.courses.WordCount; 4 | import ru.kontur.courses.WordStatistics; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | public class WordStatisticsSTA implements WordStatistics { 12 | private final static Map statistics = new HashMap<>(); 13 | 14 | public WordStatisticsSTA() { 15 | statistics.clear(); 16 | } 17 | 18 | @Override 19 | public void addWord(String word) { 20 | if (word == null) throw new IllegalArgumentException(); 21 | if (word.isBlank()) return; 22 | if (word.length() > 10) 23 | word = word.substring(0, 10); 24 | String lowerWord = word.toLowerCase(); 25 | 26 | int count = statistics.getOrDefault(lowerWord, 0); 27 | statistics.put(lowerWord, 1 + count); 28 | } 29 | 30 | @Override 31 | public List getStatistics() { 32 | return statistics.entrySet().stream().map(it -> new WordCount(it.getKey(), it.getValue())).sorted((left, right) -> { 33 | if (left.getCount() < right.getCount()) { 34 | return 1; 35 | } else if (left.getCount() > right.getCount()) { 36 | return -1; 37 | } else { 38 | return left.getWord().compareTo(right.getWord()); 39 | } 40 | }).toList(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/IncorrectImplementationTest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses; 2 | 3 | import org.junit.jupiter.api.DynamicTest; 4 | import org.junit.jupiter.api.TestFactory; 5 | import org.junit.platform.launcher.LauncherSession; 6 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; 7 | import org.junit.platform.launcher.core.LauncherFactory; 8 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener; 9 | import ru.kontur.courses.donotopen.*; 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.util.stream.Stream; 12 | 13 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 14 | 15 | public class IncorrectImplementationTest { 16 | @TestFactory 17 | Stream stream() { 18 | var request = LauncherDiscoveryRequestBuilder.request() 19 | .selectors(selectClass(WordStatisticsTest.class)).build(); 20 | 21 | var listener = new SummaryGeneratingListener(); 22 | 23 | return Stream.of( 24 | new WordStatistics01(), 25 | new WordStatistics02(), 26 | new WordStatistics03(), 27 | new WordStatistics04(), 28 | new WordStatistics123(), 29 | new WordStatistics998(), 30 | new WordStatistics999(), 31 | new WordStatisticsC(), 32 | new WordStatisticsCR(), 33 | new WordStatisticsE(), 34 | new WordStatisticsE2(), 35 | new WordStatisticsE3(), 36 | new WordStatisticsE4(), 37 | new WordStatisticsEN1(), 38 | new WordStatisticsEN2(), 39 | new WordStatisticsL2(), 40 | new WordStatisticsL3(), 41 | new WordStatisticsL4(), 42 | new WordStatisticsQWE(), 43 | new WordStatisticsSTA() 44 | ).map(it -> DynamicTest.dynamicTest(it.getClass().getSimpleName(), () -> { 45 | WordStatisticsTest.wordStatisticFactory = () -> { 46 | try { 47 | return it.getClass().getConstructor().newInstance(); 48 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | 49 | NoSuchMethodException e) { 50 | throw new RuntimeException(e); 51 | } 52 | }; 53 | 54 | try (LauncherSession session = LauncherFactory.openSession()) { 55 | var launcher = session.getLauncher(); 56 | 57 | launcher.registerTestExecutionListeners(listener); 58 | 59 | launcher.execute(request); 60 | var summary = listener.getSummary(); 61 | if (summary.getTestsFailedCount() == 0) { 62 | throw new RuntimeException("Некорректная имплементация прошла, не хватает тестов"); 63 | } 64 | } 65 | })); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/WordStatisticFactory.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses; 2 | 3 | public interface WordStatisticFactory { 4 | public WordStatistics create(); 5 | } -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/WordStatisticsTest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | 10 | public class WordStatisticsTest { 11 | /** 12 | * Подставляются разные имплементации при прогоне IncorrectImplementation, по умолчанию reference 13 | */ 14 | static WordStatisticFactory wordStatisticFactory = WordStatisticImpl::new; 15 | 16 | private WordStatistics wordStatistic; 17 | 18 | @BeforeEach 19 | public void setUp() { 20 | wordStatistic = wordStatisticFactory.create(); 21 | } 22 | 23 | @Test 24 | public void getStatisticsIsEmptyAfterCreation() { 25 | assertTrue(wordStatistic.getStatistics().isEmpty()); 26 | } 27 | 28 | @Test 29 | public void getStatisticsContainerItemAfterAddition() { 30 | wordStatistic.addWord("abc"); 31 | assertTrue(wordStatistic.getStatistics().contains(new WordCount("abc", 1))); 32 | } 33 | 34 | @Test 35 | public void getStatisticsContainsManyItemsAfterAdditionOfDifferentWords() { 36 | wordStatistic.addWord("abc"); 37 | wordStatistic.addWord("def"); 38 | assertEquals(2, wordStatistic.getStatistics().size()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/homework/NumberValidator.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.homework; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class NumberValidator { 6 | private final Pattern numberRegex; 7 | private final Boolean onlyPositive; 8 | private final int precision; 9 | private final int scale; 10 | 11 | public NumberValidator(int precision, int scale, Boolean onlyPositive) { 12 | this.precision = precision; 13 | this.scale = scale; 14 | this.onlyPositive = onlyPositive; 15 | if (precision <= 0) 16 | throw new IllegalArgumentException("precision must be a positive number"); 17 | if (scale < 0 || scale >= precision) 18 | throw new IllegalArgumentException("precision must be a non-negative number less or equal than precision"); 19 | numberRegex = Pattern.compile("^([+-]?)(\\d+)([.,](\\d+))?$"); 20 | } 21 | 22 | public Boolean isValidNumber(String value) { 23 | // Проверяем соответствие входного значения формату N(m,k), в соответствии с правилом, 24 | // описанным в Формате описи документов, направляемых в налоговый орган в электронном виде по телекоммуникационным каналам связи: 25 | // Формат числового значения указывается в виде N(m.к), где m – максимальное количество знаков в числе, включая знак (для отрицательного числа), 26 | // целую и дробную часть числа без разделяющей десятичной точки, k – максимальное число знаков дробной части числа. 27 | // Если число знаков дробной части числа равно 0 (т.е. число целое), то формат числового значения имеет вид N(m). 28 | 29 | if (value == null || value.isBlank()) 30 | return false; 31 | 32 | var matcher = numberRegex.matcher(value); 33 | var match = matcher.matches(); 34 | 35 | if (!match) 36 | return false; 37 | 38 | // Знак и целая часть 39 | var intPart = matcher.group(1).length() + matcher.group(2).length(); 40 | // Дробная часть 41 | var fracGroup = matcher.group(4); 42 | 43 | var fracPart = fracGroup == null ? 0 : fracGroup.length(); 44 | 45 | if (intPart + fracPart > precision || fracPart > scale) 46 | return false; 47 | 48 | return !onlyPositive || !matcher.group(1).equals("-"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/homework/NumberValidatorTest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.homework; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | public class NumberValidatorTest { 8 | 9 | @Test 10 | public void test() { 11 | assertThrows(IllegalArgumentException.class, () -> { 12 | new NumberValidator(-1, 2, true); 13 | }); 14 | assertDoesNotThrow(() -> { 15 | new NumberValidator(1, 0, true); 16 | }); 17 | assertThrows(IllegalArgumentException.class, () -> { 18 | new NumberValidator(-1, 2, false); 19 | }); 20 | assertDoesNotThrow(() -> { 21 | new NumberValidator(1, 0, true); 22 | }); 23 | 24 | assertTrue(new NumberValidator(17, 2, true).isValidNumber("0.0")); 25 | assertTrue(new NumberValidator(17, 2, true).isValidNumber("0")); 26 | assertTrue(new NumberValidator(17, 2, true).isValidNumber("0.0")); 27 | assertFalse(new NumberValidator(3, 2, true).isValidNumber("00.00")); 28 | assertFalse(new NumberValidator(3, 2, true).isValidNumber("-0.00")); 29 | assertTrue(new NumberValidator(17, 2, true).isValidNumber("0.0")); 30 | assertFalse(new NumberValidator(3, 2, true).isValidNumber("+0.00")); 31 | assertTrue(new NumberValidator(4, 2, true).isValidNumber("+1.23")); 32 | assertFalse(new NumberValidator(3, 2, true).isValidNumber("+1.23")); 33 | assertFalse(new NumberValidator(17, 2, true).isValidNumber("0.000")); 34 | assertFalse(new NumberValidator(3, 2, true).isValidNumber("-1.23")); 35 | assertFalse(new NumberValidator(3, 2, true).isValidNumber("a.sd")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/homework/ObjectComparisonTest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.homework; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | public class ObjectComparisonTest { 9 | 10 | @Test 11 | public void chechCurrentTasr() { 12 | var actualTsar = TsarRegistry.getCurrentTsar(); 13 | 14 | var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, 15 | new Person("Vasili III of Russia", 28, 170, 60, null)); 16 | 17 | // Перепишите код на использование Fluent assertions assertJ 18 | assertEquals(actualTsar.name, expectedTsar.name); 19 | assertEquals(actualTsar.age, expectedTsar.age); 20 | assertEquals(actualTsar.height, expectedTsar.height); 21 | assertEquals(actualTsar.weight, expectedTsar.weight); 22 | 23 | assertEquals(expectedTsar.parent.name, actualTsar.parent.name); 24 | assertEquals(expectedTsar.parent.age, actualTsar.parent.age); 25 | assertEquals(expectedTsar.parent.height, actualTsar.parent.height); 26 | assertEquals(expectedTsar.parent.parent, actualTsar.parent.parent); 27 | } 28 | 29 | @Test 30 | public void checkCurrentTsarWithCustomEquality() { 31 | var actualTsar = TsarRegistry.getCurrentTsar(); 32 | var expectedTsar = new Person("Ivan IV The Terrible", 54, 170, 70, 33 | new Person("Vasili III of Russia", 28, 170, 60, null)); 34 | 35 | // Какие недостатки у такого подхода? 36 | assertTrue(areEqual(actualTsar, expectedTsar)); 37 | } 38 | 39 | private boolean areEqual(Person actual, Person expected) { 40 | if (actual == expected) return true; 41 | if (actual == null || expected == null) return false; 42 | return 43 | actual.name.equals(expected.name) 44 | && actual.age == expected.age 45 | && actual.height == expected.height 46 | && actual.weight == expected.weight 47 | && areEqual(actual.parent, expected.parent); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/homework/Person.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.homework; 2 | 3 | public class Person { 4 | public static int idCounter = 0; 5 | public int age, height, weight; 6 | public String name; 7 | public Person parent; 8 | public int id; 9 | 10 | public Person(String name, int age, int height, int weight, Person parent) { 11 | id = idCounter++; 12 | this.name = name; 13 | this.age = age; 14 | this.height = height; 15 | this.weight = weight; 16 | this.parent = parent; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/homework/README.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание 2 | 3 | ## Сравнение объектов 4 | 5 | Изучите тест в классе ObjectComparisonTest. 6 | Затем изучите [документацию assertj](https://assertj.github.io/doc/). 7 | 8 | Перепишите тест с использованием наиболее подходящего метода AssertJ так чтобы: 9 | 10 | * тест продолжал работать, 11 | * его читаемость возросла, 12 | * он стал расширяем: добавление свойств в класс Person должно приводить к минимуму изменений в тестах. 13 | 14 | В комментариях поясните, чем ваше решение лучше решения в методе checkCurrentTsarWithCustomEquality. 15 | 16 | ## Рефакторинг тестов 17 | 18 | Изучите код теста в классе NumberValidatorTests. 19 | 20 | Перепишите тест так, чтобы 21 | 22 | * найти и удалить повторяющиеся проверки, 23 | * найти недостающие проверки, 24 | * при падении теста было без стек-трейса понятно на каких данных код не работает, 25 | * одна упавшая проверка не блокировала прохождение остальных проверок. 26 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/homework/TsarRegistry.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.homework; 2 | 3 | public class TsarRegistry { 4 | public static Person getCurrentTsar() { 5 | return new Person( 6 | "Ivan IV The Terrible", 54, 170, 70, 7 | new Person("Vasili III of Russia", 28, 170, 60, null)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/AAATest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | public class AAATest { 14 | @Test 15 | public void giveResultOfSameSizeOnEqualSizeArrays() { 16 | var arr = new int[]{1}; 17 | var arr2 = new int[]{2}; 18 | 19 | var result = zipJava9(arr, arr2); 20 | 21 | assertEquals(1, result.get(0).getKey()); 22 | assertEquals(2, result.get(0).getValue()); 23 | } 24 | 25 | @Test 26 | public void beEmptyWhenBothInputsAreEmpty() { 27 | var arr = new int[0]; 28 | var arr2 = new int[0]; 29 | 30 | var result = zipJava9(arr, arr2); 31 | 32 | assertTrue(result.isEmpty()); 33 | } 34 | 35 | @Test 36 | public void beEmptyWhenFirstIsEmpty() { 37 | var arr = new int[0]; 38 | var arr2 = new int[]{1, 2}; 39 | 40 | var result = zipJava9(arr, arr2); 41 | 42 | assertTrue(result.isEmpty()); 43 | } 44 | 45 | @Test 46 | public void beEmptyWhenSecondIsEmpty() { 47 | var arr = new int[]{1, 2}; 48 | var arr2 = new int[0]; 49 | 50 | var result = zipJava9(arr, arr2); 51 | 52 | assertTrue(result.isEmpty()); 53 | } 54 | 55 | @Test 56 | public void haveLengthOfSecondWhenFirstContainsMoreElements() { 57 | var arr = new int[]{1, 3, 5, 7}; 58 | var arr2 = new int[]{2, 4}; 59 | 60 | var result = zipJava9(arr, arr2); 61 | 62 | assertEquals(1, result.get(0).getKey()); 63 | assertEquals(2, result.get(0).getValue()); 64 | assertEquals(3, result.get(1).getKey()); 65 | assertEquals(4, result.get(1).getValue()); 66 | } 67 | 68 | @Test 69 | public void haveLengthOfFirstWhenSecondContainsMoreElements() { 70 | var arr1 = new int[]{1, 3}; 71 | var arr2 = new int[]{2, 4, 6, 8}; 72 | 73 | var result = zipJava9(arr1, arr2); 74 | 75 | assertEquals(1, result.get(0).getKey()); 76 | assertEquals(2, result.get(0).getValue()); 77 | assertEquals(3, result.get(1).getKey()); 78 | assertEquals(4, result.get(1).getValue()); 79 | } 80 | 81 | public static List> zipJava9(int[] as, int[] bs) { 82 | return IntStream.range(0, Math.min(as.length, bs.length)) 83 | .mapToObj(i -> Map.entry(as[i], bs[i])).toList(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/Parametrized.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.ValueSource; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 7 | 8 | public class Parametrized { 9 | 10 | @ParameterizedTest 11 | @ValueSource(strings = {"123", "1.1", "1.1e1", "1.1e-1", "-0.1"}) 12 | public void parseDouble(String input) { 13 | assertDoesNotThrow(() -> { 14 | Double.parseDouble(input); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/SpecificationTest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.EmptyStackException; 6 | import java.util.Stack; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | public class SpecificationTest { 11 | 12 | @Test 13 | public void constructorCreateEmptyStack() { 14 | var stack = new Stack<>(); 15 | 16 | assertEquals(0, stack.size()); 17 | } 18 | 19 | @Test 20 | public void toArrayReturnsItemsInNotPopOrder() { 21 | var stack = new Stack(); 22 | stack.add(1); 23 | stack.add(2); 24 | stack.add(3); 25 | 26 | assertArrayEquals(new Integer[]{1, 2, 3}, stack.toArray(Integer[]::new)); 27 | } 28 | 29 | @Test 30 | public void pushAddsItemToStackTop() { 31 | var stack = new Stack(); 32 | stack.add(1); 33 | stack.add(2); 34 | stack.add(3); 35 | 36 | stack.push(42); 37 | 38 | assertArrayEquals(new Integer[]{1, 2, 3, 42}, stack.toArray(Integer[]::new)); 39 | } 40 | 41 | @Test 42 | public void popOnEmptyStackFails() { 43 | var stack = new Stack(); 44 | 45 | assertThrows(EmptyStackException.class, () -> { 46 | stack.pop(); 47 | }); 48 | } 49 | 50 | @Test 51 | public void popReturnLastPushedItem() { 52 | var stack = new Stack(); 53 | stack.add(1); 54 | stack.add(2); 55 | stack.add(3); 56 | 57 | stack.push(42); 58 | 59 | assertEquals(42, stack.pop()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/builder/TestBuilderTest.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples.builder; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | public class TestBuilderTest { 6 | 7 | @Test 8 | public void worksWithObjectMother() { 9 | var user = TestUsers.asRegularUser(); 10 | var adminUser = TestUsers.anAdmin(); 11 | // Если в тестах нужно много комбинаций параметров, будет комбинаторный взрыв. 12 | } 13 | 14 | @Test 15 | public void worksWithBuilder() { 16 | var user = TestUserBuilder.aUser().build(); 17 | var adminUser = TestUserBuilder.aUser().inAdminRole().build(); 18 | // Такой код не ломается, при смене сигнатуры конструктора User. 19 | // Это важно, если таких тестов много. 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/builder/TestUserBuilder.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples.builder; 2 | 3 | public class TestUserBuilder { 4 | public final String DEFAULT_NAME = ""; 5 | public final String DEFAULT_ROLE = ""; 6 | public final String DEFAULT_PASSWORD = ""; 7 | private String name = DEFAULT_NAME; 8 | private String password = DEFAULT_PASSWORD; 9 | private String role = DEFAULT_ROLE; 10 | private String login; 11 | 12 | private TestUserBuilder() { 13 | } 14 | 15 | public static TestUserBuilder aUser() { 16 | return new TestUserBuilder(); 17 | } 18 | 19 | public TestUserBuilder withName(String newName) { 20 | name = newName; 21 | return this; 22 | } 23 | 24 | public TestUserBuilder withLogin(String newLogin) { 25 | login = newLogin; 26 | return this; 27 | } 28 | 29 | public TestUserBuilder withPassword(String newPassword) { 30 | password = newPassword; 31 | return this; 32 | } 33 | 34 | public TestUserBuilder withNoPassword() { 35 | password = null; 36 | return this; 37 | } 38 | 39 | public TestUserBuilder inUserRole() { 40 | return inRole("ROLE_USER"); 41 | } 42 | 43 | public TestUserBuilder inAdminRole() { 44 | return inRole("ROLE_ADMIN"); 45 | } 46 | 47 | public TestUserBuilder inRole(String newRole) { 48 | this.role = newRole; 49 | return this; 50 | } 51 | 52 | public TestUserBuilder but() { 53 | return aUser().inRole(role).withName(name).withPassword(password).withLogin(login); 54 | } 55 | 56 | public User build() { 57 | return new User(name, login, password, role); 58 | } 59 | 60 | public User aRegularUser() { 61 | return aUser().build(); 62 | } 63 | 64 | public User anAdmin() { 65 | return aUser() 66 | .withName("Neo") 67 | .withLogin("neo") 68 | .inAdminRole().build(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/builder/TestUsers.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples.builder; 2 | 3 | public class TestUsers { 4 | 5 | public static User asRegularUser() { 6 | return new User("Triniti", "tri", "asdasd", "ROLE_USER"); 7 | } 8 | 9 | public static User anAdmin() { 10 | return new User("Agent Smith", "smith", "qweqwe", "ROLE_ADMIN"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java/src/test/java/ru/kontur/courses/samples/builder/User.java: -------------------------------------------------------------------------------- 1 | package ru.kontur.courses.samples.builder; 2 | 3 | public class User { 4 | private String login; 5 | private String name; 6 | private String password; 7 | private String role; 8 | 9 | public User(String login, String name, String password, String role) { 10 | this.login = login; 11 | this.name = name; 12 | this.password = password; 13 | this.role = role; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ], 5 | "plugins": [ 6 | "transform-object-rest-spread" 7 | ] 8 | } -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | .idea/tasks.xml 3 | -------------------------------------------------------------------------------- /js/.idea/js.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /js/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /js/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /js/.idea/runConfigurations/Samples_AAA.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | 8 | tdd 9 | --require babel-register 10 | SUITE 11 | $PROJECT_DIR$/src/samples/AAA/zip_should.test.js 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /js/.idea/runConfigurations/Samples_Antipatterns.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | 8 | bdd 9 | --require babel-register 10 | DIRECTORY 11 | $PROJECT_DIR$/src/samples/antipatterns 12 | true 13 | 14 | 15 | -------------------------------------------------------------------------------- /js/.idea/runConfigurations/Samples_Parametrized.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | 8 | bdd 9 | --require babel-register 10 | SUITE 11 | $PROJECT_DIR$/src/samples/parametrized/number_should.test.js 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /js/.idea/runConfigurations/Samples_Specifications.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | 8 | bdd 9 | --require babel-register 10 | SUITE 11 | $PROJECT_DIR$/src/samples/specifications/stack.spec.js 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /js/.idea/runConfigurations/Samples_TestDataBuilder_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | 8 | bdd 9 | --require babel-register 10 | SUITE 11 | $PROJECT_DIR$/src/samples/testDataBuilder/sampleTests.js 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /js/.idea/runConfigurations/Words_Statistics_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | project 4 | 5 | $PROJECT_DIR$ 6 | true 7 | 8 | bdd 9 | --require babel-register 10 | SUITE 11 | $PROJECT_DIR$/src/challenge/wordsStatistics.test.js 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /js/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /js/.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /js/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/.solved": true 9 | } 10 | } -------------------------------------------------------------------------------- /js/Mocha.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 19 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testing", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --require babel-register ./src/samples/antipatterns/*.test.js ./src/samples/parametrized/*.test.js ./src/samples/specifications/*.spec.js ./src/samples/testDataBuilder/*.test.js", 8 | "test-tdd": "mocha --ui tdd --require babel-register ./src/samples/AAA/*.test.js", 9 | "challenge": "babel-node ./src/challenge/index.js", 10 | "challenge-test": "mocha --require babel-register ./src/challenge/*.test.js" 11 | }, 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "babel-cli": "^6.26.0", 15 | "babel-core": "^6.26.0", 16 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 17 | "babel-preset-env": "^1.6.1", 18 | "chai": "^4.1.2", 19 | "firebase": "^4.6.0", 20 | "mocha": "^4.0.1" 21 | }, 22 | "author": "", 23 | "license": "ISC" 24 | } 25 | -------------------------------------------------------------------------------- /js/src/challenge/incorrectImplementations/wordsStatistics.incorrect.test.js: -------------------------------------------------------------------------------- 1 | import * as incorrectImplementations from "./doNotOpen"; 2 | 3 | import wordsStatisticsTests from "../wordsStatistics.test"; 4 | 5 | const implementations = [ 6 | incorrectImplementations.WordsStatisticsL2, 7 | incorrectImplementations.WordsStatisticsL3, 8 | incorrectImplementations.WordsStatisticsL4, 9 | incorrectImplementations.WordsStatisticsC, 10 | incorrectImplementations.WordsStatisticsE, 11 | incorrectImplementations.WordsStatisticsE2, 12 | incorrectImplementations.WordsStatisticsE3, 13 | incorrectImplementations.WordsStatisticsE4, 14 | incorrectImplementations.WordsStatisticsO1, 15 | incorrectImplementations.WordsStatisticsO2, 16 | incorrectImplementations.WordsStatisticsO3, 17 | incorrectImplementations.WordsStatisticsO4, 18 | incorrectImplementations.WordsStatisticsCR, 19 | incorrectImplementations.WordsStatisticsSTA, 20 | incorrectImplementations.WordsStatistics123, 21 | incorrectImplementations.WordsStatisticsQWE, 22 | incorrectImplementations.WordsStatistics998, 23 | incorrectImplementations.WordsStatistics999, 24 | incorrectImplementations.WordsStatisticsEN1, 25 | incorrectImplementations.WordsStatisticsEN2, 26 | ]; 27 | 28 | for (const implementation of implementations) { 29 | describe(implementation.name, function (){ 30 | wordsStatisticsTests(() => new implementation()) 31 | }); 32 | } -------------------------------------------------------------------------------- /js/src/challenge/index.js: -------------------------------------------------------------------------------- 1 | import Challenger from "./infrastructure/challenger"; 2 | 3 | (async function() { 4 | try { 5 | await new Challenger().run(); 6 | } catch(error) { 7 | console.error(error); 8 | } 9 | })(); -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/challenger.js: -------------------------------------------------------------------------------- 1 | import Mocha from "mocha"; 2 | import path from "path"; 3 | 4 | import * as reporters from "./reporters"; 5 | import ReportDataCollector from "./reporters/failedTestsCollector"; 6 | import FirebaseTestResultsPoster from "./resultPoster"; 7 | 8 | import * as stringHelpers from "../../lib/stringHelpers"; 9 | import ConsoleWriter from "./consoleWriter"; 10 | 11 | import { AUTHORS } from "../yourName"; 12 | 13 | export default class Challenger { 14 | async run() { 15 | if (!stringHelpers.isDefinedString(AUTHORS) || stringHelpers.isWhitespace(AUTHORS)){ 16 | ConsoleWriter.writeError("Enter your surnames at yourName.js in AUTHORS constant"); 17 | return; 18 | } 19 | 20 | ConsoleWriter.write("Checking if all tests pass with correct implementation..."); 21 | const failedCount = await this.testCorrectImplementation(); 22 | if (failedCount > 0) { 23 | return; 24 | } 25 | ConsoleWriter.write("Checking if tests fail with incorrect implementations..."); 26 | const incorrectImplementationTestResult = await this.testIncorrectImplementation(); 27 | 28 | if (stringHelpers.isDefinedString(AUTHORS) && !stringHelpers.isWhitespace(AUTHORS)) { 29 | await this.postResults(incorrectImplementationTestResult); 30 | } 31 | } 32 | 33 | testCorrectImplementation() { 34 | return new Promise((resolve) => { 35 | const reporter = reporters.CorrectImplementationReporter; 36 | const testFileName = "./src/challenge/wordsStatistics.test.js"; 37 | 38 | this.createTestsRunner(reporter, testFileName) 39 | .run((failedCount) => resolve(failedCount)); 40 | }); 41 | } 42 | 43 | testIncorrectImplementation() { 44 | return new Promise((resolve) => { 45 | const collector = new ReportDataCollector(); 46 | 47 | const reporter = reporters.IncorrectImplementationsReporter; 48 | const testFileName = "./src/challenge/incorrectImplementations/wordsStatistics.incorrect.test.js"; 49 | const reporterOptions = { custom: ({ collector: collector }) }; 50 | 51 | this.createTestsRunner(reporter, testFileName, reporterOptions) 52 | .run(() => resolve(collector.getStatistics())); 53 | }); 54 | } 55 | 56 | createTestsRunner(reporter, testFileName, reporterOptions) { 57 | const fileName = path.resolve(testFileName); 58 | 59 | return new Mocha() 60 | .ui("bdd") 61 | .reporter(reporter, reporterOptions) 62 | .addFile(fileName); 63 | } 64 | 65 | async postResults(testResult) { 66 | const firebasePoster = new FirebaseTestResultsPoster(); 67 | const values = testResult.reduce((r, v) => ({ 68 | ...r, 69 | [v.implementationName]: v.failedTests.length 70 | }), {}); 71 | 72 | try { 73 | await firebasePoster.writeAsync(AUTHORS, { data: values }); 74 | } catch (error) { 75 | console.error(error) 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/consoleWriter.js: -------------------------------------------------------------------------------- 1 | const terminalCodes = { 2 | green: '\x1b[32m', 3 | red: '\x1b[31m', 4 | reset: '\x1b[0m' 5 | }; 6 | 7 | class ConsoleWriter { 8 | static writeSuccess(message) { 9 | console.log(`${terminalCodes.green}${message}${terminalCodes.reset}`); 10 | } 11 | 12 | static writeError(message) { 13 | console.log(`${terminalCodes.red}${message}${terminalCodes.reset}`); 14 | } 15 | 16 | static write(message) { 17 | console.log(message); 18 | } 19 | } 20 | 21 | export default ConsoleWriter; -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/reporters/correctImplementationReporter.js: -------------------------------------------------------------------------------- 1 | import mocha from "mocha"; 2 | 3 | import ConsoleWriter from "../consoleWriter"; 4 | 5 | export default function CorrectImplementationReporter(runner) { 6 | mocha.reporters.Base.call(this, runner); 7 | 8 | const failedTests = []; 9 | 10 | runner.on('fail', function(test, err){ 11 | failedTests.push({name: test.titlePath().slice(-1), error: err}); 12 | }); 13 | 14 | runner.on('end', function() { 15 | if (failedTests.length > 0) { 16 | const failedTestNames = failedTests.map((t) => `"${t.name}"`).join(", "); 17 | ConsoleWriter.writeError(`Incorrect tests detected: ${failedTestNames}`); 18 | } else { 19 | ConsoleWriter.writeSuccess("Tests are OK!"); 20 | } 21 | }); 22 | } -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/reporters/failedTestsCollector.js: -------------------------------------------------------------------------------- 1 | export default class FailedTestsCollector { 2 | constructor() { 3 | this.statistics = new Map(); 4 | } 5 | 6 | append(implementationName, failedTests) { 7 | this.statistics.set(implementationName, failedTests); 8 | } 9 | 10 | getStatistics() { 11 | return Array.from(this.statistics) 12 | .map((keyValue) => ({ 13 | failedTests: keyValue[1], 14 | implementationName: keyValue[0] 15 | })) 16 | .sort((a, b) => { 17 | const compare = a.implementationName.localeCompare(b.implementationName); 18 | return compare !== 0 ? compare : a.failedTests - b.failedTests; 19 | }); 20 | } 21 | } -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/reporters/incorrectImplementationsReporter.js: -------------------------------------------------------------------------------- 1 | import mocha from "mocha"; 2 | 3 | import ConsoleWriter from "../consoleWriter"; 4 | 5 | export default function IncorrectImplementationsReporter(runner, options) { 6 | mocha.reporters.Base.call(this, runner); 7 | 8 | const testSuitesResults = []; 9 | let failedTests = []; 10 | 11 | const {collector} = options.reporterOptions.custom; 12 | 13 | runner.on('suite', function (suite) { 14 | if (isImplementationSuite(suite)) { 15 | failedTests = []; 16 | } 17 | }); 18 | 19 | runner.on('fail', function (test, error) { 20 | failedTests.push({ 21 | name: test.titlePath().slice(1).join(" "), 22 | error: error 23 | }); 24 | }); 25 | 26 | runner.on('suite end', function (suite) { 27 | if (isImplementationSuite(suite)) { 28 | const {title: implementationName} = suite; 29 | 30 | const failedTestNames = failedTests.map((t) => `[${t.name}]`); 31 | const failedTestNamesString = failedTestNames.join(", "); 32 | testSuitesResults.push({ 33 | failed: failedTests.length > 0, 34 | failedTestNames: failedTestNamesString, 35 | implementationName: implementationName 36 | }); 37 | 38 | collector.append(implementationName, failedTestNames); 39 | } 40 | }); 41 | 42 | runner.on('end', function () { 43 | testSuitesResults.forEach((r) => { 44 | if (r.failed === true) { 45 | ConsoleWriter.writeSuccess(`${r.implementationName}\tfails on: ${r.failedTestNames}`); 46 | } else { 47 | ConsoleWriter.writeError(`${r.implementationName}\twrite tests to kill it`); 48 | } 49 | }) 50 | }) 51 | } 52 | 53 | function isImplementationSuite(suite) { 54 | return suite.parent && suite.parent.root === true; 55 | } -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/reporters/index.js: -------------------------------------------------------------------------------- 1 | export { default as IncorrectImplementationsReporter } from "./incorrectImplementationsReporter"; 2 | export { default as CorrectImplementationReporter } from "./correctImplementationReporter"; -------------------------------------------------------------------------------- /js/src/challenge/infrastructure/resultPoster.js: -------------------------------------------------------------------------------- 1 | import * as firebase from "firebase"; 2 | 3 | export default class ResultPoster { 4 | constructor() { 5 | const config = { 6 | databaseURL: "https://testing-challenge.firebaseio.com/" 7 | }; 8 | 9 | firebase.initializeApp(config); 10 | } 11 | 12 | async writeAsync(author, data) { 13 | const safeAuthor = this.toUriSafeString(author); 14 | const path = `word-statistics/${this.buildDateKey()}/${safeAuthor}` 15 | const postData = { 16 | implementations: data.data, 17 | time: new Date().toISOString(), 18 | lang: 'js', 19 | }; 20 | 21 | const result = await firebase.database().ref(path).set( 22 | postData, 23 | err => { if (err) console.log("Error while submitting " + err); } 24 | ); 25 | firebase.database().goOffline(); 26 | return result; 27 | } 28 | 29 | buildDateKey() { 30 | const now = new Date(); 31 | const day = ("0" + now.getDate()).slice(-2); 32 | const month = ("0" + (now.getMonth() + 1)).slice(-2); 33 | const year = now.getFullYear(); 34 | return year + month + day; 35 | } 36 | 37 | toUriSafeString(word) { 38 | return word.replace(/[.$#\[\]\/\u0000-\u0020 ]/g, "_"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /js/src/challenge/wordsStatistics.js: -------------------------------------------------------------------------------- 1 | import * as stringHelpers from "../lib/stringHelpers"; 2 | import ArgumentNullError from "../lib/argumentNullError"; 3 | 4 | /** 5 | * Частотный словарь добавленных слов. 6 | * Слова сравниваются без учета регистра символов. 7 | * Порядок — по убыванию частоты слова. 8 | * При одинаковой частоте — в лексикографическом порядке. 9 | */ 10 | export default class WordsStatistics { 11 | constructor() { 12 | this.statistics = new Map(); 13 | } 14 | 15 | addWord(word) { 16 | if (!stringHelpers.isDefinedString(word)) { 17 | throw new ArgumentNullError("word should be a defined string"); 18 | } 19 | if (stringHelpers.isWhitespace(word)) { 20 | return; 21 | } 22 | if (word.length > 10) { 23 | word = word.substr(0, 10); 24 | } 25 | this.statistics.set(word.toLowerCase(), 1 + (this.statistics.get(word.toLowerCase()) || 0)); 26 | } 27 | 28 | getStatistics() { 29 | return Array 30 | .from(this.statistics) 31 | .map((keyValue) => ({ 32 | word: keyValue[0], 33 | count: keyValue[1] 34 | })) 35 | .sort((a, b) => a.count !== b.count 36 | ? b.count - a.count 37 | : a.word.localeCompare(b.word)); 38 | } 39 | } -------------------------------------------------------------------------------- /js/src/challenge/wordsStatistics.test.js: -------------------------------------------------------------------------------- 1 | import chai, {expect} from "chai"; 2 | chai.should(); 3 | 4 | import WordsStatistics from "./wordsStatistics"; 5 | import ArgumentNullError from "../lib/argumentNullError"; 6 | 7 | describe("words statistics", function () { 8 | wordStatisticsTests(() => new WordsStatistics()); 9 | }); 10 | 11 | export default function wordStatisticsTests(createWordStatistics) { 12 | let wordsStatistics; 13 | 14 | beforeEach(() => { 15 | wordsStatistics = createWordStatistics(); 16 | }); 17 | 18 | describe("getStatistics", () => { 19 | it("is empty after creation", () => { 20 | wordsStatistics.getStatistics().should.be.empty; 21 | }); 22 | 23 | it("contains item after addition", () => { 24 | const word = "abc"; 25 | wordsStatistics.addWord(word); 26 | wordsStatistics 27 | .getStatistics() 28 | .should.be.eql([{word: word, count: 1}]) 29 | }); 30 | 31 | it("contains many items after addition of different words", () => { 32 | wordsStatistics.addWord("abc"); 33 | wordsStatistics.addWord("def"); 34 | wordsStatistics 35 | .getStatistics() 36 | .should.have.lengthOf(2); 37 | }); 38 | }); 39 | 40 | //Документация по BDD стилю проверок Chai Assertion Library (http://chaijs.com/api/bdd/) 41 | } 42 | -------------------------------------------------------------------------------- /js/src/challenge/yourName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ваши фамилии через пробел. Например, "Egorov Shagalina" 3 | * @const 4 | * @type {string} 5 | */ 6 | export const AUTHORS = ""; 7 | -------------------------------------------------------------------------------- /js/src/lib/argumentNullError.js: -------------------------------------------------------------------------------- 1 | export default function ArgumentNullError(message) { 2 | this.name = 'ArgumentNullError'; 3 | this.message = message || ""; 4 | this.stack = (new Error()).stack; 5 | } 6 | ArgumentNullError.prototype = Object.create(Error.prototype); 7 | ArgumentNullError.prototype.constructor = ArgumentNullError; 8 | -------------------------------------------------------------------------------- /js/src/lib/stack.js: -------------------------------------------------------------------------------- 1 | export default class Stack { 2 | constructor(array) { 3 | this.array = []; 4 | if (array) { 5 | for (let i = 0; i < array.length; i++) { 6 | this.array.push(array[i]); 7 | } 8 | } 9 | } 10 | 11 | push(item) { 12 | this.array.push(item); 13 | } 14 | 15 | pop() { 16 | if (this.array.length > 0) { 17 | return this.array.pop(); 18 | } 19 | throw "Stack is empty"; 20 | } 21 | 22 | peek() { 23 | if (this.array.length > 0) { 24 | return this.array[this.array.length - 1]; 25 | } 26 | throw "Stack is empty"; 27 | } 28 | 29 | any() { 30 | return this.array.length > 0; 31 | } 32 | 33 | count() { 34 | return this.array.length; 35 | } 36 | 37 | toArray() { 38 | const result = []; 39 | for (let i = 0; i < this.array.length; i++) 40 | result[i] = this.array[this.array.length - i - 1]; 41 | return result; 42 | } 43 | } -------------------------------------------------------------------------------- /js/src/lib/stringHelpers.js: -------------------------------------------------------------------------------- 1 | export function isDefinedString(word) { 2 | return word !== undefined && word !== null && typeof word === "string"; 3 | } 4 | 5 | export function isWhitespace(word) { 6 | return word.match(/^\s*$/) !== null; 7 | } 8 | 9 | export function isEmpty(word) { 10 | return word === ""; 11 | } 12 | 13 | // http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ 14 | export function calculateHash(word) { 15 | let hash = 0; 16 | if (word.length === 0) { 17 | return hash; 18 | } 19 | for (let i = 0; i < word.length; i++) { 20 | let chr = word.charCodeAt(i); 21 | hash = ((hash << 5) - hash) + chr; 22 | hash |= 0; // Convert to 32bit integer 23 | } 24 | return hash; 25 | } 26 | -------------------------------------------------------------------------------- /js/src/lib/zip.js: -------------------------------------------------------------------------------- 1 | Array.prototype.zip = function (secondArray, resultSelector) { 2 | const resultArray = []; 3 | for (let i = 0; i < this.length && i < secondArray.length; i++) { 4 | resultArray.push(resultSelector(this[i], secondArray[i])); 5 | } 6 | return resultArray; 7 | } 8 | -------------------------------------------------------------------------------- /js/src/samples/AAA/zip_should.test.js: -------------------------------------------------------------------------------- 1 | import "../../lib/zip"; 2 | import {assert} from "chai"; 3 | 4 | suite("zip should", function() { 5 | test("give result of same size on equal size arrays", function() { 6 | const arr1 = [1]; 7 | const arr2 = [2]; 8 | 9 | const result = arr1.zip(arr2, (a, b) => ({a, b})); 10 | 11 | assert.deepEqual(result, [{a: 1, b: 2}]); 12 | }); 13 | 14 | test("be empty when both inputs are empty", function() { 15 | const arr1 = []; 16 | const arr2 = []; 17 | 18 | const result = arr1.zip(arr2, (a, b) => ({a, b})); 19 | 20 | assert.isEmpty(result); 21 | }); 22 | 23 | test("be empty when first is empty", function() { 24 | const arr1 = []; 25 | const arr2 = [1, 2]; 26 | 27 | const result = arr1.zip(arr2, (a, b) => ({a, b})); 28 | 29 | assert.isEmpty(result); 30 | }); 31 | 32 | test("be empty when second is empty", function() { 33 | const arr1 = [1, 2]; 34 | const arr2 = []; 35 | 36 | const result = arr1.zip(arr2, (a, b) => ({a, b})); 37 | 38 | assert.isEmpty(result); 39 | }); 40 | 41 | test("have length of second when first contains more elements", function() { 42 | const arr1 = [1, 3, 5, 7]; 43 | const arr2 = [2, 4]; 44 | 45 | const result = arr1.zip(arr2, (a, b) => ({a, b})); 46 | 47 | assert.deepEqual(result, [{a: 1, b: 2}, {a: 3, b: 4}]); 48 | }); 49 | 50 | test("have length of first when second contains more elements", function() { 51 | const arr1 = [1, 3]; 52 | const arr2 = [2, 4, 6, 8]; 53 | 54 | const result = arr1.zip(arr2, (a, b) => ({a, b})); 55 | 56 | assert.deepEqual(result, [{a: 1, b: 2}, {a: 3, b: 4}]); 57 | }); 58 | }); -------------------------------------------------------------------------------- /js/src/samples/antipatterns/stack1.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import fs from "fs"; 3 | import Stack from '../../lib/stack.js' 4 | 5 | describe("Stack1", () => { 6 | it("test", () => { 7 | const lines = fs.readFileSync("C:\\work\\edu\\testing-course\\Patterns\\data.txt") 8 | .split(" ") 9 | .map(line => ({ command: line[0], value: line[1] })); 10 | 11 | const stack = new Stack(); 12 | for (const line of lines) { 13 | if (line.command === "push") { 14 | stack.push(line.value); 15 | } else { 16 | assert.equal(stack.pop(), line.value); 17 | } 18 | } 19 | }); 20 | 21 | //#region Почему это плохо? 22 | 23 | /* 24 | ## Антипаттерн Local Hero 25 | 26 | Тест не будет работать на машине другого человека или на Build-сервере. 27 | Да и у того же самого человека после переустановки ОС / повторного Clone репозитория / ... 28 | 29 | ## Решение 30 | 31 | Тест не должен зависеть от особенностей локальной среды. 32 | Если нужна работа с файлами, делайте это по относительным путям. 33 | 34 | // path.resolve превращает относительный путь в абсолютный 35 | const lines = fs.readFileSync(path.resolve("data.txt")); 36 | */ 37 | 38 | //#endregion 39 | }); -------------------------------------------------------------------------------- /js/src/samples/antipatterns/stack2.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import Stack from '../../lib/stack.js' 3 | 4 | describe("Stack2", () => { 5 | it("test", () => { 6 | const stack = new Stack(); 7 | stack.push(10); 8 | stack.push(20); 9 | stack.push(30); 10 | while (stack.any()) { 11 | console.log(stack.pop()); 12 | } 13 | }) 14 | 15 | //#region Почему это плохо? 16 | 17 | /* 18 | ## Антипаттерн Loudmouth 19 | 20 | Тест не является автоматическим. Если он сломается, никто этого не заметит. 21 | 22 | ## Мораль 23 | 24 | Вместо вывода на консоль, используйте Assert-ы. 25 | */ 26 | 27 | //#endregion 28 | }); -------------------------------------------------------------------------------- /js/src/samples/antipatterns/stack3.test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import Stack from '../../lib/stack.js' 3 | 4 | describe("Stack3", () => { 5 | it("test", () => { 6 | const stack = new Stack(); 7 | assert.isFalse(stack.any()); 8 | stack.push(1); 9 | stack.pop(); 10 | assert.isFalse(stack.any()); 11 | 12 | stack.push(1); 13 | stack.push(2); 14 | stack.push(3); 15 | assert.equal(stack.count(), 3); 16 | 17 | stack.pop(); 18 | stack.pop(); 19 | stack.pop(); 20 | assert.isFalse(stack.any()); 21 | 22 | for (let i = 0; i < 1000; i++) { 23 | stack.push(i); 24 | } 25 | for (let i = 1000; i > 0; i--) { 26 | assert.equal(stack.pop(), i - 1); 27 | } 28 | }); 29 | 30 | //#region Почему это плохо? 31 | 32 | /* 33 | ## Антипаттерн Freeride 34 | 35 | 1. Непонятна область его ответственности. Складывается впечатление, что он тестирует все, однако он это делает плохо. 36 | Он дает ложное чувство, что все протестировано. Хотя, например, этот тест не проверяет много важных случаев. 37 | 38 | 2. Таким тестам как-правило невозможно придумать внятное название. 39 | 40 | 3. Если что-то упадет в середине теста, будет сложно разобраться что именно пошло не так и сложно отлаживать — нужно жонглировать точками останова. 41 | 42 | 4. Такой тест не работает как документация. По этому сценарию непросто восстановить требования к тестируемому объекту. 43 | 44 | ## Мораль 45 | 46 | Каждый тест должен тестировать одно конкретное требование. Это требование должно отражаться в названии теста. 47 | Если вы не можете придумать название теста, у вас Loudmouth! 48 | */ 49 | 50 | //#endregion 51 | }); -------------------------------------------------------------------------------- /js/src/samples/antipatterns/stack4.test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import Stack from '../../lib/stack.js' 3 | 4 | describe("Stack4", () => { 5 | it("test", () => { 6 | const stack = new Stack([1, 2, 3, 4, 5]); 7 | const result = stack.pop(); 8 | assert.equal(result, 5); 9 | assert.isTrue(stack.any()); 10 | assert.equal(stack.count(), 4); 11 | assert.equal(stack.peek(), 4); 12 | assert.deepEqual(stack.toArray(), [4, 3, 2, 1]); 13 | }) 14 | 15 | //#region Почему это плохо? 16 | 17 | /* 18 | ## Антипаттерн Overspecification 19 | 20 | 1. Непонятна область ответственности. Сложно придумать название. Не так плохо, как FreeRide, но плохо. 21 | 22 | 2. Изменение API роняет сразу много подобных тестов, создавая много рутинной работы по их починке. 23 | 24 | 3. Если все тесты будут такими, то при появлении бага, падают они большой компанией. 25 | 26 | 27 | ## Мораль 28 | 29 | Сфокусируйтесь на проверке одного конкретного требования в каждом тесте. 30 | Не старайтесь проверить "за одно" какое-то требование сразу во всех тестах — это может выйти боком. 31 | 32 | Признак возможной проблемы — более одного Assert на метод. 33 | */ 34 | 35 | //#endregion 36 | }); -------------------------------------------------------------------------------- /js/src/samples/parametrized/number_should.test.js: -------------------------------------------------------------------------------- 1 | import {assert} from "chai"; 2 | 3 | describe("Number", () => { 4 | const tests = [ 5 | {a: 12, b: 3, expected: 4}, 6 | {a: 12, b: 2, expected: 6}, 7 | {a: 12, b: 4, expected: 3} 8 | ]; 9 | 10 | tests.forEach(test => { 11 | it(`should divide (${test.a}, ${test.b})`, () => { 12 | assert.equal(test.a / test.b, test.expected); 13 | }); 14 | }); 15 | 16 | [ 17 | {input: "123", expectedResult: 123, testName: "integer"}, 18 | {input: "1.1", expectedResult: 1.1, testName: "faction"}, 19 | {input: "1.1e1", expectedResult: 1.1e1, testName: "scientific with positive exp"}, 20 | {input: "1.1e-1", expectedResult: 1.1e-1, testName: "scientific with negative exp"}, 21 | {input: "-0.1", expectedResult: -0.1, testName: "negative fraction"} 22 | ].forEach(({input, expectedResult, testName}) => { 23 | it("should parse " + testName, () => { 24 | assert.equal(Number.parseFloat(input), expectedResult); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /js/src/samples/specifications/stack.spec.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import Stack from '../../lib/stack.js' 3 | 4 | describe("Stack", () => { 5 | describe("constructor", () => { 6 | it("creates empty stack", () => { 7 | const stack = new Stack(); 8 | 9 | assert.equal(stack.count(), 0) 10 | }); 11 | 12 | it("pushes items to empty stack", () => { 13 | const stack = new Stack([1, 2, 3]); 14 | 15 | assert.equal(stack.count(), 3); 16 | assert.equal(stack.pop(), 3); 17 | assert.equal(stack.pop(), 2); 18 | assert.equal(stack.pop(), 1); 19 | assert.equal(stack.count(), 0); 20 | }); 21 | }); 22 | 23 | describe("toArray", () => { 24 | it("returns items in pop order", () => { 25 | const stack = new Stack([1, 2, 3]); 26 | 27 | assert.deepEqual(stack.toArray(), [3, 2, 1]); 28 | }); 29 | }); 30 | 31 | describe("push", () => { 32 | it("adds item to stack top", () => { 33 | const stack = new Stack([1, 2, 3]); 34 | 35 | stack.push(42); 36 | 37 | assert.deepEqual(stack.toArray(), [42, 3, 2, 1]); 38 | }); 39 | }); 40 | 41 | describe("pop", () => { 42 | it("fails on empty stack", () => { 43 | const stack = new Stack(); 44 | 45 | assert.throws(() => { stack.pop(); }, "Stack is empty") 46 | }); 47 | 48 | it("returns last pushed item", () => { 49 | const stack = new Stack([1, 2, 3]); 50 | 51 | stack.push(42); 52 | 53 | assert.equal(stack.pop(), 42); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /js/src/samples/testDataBuilder/sampleTests.js: -------------------------------------------------------------------------------- 1 | import * as testUsers from "./testUsers"; 2 | import TestUserBuilder from "./testUserBuilder"; 3 | 4 | 5 | describe("testDataBuilder sample tests", () => { 6 | it("works with ObjectMother", () => { 7 | const user = testUsers.aRegularUser(); 8 | const adminUser = testUsers.anAdmin(); 9 | // Если в тестах нужно много комбинаций параметров, будет комбинаторный взрыв. 10 | }); 11 | 12 | it("works with Builder", () => { 13 | const user = TestUserBuilder.aUser().build(); 14 | const adminUser = TestUserBuilder.aUser().inAdminRole().build(); 15 | // Такой код не ломается, при смене сигнатуры конструктора User. 16 | // Это важно, если таких тестов много. 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /js/src/samples/testDataBuilder/testUserBuilder.js: -------------------------------------------------------------------------------- 1 | import User from "./user"; 2 | 3 | const DEFAULT_NAME = "John Smith"; 4 | const DEFAULT_ROLE = "ROLE_USER"; 5 | const DEFAULT_PASSWORD = "42"; 6 | 7 | 8 | export default class TestUserBuilder { 9 | constructor() { 10 | this.name = DEFAULT_NAME; 11 | this.password = DEFAULT_PASSWORD; 12 | this.role = DEFAULT_ROLE; 13 | this.login = null; 14 | } 15 | 16 | static aRegularUser() { 17 | return this.aUser().build(); 18 | } 19 | 20 | static anAdmin() { 21 | return this.aUser() 22 | .withName("Neo") 23 | .withLogin("neo") 24 | .inAdminRole() 25 | .build(); 26 | } 27 | 28 | static aUser() { 29 | return new TestUserBuilder(); 30 | } 31 | 32 | static but() { 33 | return this.aUser() 34 | .inRole(this.role) 35 | .withName(this.name) 36 | .withPassword(this.password) 37 | .withLogin(this.login) 38 | } 39 | 40 | withName(newName) { 41 | this.name = newName; 42 | return this; 43 | } 44 | 45 | withLogin(newLogin) { 46 | this.login = newLogin; 47 | return this; 48 | } 49 | 50 | withPassword(newPassword) { 51 | this.password = newPassword; 52 | return this; 53 | } 54 | 55 | withNoPassword() { 56 | this.password = null; 57 | return this; 58 | } 59 | 60 | inUserRole() { 61 | return this.inRole("ROLE_USER"); 62 | } 63 | 64 | inAdminRole() { 65 | return this.inRole("ROLE_ADMIN"); 66 | } 67 | 68 | inRole(newRole) { 69 | this.role = newRole; 70 | return this; 71 | } 72 | 73 | build() { 74 | return new User(this.name, this.login, this.password, this.role); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /js/src/samples/testDataBuilder/testUsers.js: -------------------------------------------------------------------------------- 1 | import User from "./user"; 2 | 3 | export function aRegularUser() { 4 | return new User("Triniti", "tri", "asdasd", "ROLE_USER"); 5 | } 6 | 7 | export function anAdmin() { 8 | return new User("Agent Smith", "smith", "qweqwe", "ROLE_ADMIN"); 9 | } 10 | -------------------------------------------------------------------------------- /js/src/samples/testDataBuilder/user.js: -------------------------------------------------------------------------------- 1 | export default class User { 2 | constructor(name, login, password, role) { 3 | this.name = name; 4 | this.login = login; 5 | this.password = password; 6 | this.role = role; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /leaderboard-README.md: -------------------------------------------------------------------------------- 1 | Чтобы показать в лидерборде только js (cs) решения, добавь в ULR параметр lang=js (lang=cs) 2 | 3 | leaderboard.html?lang=cs -------------------------------------------------------------------------------- /leaderboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 19 | 20 | 21 |
22 | 23 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Задание Challenge 2 | 3 | 1. Перед началом работы, зайди в файл `python/challenge/your_name.py` и укажи в нём свою фамилию (фамилии), это необходимо для 4 | персонализации статистики по написанным тестам. 5 | 2. Изучи реализацию WordsStatistics в `python/challenge/word_statistics.py`. 6 | 3. В файл `python/challenge/test_words_statistics.py` добавь тесты, проверяющие корректность 7 | WordsStatistics (на основании реализации). 8 | 4. Референсная реализация должна проходить все ваши тесты. 9 | 5. Каждая некорректная реализация должна падать хотя бы на одном тесте. 10 | 6. Для запуска тестов рекомендуется запускать файл `python/challenge/run_tests.py`, он выведет статистику по 11 | написанным тестам, а также отправит её в дашборд. 12 | 13 | 14 | P.S. В файле `python/challenge/incorrect_implementation/do_not_open.py` (не открывай его!) находится еще 15 | некоторое количество реализаций, каждая из которых содержит некоторую ошибку. 16 | -------------------------------------------------------------------------------- /python/challenge/.solved/implementation_comments.md: -------------------------------------------------------------------------------- 1 | | Имплементация | Комментарий | 2 | |-----------------------|----------------------------------------------------------------------------------| 3 | | WordsStatisticsL2 | не проверяет длину слова | 4 | | WordsStatisticsL3 | Укорачивает слова длинной больше 5 и меньше 10 | 5 | | WordsStatisticsL4 | Слово длиной 9 | 6 | | WordsStatisticsC | Проверяется word to lower, а пишется без to lower | 7 | | WordsStatisticsE | Исключение в случае пробелов | 8 | | WordsStatisticsE2 | Return в случае None | 9 | | WordsStatisticsE3 | Тест 10 пробелов и а | 10 | | WordsStatisticsE4 | Без проверки None | 11 | | WordsStatisticsO1 | Сортировка только по слову | 12 | | WordsStatisticsO2 | Сортировка только по count | 13 | | WordsStatisticsO3 | Корректная имплементация сортируется по слову | 14 | | WordsStatisticsO4 | Слово сортируется в обратном порядке вместо нормального | 15 | | WordsStatisticsCR | Пустая строка | 16 | | WordsStatisticsSTA | Статистика каждый раз заново перерождается - инстанс лежит глобально в классе | 17 | | WordsStatistics123 | Некорректная имплементация хранилища - хэш и список | 18 | | WordsStatisticsQWE | Некорректная имплементация toLower РУССКИЕ БУКВЫ | 19 | | WordsStatistics998 | Тест на производительность | 20 | | WordsStatistics999 | 40000 одинаковых слов добавляются за 1 секунду | 21 | | WordsStatisticsEN1 | Статистика чистится после получения | 22 | | WordsStatisticsEN2 | Статистика считается один раз | -------------------------------------------------------------------------------- /python/challenge/fb.py: -------------------------------------------------------------------------------- 1 | from firebase import firebase 2 | from datetime import date 3 | import re 4 | 5 | 6 | def firebase_init(): 7 | firebase_url = f"https://testing-challenge.firebaseio.com" 8 | return firebase.FirebaseApplication(firebase_url) 9 | 10 | 11 | def get_safe_username(username: str) -> str: 12 | username = re.sub(r"[^\w_)( -]", "", username.strip()) 13 | username = re.sub(r"\s+", "_", username.strip()) 14 | return username 15 | 16 | 17 | def get_firebase_key_from_filename(filename: str) -> str: 18 | key = re.sub(r"\.py", "", filename.strip()) 19 | return f"WordsStatistics{key.split('_')[-1]}" 20 | 21 | 22 | def create_data_for_fb(data: dict) -> dict: 23 | return { 24 | "implementations": data, 25 | "lang": "py", 26 | "time": date.today().strftime("%Y%m%d") 27 | } 28 | 29 | 30 | def send_to_firebase(user: str, data: dict) -> None: 31 | date_now = date.today().strftime("%Y%m%d") 32 | fb = firebase_init() 33 | r = fb.put(f"/word-statistics/{date_now}/", get_safe_username(user), create_data_for_fb(data)) 34 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_correct_implementation.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[3])) 5 | from python.challenge.word_statistics import WordsStatistics 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatistics() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_123.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatistics123 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatistics123() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_998.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatistics998 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatistics998() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_999.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatistics999 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatistics999() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_C.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsC 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsC() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_CR.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsCR 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsCR() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_E.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsE 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsE() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_E2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsE2 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsE2() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_E3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsE3 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsE3() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_E4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsE4 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsE4() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_EN1.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsEN1 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsEN1() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_EN2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsEN2 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsEN2() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_L2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsL2 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsL2() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_L3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsL3 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsL3() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_L4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsL4 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsL4() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_O1.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsO1 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsO1() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_O2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsO2 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsO2() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_O3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsO3 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsO3() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_O4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsO4 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsO4() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_QWE.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsQWE 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsQWE() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/list_tests/test_incorrect_implementation/test_STA.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | from pathlib import Path 4 | sys.path.append(str(Path(__file__).parents[4])) 5 | from python.challenge.incorrect_implementation.do_not_open import WordsStatisticsSTA 6 | 7 | 8 | @pytest.fixture 9 | def words_statistics(): 10 | return WordsStatisticsSTA() 11 | 12 | 13 | from python.challenge.test_words_statistics import TestWordsStatistics 14 | -------------------------------------------------------------------------------- /python/challenge/run_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import fb 3 | import pytest 4 | from glob import iglob 5 | from your_name import AUTHORS 6 | from io import StringIO 7 | from contextlib import redirect_stdout 8 | 9 | 10 | def get_folder_with_incorrect_tests(): 11 | for f in iglob("**/test_incorrect_implementation", recursive=True): 12 | return f 13 | 14 | 15 | def get_path_to_correct_tests(): 16 | for f in iglob("**/test_correct_implementation.py", recursive=True): 17 | return f 18 | 19 | 20 | def run_tests_for_correct_implementation(): 21 | temp_stdout = StringIO() 22 | with redirect_stdout(temp_stdout): 23 | tests_result = pytest.main([get_path_to_correct_tests(),]) 24 | result = "ПРОЙДЕНЫ" if not tests_result else "НЕ ПРОЙДЕНЫ" 25 | print(f"Тесты на правильную имплементацию: {result}") 26 | return bool(not tests_result) 27 | 28 | 29 | def run_tests_for_incorrect_implementation(): 30 | temp_stdout = StringIO() 31 | firebase_data = {} 32 | total_count = 0 33 | correct_passed = 0 34 | tests_folder = get_folder_with_incorrect_tests() 35 | for filename in os.listdir(tests_folder): 36 | if filename.startswith("test_") and filename.endswith(".py"): 37 | with redirect_stdout(temp_stdout): 38 | test_result = pytest.main([f"{tests_folder}/{filename}",]) 39 | total_count += 1 40 | correct_passed += test_result 41 | firebase_data[fb.get_firebase_key_from_filename(filename)] = test_result 42 | print(f"Тесты на неправильную имплементацию: КОРРЕКТНО {correct_passed} из {total_count}") 43 | fb.send_to_firebase(AUTHORS, firebase_data) 44 | 45 | 46 | if __name__ == "__main__": 47 | if not AUTHORS: 48 | raise ValueError("Укажите фамилии учеников в файл /python/challenge/your_name.py") 49 | if run_tests_for_correct_implementation(): 50 | run_tests_for_incorrect_implementation() 51 | -------------------------------------------------------------------------------- /python/challenge/test_words_statistics.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from python.challenge.word_statistics import WordCount, WordsStatistics 4 | 5 | 6 | @pytest.fixture 7 | def words_statistics(): 8 | return WordsStatistics() 9 | 10 | 11 | class TestWordsStatistics: 12 | def test_get_statistics_is_empty_after_creation(self, words_statistics): 13 | assert words_statistics.get_statistics() == [] 14 | 15 | def test_get_statistics_contains_item_after_addition(self, words_statistics): 16 | words_statistics.add_word("abc") 17 | assert words_statistics.get_statistics() == [WordCount("abc", 1)] 18 | 19 | def test_get_statistics_contains_many_items_after_addition_of_different_words(self, words_statistics): 20 | words_statistics.add_word("abc") 21 | words_statistics.add_word("def") 22 | assert len(words_statistics.get_statistics()) == 2 23 | -------------------------------------------------------------------------------- /python/challenge/word_statistics.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | from collections import OrderedDict 3 | 4 | 5 | class WordCount: 6 | def __init__(self, word: str, count: int) -> None: 7 | self.word = word 8 | self.count = count 9 | 10 | @staticmethod 11 | def create(pair: Tuple[str, int]) -> 'WordCount': 12 | return WordCount(pair[0], pair[1]) 13 | 14 | def __eq__(self, other: str) -> bool: 15 | if not isinstance(other, WordCount): 16 | return False 17 | return (self.word, self.count) == (other.word, other.count) 18 | 19 | 20 | class WordsStatistics: 21 | def __init__(self): 22 | self.statistics = OrderedDict() 23 | 24 | def add_word(self, word: str) -> None: 25 | if word is None: 26 | raise ValueError("Word cannot be None") 27 | if not word.strip(): 28 | return 29 | if len(word) > 10: 30 | word = word[:10] 31 | word = word.lower() 32 | self.statistics[word] = self.statistics.get(word, 0) + 1 33 | 34 | def get_statistics(self) -> List[WordCount]: 35 | return [WordCount.create(item) for item in sorted(self.statistics.items(), key=lambda x: (-x[1], x[0]))] 36 | -------------------------------------------------------------------------------- /python/challenge/your_name.py: -------------------------------------------------------------------------------- 1 | AUTHORS = "Python Student" # Ваши фамилии через пробел. Например, "Egorov Shagalina" 2 | -------------------------------------------------------------------------------- /python/lib/stack.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any, List 2 | 3 | 4 | class StackIsEmptyException(Exception): 5 | pass 6 | 7 | 8 | class Stack: 9 | 10 | def __init__(self, values: Optional[List[Any]] = None) -> None: 11 | self.stack = values or [] 12 | 13 | def push(self, value: Any) -> None: 14 | self.stack.append(value) 15 | 16 | def pop(self) -> Any: 17 | if len(self.stack) <= 0: 18 | raise StackIsEmptyException 19 | return self.stack.pop() 20 | 21 | def top(self) -> List[Any]: 22 | if len(self.stack) <= 0: 23 | raise StackIsEmptyException 24 | return self.stack[-1] 25 | 26 | def to_list(self) -> List[Any]: 27 | return list(reversed(self.stack)) 28 | 29 | def __len__(self) -> int: 30 | return len(self.stack) 31 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==7.3.1 2 | pytest-timeout==2.1.0 3 | firebase==4.0.1 4 | -------------------------------------------------------------------------------- /python/samples/antipatterns/test_stack_1.py: -------------------------------------------------------------------------------- 1 | from python.lib.stack import Stack 2 | 3 | 4 | def test_stack(): 5 | data = [] 6 | with open("/Users/admin/Documents/data.txt") as f: 7 | for line in f.readlines(): 8 | data.append(line.split()) 9 | 10 | stack = Stack() 11 | for action, num in data: 12 | if action == "push": 13 | stack.push(int(num)) 14 | elif action == "pop": 15 | assert stack.pop() == int(num) 16 | 17 | ## Антипаттерн Local Hero 18 | 19 | # Тест не будет работать на машине другого человека или на Build-сервере. 20 | # Да и у того же самого человека после Clean Solution / переустановки ОС / повторного Clone репозитория / ... 21 | # 22 | 23 | ## Решение 24 | # 25 | # Тест не должен зависеть от особенностей локальной среды. 26 | # Если нужна работа с файлами, то либо включите файл в проект и настройте в свойствах его копирование в OutputDir, 27 | # либо поместите его в ресурсы. 28 | # 29 | # можно указать относительный путь до файла или путь из корня проекта 30 | # with open("../data.txt") as f: 31 | # ... 32 | -------------------------------------------------------------------------------- /python/samples/antipatterns/test_stack_2.py: -------------------------------------------------------------------------------- 1 | from python.lib.stack import Stack 2 | 3 | 4 | def test_stack(): 5 | stack = Stack() 6 | stack.push(1) 7 | stack.push(2) 8 | stack.push(3) 9 | 10 | while len(stack) > 0: 11 | print(stack.pop()) 12 | 13 | ## Антипаттерн Loudmouth 14 | # 15 | # Тест не является автоматическим. Если он сломается, никто этого не заметит. 16 | # 17 | ## Мораль 18 | # 19 | # Вместо вывода на консоль, используйте Assert-ы. 20 | -------------------------------------------------------------------------------- /python/samples/antipatterns/test_stack_3.py: -------------------------------------------------------------------------------- 1 | from python.lib.stack import Stack 2 | 3 | 4 | def test_stack(): 5 | stack = Stack() 6 | 7 | assert len(stack) == 0 8 | 9 | stack.push(1) 10 | stack.pop() 11 | assert len(stack) == 0 12 | 13 | stack.push(1) 14 | stack.push(2) 15 | stack.push(3) 16 | assert len(stack) == 3 17 | 18 | stack.pop() 19 | stack.pop() 20 | stack.pop() 21 | assert len(stack) == 0 22 | 23 | for i in range(1001): 24 | stack.push(i) 25 | 26 | for i in range(1000, -1, -1): 27 | assert stack.pop() == i 28 | 29 | ## Антипаттерн Freeride 30 | 31 | # 1. Непонятна область его ответственности. Складывается впечатление, что он тестирует все, однако он это делает плохо. 32 | # Он дает ложное чувство, что все протестировано. Хотя, например, этот тест не проверяет много важных случаев. 33 | # 34 | # 2. Таким тестам как-правило невозможно придумать внятное название. 35 | # 36 | # 3. Если что-то упадет в середине теста, будет сложно разобраться что именно пошло не так и сложно отлаживать — нужно жонглировать точками останова. 37 | # 38 | # 4. Такой тест не работает как документация. По этому сценарию непросто восстановить требования к тестируемому объекту. 39 | 40 | ## Мораль 41 | 42 | # Каждый тест должен тестировать одно конкретное требование. Это требование должно отражаться в названии теста. 43 | # Если вы не можете придумать название теста, у вас Free Ride! 44 | -------------------------------------------------------------------------------- /python/samples/antipatterns/test_stack_4.py: -------------------------------------------------------------------------------- 1 | from python.lib.stack import Stack 2 | 3 | 4 | def test_stack(): 5 | stack = Stack([1, 2, 3, 4, 5]) 6 | result = stack.pop() 7 | 8 | assert result == 5 9 | assert len(stack) == 4 10 | assert stack.top() == 4 11 | assert stack.to_list() == [4, 3, 2, 1] 12 | 13 | 14 | 15 | ## Антипаттерн Overspecification 16 | 17 | # 1. Непонятна область ответственности. Сложно придумать название. Не так плохо, как FreeRide, но плохо. 18 | # 19 | # 2. Изменение API роняет сразу много подобных тестов, создавая много рутинной работы по их починке. 20 | # 21 | # 3. Если все тесты будут такими, то при появлении бага, падают они большой компанией. 22 | 23 | 24 | ## Мораль 25 | 26 | # Сфокусируйтесь на проверке одного конкретного требования в каждом тесте. 27 | # Не старайтесь проверить "за одно" какое-то требование сразу во всех тестах — это может выйти боком. 28 | # 29 | # Признак возможной проблемы — более одного Assert на метод. 30 | -------------------------------------------------------------------------------- /python/samples/data.txt: -------------------------------------------------------------------------------- 1 | push 1 2 | pop 1 3 | push 2 4 | push 3 5 | pop 3 6 | pop 2 -------------------------------------------------------------------------------- /python/samples/parametrized/test_float.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(params=[(12.0, 3.0, 4), (12.0, 2.0, 6), (12.0, 4.0, 3)]) 5 | def divide_test_cases(request): 6 | return request.param 7 | 8 | 9 | @pytest.mark.parametrize("input_str, expected", 10 | [("123", 123), ("1.1", 1.1), ("1.1e1", 1.1e1), ("1.1e-1", 1.1e-1), ("-0.1", -0.1)]) 11 | def test_parse_with_invariant_culture(input_str, expected): 12 | assert pytest.approx(float(input_str), 0.0001) == expected 13 | 14 | 15 | def test_divide(divide_test_cases): 16 | a, b, expected = divide_test_cases 17 | assert pytest.approx(a / b, 0.0001) == expected 18 | -------------------------------------------------------------------------------- /python/samples/specifications/test_stack_specification.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from python.lib.stack import Stack, StackIsEmptyException 4 | 5 | 6 | class TestStackSpecification: 7 | 8 | def test_create_empty_stack(self) -> None: 9 | stack = Stack() 10 | 11 | assert len(stack) == 0 12 | 13 | def test_push_items_to_empty_stack(self) -> None: 14 | stack = Stack([1, 2, 3]) 15 | 16 | assert len(stack) == 3 17 | assert stack.pop() == 3 18 | assert stack.pop() == 2 19 | assert stack.pop() == 1 20 | assert len(stack) == 0 21 | 22 | def test_to_array_returns_items_in_pop_order(self) -> None: 23 | stack = Stack([1, 2, 3]) 24 | 25 | assert stack.to_list() == [3, 2, 1] 26 | 27 | def test_add_items_on_stack_top(self) -> None: 28 | stack = Stack([1, 2, 3]) 29 | stack.push(42) 30 | 31 | assert stack.to_list() == [42, 3, 2, 1] 32 | 33 | def test_pop_on_empty_stack_fails(self) -> None: 34 | stack = Stack() 35 | 36 | with pytest.raises(StackIsEmptyException): 37 | stack.pop() 38 | 39 | def test_pop_returns_last_pushed_item(self) -> None: 40 | stack = Stack([1, 2, 3]) 41 | stack.push(42) 42 | 43 | assert stack.pop() == 42 44 | -------------------------------------------------------------------------------- /python/samples/test_data_builder/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from user import User 4 | 5 | 6 | @pytest.fixture 7 | def regular_user(): 8 | return User(name="Triniti", role="ROLE_USER", login="tri", password="asdasd") 9 | 10 | 11 | @pytest.fixture 12 | def admin(): 13 | return User(name="Agent Smith", role="ROLE_ADMIN", login="smith", password="qweqwe") 14 | -------------------------------------------------------------------------------- /python/samples/test_data_builder/test_user_builder.py: -------------------------------------------------------------------------------- 1 | # фикстуры из файла conftest.py импортируются автоматически 2 | from user_builder import TestUserBuilder 3 | 4 | 5 | class TestTestDataBuilderSample: 6 | def test_works_with_object_mother(self, regular_user, admin): 7 | assert regular_user.name == "Triniti" 8 | assert regular_user.login == "tri" 9 | assert regular_user.password == "asdasd" 10 | assert regular_user.role == "ROLE_USER" 11 | 12 | assert admin.name == "Agent Smith" 13 | assert admin.login == "smith" 14 | assert admin.password == "qweqwe" 15 | assert admin.role == "ROLE_ADMIN" 16 | 17 | def test_works_with_builder(self): 18 | user = TestUserBuilder.user().build() 19 | admin_user = TestUserBuilder.user().in_admin_role().with_login("very_admin").build() 20 | assert user.name == "John Smith" 21 | assert user.login is None 22 | assert user.password == "42" 23 | assert user.role == "ROLE_USER" 24 | assert admin_user.name == "John Smith" 25 | assert admin_user.login == "very_admin" 26 | assert admin_user.password == "42" 27 | assert admin_user.role == "ROLE_ADMIN" 28 | -------------------------------------------------------------------------------- /python/samples/test_data_builder/user.py: -------------------------------------------------------------------------------- 1 | class User: 2 | def __init__(self, name: str, role: str, login: str = None, password: str = None): 3 | self.name = name 4 | self.role = role 5 | self.login = login 6 | self.password = password 7 | -------------------------------------------------------------------------------- /python/samples/test_data_builder/user_builder.py: -------------------------------------------------------------------------------- 1 | from user import User 2 | 3 | 4 | class TestUserBuilder: 5 | DEFAULT_NAME = "John Smith" 6 | DEFAULT_ROLE = "ROLE_USER" 7 | DEFAULT_PASSWORD = "42" 8 | 9 | def __init__(self): 10 | self.name = self.DEFAULT_NAME 11 | self.password = self.DEFAULT_PASSWORD 12 | self.role = self.DEFAULT_ROLE 13 | self.login = None 14 | 15 | @classmethod 16 | def user(cls): 17 | return cls() 18 | 19 | def with_name(self, new_name: str) -> 'TestUserBuilder': 20 | self.name = new_name 21 | return self 22 | 23 | def with_login(self, new_login: str) -> 'TestUserBuilder': 24 | self.login = new_login 25 | return self 26 | 27 | def with_password(self, new_password: str) -> 'TestUserBuilder': 28 | self.password = new_password 29 | return self 30 | 31 | def with_no_password(self) -> 'TestUserBuilder': 32 | self.password = None 33 | return self 34 | 35 | def in_user_role(self) -> 'TestUserBuilder': 36 | return self.in_role("ROLE_USER") 37 | 38 | def in_admin_role(self) -> 'TestUserBuilder': 39 | return self.in_role("ROLE_ADMIN") 40 | 41 | def in_role(self, new_role: str) -> 'TestUserBuilder': 42 | self.role = new_role 43 | return self 44 | 45 | def but(self) -> 'TestUserBuilder': 46 | return self.user().in_role(self.role).with_name(self.name).with_password(self.password).with_login(self.login) 47 | 48 | def build(self) -> User: 49 | return User(name=self.name, role=self.role, login=self.login, password=self.password) 50 | 51 | @staticmethod 52 | def regular_user() -> User: 53 | return TestUserBuilder.user().build() 54 | 55 | @staticmethod 56 | def admin() -> User: 57 | return TestUserBuilder.user().with_name("Neo").with_login("neo").in_admin_role().build() 58 | -------------------------------------------------------------------------------- /python/settings.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontur-courses/testing/9120c2429edab93c7653b63ee5df8c950bf1fbc3/python/settings.zip -------------------------------------------------------------------------------- /testing-c#.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontur-courses/testing/9120c2429edab93c7653b63ee5df8c950bf1fbc3/testing-c#.pptx -------------------------------------------------------------------------------- /testing-java.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontur-courses/testing/9120c2429edab93c7653b63ee5df8c950bf1fbc3/testing-java.pptx -------------------------------------------------------------------------------- /testing.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontur-courses/testing/9120c2429edab93c7653b63ee5df8c950bf1fbc3/testing.pptx --------------------------------------------------------------------------------