Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
28e3f4a
Добавил IDisposable в интерфейс IReadOnlyStream. Реализовал в классе …
Certalarm Feb 10, 2026
c0bfb3b
Написал ReadOnlyStream.
Certalarm Feb 10, 2026
3b2db89
Написал FillSingleLetterStats.
Certalarm Feb 10, 2026
03db2c8
Написал FillDoubleLetterStats.
Certalarm Feb 10, 2026
6e5dec4
Прибрался в FillSingleLetterStats и FillDoubleLetterStats.
Certalarm Feb 10, 2026
1613c67
Перенес логику заполнения статистики в HelperFillStat.
Certalarm Feb 11, 2026
6921e2d
Добавил проект для юнит-тнстов. Установил зависимости для тестов.
Certalarm Feb 11, 2026
cb2a8cf
Создал FakeReadOnlyStream.
Certalarm Feb 11, 2026
5fcac98
Написал первый тест для FillStats.
Certalarm Feb 11, 2026
3f82f50
Создал workflow для запуска тестов.
Certalarm Feb 11, 2026
0ed8b53
Написал тесты для FillStats.
Certalarm Feb 11, 2026
313b4d5
Написал HelperRemoveStat.
Certalarm Feb 11, 2026
7c90b35
Написал тесты для RemoveStats.
Certalarm Feb 11, 2026
d90f68c
Написал HelperPrintStat. Прибрался в Program.
Certalarm Feb 11, 2026
3f04d18
Написал HelperMain. Написал тесты для PrintStats.
Certalarm Feb 11, 2026
2c41656
Написал тесты для HelperMain. Написал xml-комментарии. Прибрался в ко…
Certalarm Feb 11, 2026
77844cf
Создал папку _assets.
Certalarm Feb 11, 2026
f2384b3
Добавил исзодные файлы в папку _assets.
Certalarm Feb 11, 2026
cf8ef95
Обновил README.md
Certalarm Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/TestTask-tests-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: TestTask Build and Test # Имя workflow, отображается в интерфейсе GitHub Actions
on:
push: # Запускается при каждом push в репозиторий
branches:
- master # ветки, для которых нужно запускать тесты

jobs: # Определяет набор задач (jobs), которые будут выполнены
service-build: # Имя задачи
runs-on: windows-latest # на каком окружении будет выполняться задача
steps: # Список шагов, которые будут выполнены в рамках задачи
- name: Checkout repository
uses: actions/checkout@v3
- name: Restore NuGet packages
run: |
nuget restore TestTask\TestTask.sln # Ваш sln-файл решения
- name: Build solution
run: |
& "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\amd64\msbuild.exe" /m TestTask\TestTask.sln # Сборка решения
- name: Run tests
working-directory: TestTask.UnitTests
run: dotnet test TestTask.UnitTests.csproj --verbosity normal # Выполняет команду dotnet test для запуска юнит-тестов
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Тестовое задание.
# ТЗ.
Доброго дня.
Данный репозиторий представляет из себя тестовое задание для выполнения перед собеседованием на вакансию разработчика C# в компанию Катрен.

Expand All @@ -20,3 +20,43 @@ __Для разработки настоятельно рекомендуетс
И по готовности результат отдать на ревью методом Pull Request'a.__

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

# РЕШЕНИЕ
## Что сделано?
Всё, согласно ТЗ, все `TODO` (9 шт). Код исправлен и доработан.

### Дополнительно
- Написаны юнит-тесты,
- Настроен автозапуск тестов на `GitHub Actions` при каждом коммите в удаленный репозиторий.

### Структура Решения
Решение состоит из 2-х проектов
- TestTask (исходный проект),
- TestTask.UnitTests (юнит-тесты).

```
.
├──📂TestTask Исходный проект
│ ├──📂Helpers
│ │ ├──📄HelperFillStat.cs Логика для заполнения статистики
│ │ ├──📄HelperMain.cs Логика для основного метода Main
│ │ ├──📄HelperPrintStat.cs Логика для печати статистики
│ │ └──📄HelperRemoveStat.cs Логика для удаления статистики
│ ├──📄CharType.cs
│ ├──📄IReadOnlyStream.cs
│ ├──📄LetterStats.cs
│ ├──📄Program.cs Точка входа в программу
│ └──📄ReadOnlyStream.cs
└──📂TestTask.UnitTests Проект с юнит-тестами
├──📂Fakes
│ └──📄FakeReadOnlyStream.cs Фейковая реализация ReadOnlyStream
├──📂InputData
│ └──📄InputObjectData.cs Входные данные для тестов
├──📄FillStatsTests.cs Юнит-тесты для HelperFillStat
├──📄HelperMainTests.cs Юнит-тесты для HelperMain
├──📄PrintStatsTests.cs Юнит-тесты для HelperPrintStat
└──📄RemoveStatsTests.cs Юнит-тесты для HelperRemoveStat
```

Исполняемые файлы с программой [здесь](https://github.com/Certalarm/TestTask/tree/master/_assets/TestTask.zip). Не требует установки.
25 changes: 25 additions & 0 deletions TestTask.UnitTests/Fakes/FakeReadOnlyStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.IO;
using System.Text;

namespace TestTask.UnitTests.Fakes
{
public class FakeReadOnlyStream : ReadOnlyStream
{
public FakeReadOnlyStream(string inputData, Encoding encoding = default) : base(inputData, encoding)
{
}

public override Stream CreateStream(string param)
{
try
{
byte[] bytes = Encoding.GetBytes(param);
return new MemoryStream(bytes);
}
catch
{
return Stream.Null;
}
}
}
}
77 changes: 77 additions & 0 deletions TestTask.UnitTests/FillStatsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.IO;
using TestTask.Helpers;
using TestTask.UnitTests.Fakes;
using TestTask.UnitTests.InputData;
using Xunit;

namespace TestTask.UnitTests
{
public class FillStatsTests
{
// format: inputData
public static List<object[]> DataWithNoLetters = InputObjectData.DataWithNoLetters;

// format: inputData, singleStatCount, doubleStatCount
public static List<object[]> DataWithLettersOnly = InputObjectData.DataWithLettersOnlyFillStat;

[Fact]
public void create_reader_with_null_param_will_be_exception()
{
FakeReadOnlyStream sut;

Assert.Throws<ArgumentNullException>(() => sut = new FakeReadOnlyStream(null));
}

[Fact]
public void fill_stats_with_control_symbols_only_will_be_exception()
{
string stream = "\0\0\0\0\0\0\0\0\0\0\t\t\t\r\n\r\n";

IList<LetterStats> singleResult = new List<LetterStats>();
IList<LetterStats> doubleResult = new List<LetterStats>();

using (IReadOnlyStream sut = new FakeReadOnlyStream(stream))
{
Assert.Throws<EndOfStreamException>(() => singleResult = HelperFillStat.FillSingleLetterStats(sut));
Assert.Throws<EndOfStreamException>(() => doubleResult = HelperFillStat.FillDoubleLetterStats(sut));
}
}

[Theory]
[MemberData(nameof(DataWithNoLetters))]
public void fill_stats_with_no_letters_symbols_only_will_be_empty_stats(string inputData)
{
IList<LetterStats> singleResult = new List<LetterStats>();
IList<LetterStats> doubleResult = new List<LetterStats>();

using (IReadOnlyStream sut = new FakeReadOnlyStream(inputData))
{
singleResult = HelperFillStat.FillSingleLetterStats(sut);
doubleResult = HelperFillStat.FillDoubleLetterStats(sut);
}

singleResult.Should().HaveCount(0);
doubleResult.Should().HaveCount(0);
}

[Theory]
[MemberData(nameof(DataWithLettersOnly))]
public void fill_stats_with_letters_symbols_only_will_has_count(string inputData, int singleStatsCount, int doubleStatsCount)
{
IList<LetterStats> singleResult = new List<LetterStats>();
IList<LetterStats> doubleResult = new List<LetterStats>();

using (IReadOnlyStream sut = new FakeReadOnlyStream(inputData))
{
singleResult = HelperFillStat.FillSingleLetterStats(sut);
doubleResult = HelperFillStat.FillDoubleLetterStats(sut);
}

singleResult.Should().HaveCount(singleStatsCount);
doubleResult.Should().HaveCount(doubleStatsCount);
}
}
}
22 changes: 22 additions & 0 deletions TestTask.UnitTests/HelperMainTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FluentAssertions;
using System.Collections.Generic;
using TestTask.Helpers;
using TestTask.UnitTests.InputData;
using Xunit;

namespace TestTask.UnitTests
{
public class HelperMainTests
{
public static List<object[]> DataWithBadPaths = InputObjectData.DataWithBadPaths;

[Theory]
[MemberData(nameof(DataWithBadPaths))]
public void isBadArgs_with_bad_args_will_be_true(string[] args)
{
var result = HelperMain.IsBadArgs(args);

result.Should().BeTrue();
}
}
}
82 changes: 82 additions & 0 deletions TestTask.UnitTests/InputData/InputObjectData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Collections.Generic;

namespace TestTask.UnitTests.InputData
{
public static class InputObjectData
{
private static readonly List<string> _inputData = new List<string>()
{
"ААБББВВВВГДЕЖЗЯЯЯ",
"аабббввввгдежзяяя",
"аАБбБвВВвГдЕЖзяЯЯ",
};


// format: inputData
public static List<object[]> DataWithNoLetters = new List<object[]>()
{
new object[] { "" },
new object[] { "123454321708090706,-./" },
new object[] { "12345\0\0\0\0\0\0\0\0\0\0\t\t\t\r\n\r\n"},
};

// format: inputData, singleStatCount, doubleStatCount
public static List<object[]> DataWithLettersOnlyFillStat = new List<object[]>()
{
new object[] { _inputData[0], 9, 4 },
new object[] { _inputData[1], 9, 4 },
new object[] { _inputData[2], 13, 4},
};

// format: inputData, singleStatCount, doubleStatCount
public static List<object[]> DataWithLettersOnlyRemoveVowelStat = new List<object[]>()
{
new object[] { _inputData[0], 6, 2 },
new object[] { _inputData[1], 6, 2 },
new object[] { _inputData[2], 8, 2 },
};

// format: inputData, singleStatCount, doubleStatCount
public static List<object[]> DataWithLettersOnlyRemoveConsonantStat = new List<object[]>()
{
new object[] { _inputData[0], 3, 2 },
new object[] { _inputData[1], 3, 2 },
new object[] { _inputData[2], 5, 2 },
};


// format: inputData, singleStatLineCount
public static List<object[]> DataWithLettersOnlySinglePrintStat = new List<object[]>()
{
new object[] { _inputData[0], 7 },
new object[] { _inputData[1], 7 },
new object[] { _inputData[2], 9 },
};

// format: inputData, doubleStatLineCount
public static List<object[]> DataWithLettersOnlyDoublePrintStat = new List<object[]>()
{
new object[] { _inputData[0], 3 },
new object[] { _inputData[1], 3 },
new object[] { _inputData[2], 3 },
};

// format: args[]
public static List<object[]> DataWithBadPaths = new List<object[]>()
{
new object[] { null },
new object[] { new object[] { null, null } },
new object[] { new object[] { "", "" } },
new object[] { new object[] { null, "" } },
new object[] { new object[] { "", null } },
new object[] { new object[] { null, "1" } },
new object[] { new object[] { "1", null } },
new object[] { new object[] { "", "1" } },
new object[] { new object[] { "1", "" } },
new object[] { new object[] { "1", "" } },
new object[] { new object[] { "1" } },
new object[] { new object[] { "" } },
new object[] { new object[] { null } },
};
}
}
47 changes: 47 additions & 0 deletions TestTask.UnitTests/PrintStatsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Linq;
using TestTask.Helpers;
using TestTask.UnitTests.Fakes;
using TestTask.UnitTests.InputData;
using Xunit;

namespace TestTask.UnitTests
{
public class PrintStatsTests
{
public static List<object[]> DataWithLettersOnlySingle = InputObjectData.DataWithLettersOnlySinglePrintStat;
public static List<object[]> DataWithLettersOnlyDouble = InputObjectData.DataWithLettersOnlyDoublePrintStat;

[Theory]
[MemberData(nameof(DataWithLettersOnlySingle))]
public void report_single_stats_will_be_have_line_count(string inputData, int singleStatsLineCount)
{
var stream = new FakeReadOnlyStream(inputData);
var singleStat = HelperFillStat.FillSingleLetterStats(stream);
HelperRemoveStat.RemoveCharStatsByType(singleStat, CharType.Vowel);

var linesCount = HelperPrintStat.BuildReport(singleStat)
.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
.Count();

linesCount.Should().Be(singleStatsLineCount);
}

[Theory]
[MemberData(nameof(DataWithLettersOnlyDouble))]
public void report_double_stats_will_be_have_line_count(string inputData, int doubleStatsLineCount)
{
var stream = new FakeReadOnlyStream(inputData);
var doubleStat = HelperFillStat.FillDoubleLetterStats(stream);
HelperRemoveStat.RemoveCharStatsByType(doubleStat, CharType.Consonants);

var linesCount = HelperPrintStat.BuildReport(doubleStat)
.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
.Count();

linesCount.Should().Be(doubleStatsLineCount);
}
}
}
33 changes: 33 additions & 0 deletions TestTask.UnitTests/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Общие сведения об этой сборке предоставляются следующим набором
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
// связанные со сборкой.
[assembly: AssemblyTitle("TestTask.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TestTask.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
// COM, задайте атрибуту ComVisible значение TRUE для этого типа.
[assembly: ComVisible(false)]

// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
[assembly: Guid("99a1b5e9-b57d-4591-b8df-0e6796c45d4c")]

// Сведения о версии сборки состоят из указанных ниже четырех значений:
//
// Основной номер версии
// Дополнительный номер версии
// Номер сборки
// Редакция
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
Loading