├── .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 |
5 |
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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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
--------------------------------------------------------------------------------