From 40a7b4ad713186cdf1c920c74e2494c0b5be2bfa Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Mon, 24 Nov 2025 02:35:37 +0300 Subject: [PATCH 01/12] add: started completing the task --- MyNUnit/Attributes/AfterAttribute.cs | 10 ++++++++ MyNUnit/Attributes/AfterClassAttribute.cs | 10 ++++++++ MyNUnit/Attributes/Attributes.csproj | 10 ++++++++ MyNUnit/Attributes/BeforeAttribute.cs | 10 ++++++++ MyNUnit/Attributes/BeforeClassAttribute.cs | 10 ++++++++ MyNUnit/Attributes/TestAttribute.cs | 8 ++++++ MyNUnit/MyNUnit.slnx | 4 +++ MyNUnit/MyNUnit/MyNUnit.csproj | 10 ++++++++ MyNUnit/MyNUnit/TestResult.cs | 12 +++++++++ MyNUnit/MyNUnit/TestRunner.cs | 30 ++++++++++++++++++++++ 10 files changed, 114 insertions(+) create mode 100644 MyNUnit/Attributes/AfterAttribute.cs create mode 100644 MyNUnit/Attributes/AfterClassAttribute.cs create mode 100644 MyNUnit/Attributes/Attributes.csproj create mode 100644 MyNUnit/Attributes/BeforeAttribute.cs create mode 100644 MyNUnit/Attributes/BeforeClassAttribute.cs create mode 100644 MyNUnit/Attributes/TestAttribute.cs create mode 100644 MyNUnit/MyNUnit.slnx create mode 100644 MyNUnit/MyNUnit/MyNUnit.csproj create mode 100644 MyNUnit/MyNUnit/TestResult.cs create mode 100644 MyNUnit/MyNUnit/TestRunner.cs diff --git a/MyNUnit/Attributes/AfterAttribute.cs b/MyNUnit/Attributes/AfterAttribute.cs new file mode 100644 index 0000000..32e9867 --- /dev/null +++ b/MyNUnit/Attributes/AfterAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class AfterAttribute : Attribute { } diff --git a/MyNUnit/Attributes/AfterClassAttribute.cs b/MyNUnit/Attributes/AfterClassAttribute.cs new file mode 100644 index 0000000..a62a3b8 --- /dev/null +++ b/MyNUnit/Attributes/AfterClassAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class AfterClassAttribute : Attribute { } diff --git a/MyNUnit/Attributes/Attributes.csproj b/MyNUnit/Attributes/Attributes.csproj new file mode 100644 index 0000000..fd4bd08 --- /dev/null +++ b/MyNUnit/Attributes/Attributes.csproj @@ -0,0 +1,10 @@ + + + + Exe + net9.0 + enable + enable + + + diff --git a/MyNUnit/Attributes/BeforeAttribute.cs b/MyNUnit/Attributes/BeforeAttribute.cs new file mode 100644 index 0000000..40305e7 --- /dev/null +++ b/MyNUnit/Attributes/BeforeAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class BeforeAttribute : Attribute { } \ No newline at end of file diff --git a/MyNUnit/Attributes/BeforeClassAttribute.cs b/MyNUnit/Attributes/BeforeClassAttribute.cs new file mode 100644 index 0000000..fd3466d --- /dev/null +++ b/MyNUnit/Attributes/BeforeClassAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class BeforeClassAttribute : Attribute { } diff --git a/MyNUnit/Attributes/TestAttribute.cs b/MyNUnit/Attributes/TestAttribute.cs new file mode 100644 index 0000000..e80c5dc --- /dev/null +++ b/MyNUnit/Attributes/TestAttribute.cs @@ -0,0 +1,8 @@ +namespace Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class TestAttribute : Attribute +{ + public Type? Expected { get; set; } + public string? Ignore { get; set; } +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit.slnx b/MyNUnit/MyNUnit.slnx new file mode 100644 index 0000000..2c85c92 --- /dev/null +++ b/MyNUnit/MyNUnit.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/MyNUnit/MyNUnit/MyNUnit.csproj b/MyNUnit/MyNUnit/MyNUnit.csproj new file mode 100644 index 0000000..fd4bd08 --- /dev/null +++ b/MyNUnit/MyNUnit/MyNUnit.csproj @@ -0,0 +1,10 @@ + + + + Exe + net9.0 + enable + enable + + + diff --git a/MyNUnit/MyNUnit/TestResult.cs b/MyNUnit/MyNUnit/TestResult.cs new file mode 100644 index 0000000..3a8ba8c --- /dev/null +++ b/MyNUnit/MyNUnit/TestResult.cs @@ -0,0 +1,12 @@ +namespace MyNUnit; + +public class TestResult +{ + public string ClassName { get; set; } + public string MethodName { get; set; } + public bool IsSuccess { get; set; } + public bool IsIgnored { get; set; } + public string IgnoreReason { get; set; } + public string ErrorMessage { get; set; } + public TimeSpan TestTime { get; set; } +} diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs new file mode 100644 index 0000000..96c26fc --- /dev/null +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace MyNUnit +{ + public class TestRunner + { + public void RunTest(string path) + { + var dlls = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories); + var results = new ConcurrentBag(); + + Parallel.ForEach(dlls, (dllPath) => + { + try + { + var assembly = Assembly.LoadFrom(dllPath); + var testClasses = assembly.GetTypes(); + } + } + + } + + } +} From b161a575620119ca629981fdf178e8ede5907865 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Tue, 25 Nov 2025 23:53:55 +0300 Subject: [PATCH 02/12] add: the main logic requested in the task condition --- MyNUnit/Attributes/AfterAttribute.cs | 8 +- MyNUnit/Attributes/AfterClassAttribute.cs | 8 +- MyNUnit/Attributes/Attributes.csproj | 7 + MyNUnit/Attributes/BeforeAttribute.cs | 8 +- MyNUnit/Attributes/BeforeClassAttribute.cs | 8 +- MyNUnit/MyNUnit/MyNUnit.csproj | 11 ++ MyNUnit/MyNUnit/Program.cs | 17 ++ MyNUnit/MyNUnit/TestResult.cs | 2 +- MyNUnit/MyNUnit/TestRunner.cs | 206 +++++++++++++++++++-- 9 files changed, 231 insertions(+), 44 deletions(-) create mode 100644 MyNUnit/MyNUnit/Program.cs diff --git a/MyNUnit/Attributes/AfterAttribute.cs b/MyNUnit/Attributes/AfterAttribute.cs index 32e9867..3fa85c0 100644 --- a/MyNUnit/Attributes/AfterAttribute.cs +++ b/MyNUnit/Attributes/AfterAttribute.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Attributes; +namespace Attributes; [AttributeUsage(AttributeTargets.Method)] public class AfterAttribute : Attribute { } diff --git a/MyNUnit/Attributes/AfterClassAttribute.cs b/MyNUnit/Attributes/AfterClassAttribute.cs index a62a3b8..02895c8 100644 --- a/MyNUnit/Attributes/AfterClassAttribute.cs +++ b/MyNUnit/Attributes/AfterClassAttribute.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Attributes; +namespace Attributes; [AttributeUsage(AttributeTargets.Method)] public class AfterClassAttribute : Attribute { } diff --git a/MyNUnit/Attributes/Attributes.csproj b/MyNUnit/Attributes/Attributes.csproj index fd4bd08..90dd746 100644 --- a/MyNUnit/Attributes/Attributes.csproj +++ b/MyNUnit/Attributes/Attributes.csproj @@ -7,4 +7,11 @@ enable + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/MyNUnit/Attributes/BeforeAttribute.cs b/MyNUnit/Attributes/BeforeAttribute.cs index 40305e7..c295ac4 100644 --- a/MyNUnit/Attributes/BeforeAttribute.cs +++ b/MyNUnit/Attributes/BeforeAttribute.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Attributes; +namespace Attributes; [AttributeUsage(AttributeTargets.Method)] public class BeforeAttribute : Attribute { } \ No newline at end of file diff --git a/MyNUnit/Attributes/BeforeClassAttribute.cs b/MyNUnit/Attributes/BeforeClassAttribute.cs index fd3466d..fde0d0b 100644 --- a/MyNUnit/Attributes/BeforeClassAttribute.cs +++ b/MyNUnit/Attributes/BeforeClassAttribute.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Attributes; +namespace Attributes; [AttributeUsage(AttributeTargets.Method)] public class BeforeClassAttribute : Attribute { } diff --git a/MyNUnit/MyNUnit/MyNUnit.csproj b/MyNUnit/MyNUnit/MyNUnit.csproj index fd4bd08..1dfa517 100644 --- a/MyNUnit/MyNUnit/MyNUnit.csproj +++ b/MyNUnit/MyNUnit/MyNUnit.csproj @@ -7,4 +7,15 @@ enable + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/MyNUnit/MyNUnit/Program.cs b/MyNUnit/MyNUnit/Program.cs new file mode 100644 index 0000000..357ddf2 --- /dev/null +++ b/MyNUnit/MyNUnit/Program.cs @@ -0,0 +1,17 @@ +using MyNUnit; + +if (args.Length == 0) +{ + Console.WriteLine("Укажите путь"); + return; +} + +string path = args[0]; +if (!Directory.Exists(path)) +{ + Console.WriteLine($"Папка {path} не найдена"); + return; +} + +var runner = new TestRunner(); +runner.RunTest(path); \ No newline at end of file diff --git a/MyNUnit/MyNUnit/TestResult.cs b/MyNUnit/MyNUnit/TestResult.cs index 3a8ba8c..c271f1e 100644 --- a/MyNUnit/MyNUnit/TestResult.cs +++ b/MyNUnit/MyNUnit/TestResult.cs @@ -9,4 +9,4 @@ public class TestResult public string IgnoreReason { get; set; } public string ErrorMessage { get; set; } public TimeSpan TestTime { get; set; } -} +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs index 96c26fc..3280b72 100644 --- a/MyNUnit/MyNUnit/TestRunner.cs +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -1,30 +1,206 @@ -using System; +namespace MyNUnit; + using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using Attributes; -namespace MyNUnit +public class TestRunner { - public class TestRunner + public void RunTest(string path) { - public void RunTest(string path) + var dlls = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories); + var results = new ConcurrentBag(); + + Parallel.ForEach(dlls, (dllPath) => { - var dlls = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories); - var results = new ConcurrentBag(); + try + { + var assembly = Assembly.LoadFrom(dllPath); + var testClasses = assembly.GetTypes().Where(x => x.GetMethods().Any(y => y.GetCustomAttribute() != null)); - Parallel.ForEach(dlls, (dllPath) => + Parallel.ForEach(testClasses, (testClass) => + { + RunTestsInClass(testClass, results); + }); + } + catch (Exception exception) { - try + Console.WriteLine($"{exception.Message}"); + } + }); + + this.Print(results); + + } + + private bool RunStaticMethods(Type type, Type attributeType, out string error) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Where(x => x.GetCustomAttribute(attributeType) != null); + + foreach (var method in methods) + { + try + { + method.Invoke(null, null); + } + catch (Exception exception) + { + error = exception.Message; + return false; + } + } + + error = null; + return true; + } + + private void RunSingleTest(Type testClass, MethodInfo methodInfo, ConcurrentBag results) + { + var attribute = methodInfo.GetCustomAttribute(); + var result = new TestResult + { + ClassName = testClass.Name, + MethodName = methodInfo.Name, + }; + + if (!string.IsNullOrEmpty(attribute.Ignore)) + { + result.IsIgnored = true; + result.IgnoreReason = attribute.Ignore; + result.TestTime = TimeSpan.Zero; + results.Add(result); + return; + } + + var stopwatch = Stopwatch.StartNew(); + object instance = null; + + try + { + instance = Activator.CreateInstance(testClass); + RunBeforeAndAfterMethods(testClass, instance, typeof(BeforeAttribute)); + + try + { + methodInfo.Invoke(instance, null); + if (attribute.Expected != null) { - var assembly = Assembly.LoadFrom(dllPath); - var testClasses = assembly.GetTypes(); + result.IsSuccess = false; + result.ErrorMessage = $"Ожидаемое({attribute.Expected.Name}) исключение не выбросилось"; + } + else + { + result.IsSuccess = true; + } + } + catch (TargetInvocationException targetInvocationException) + { + var ex = targetInvocationException.InnerException; + if (attribute.Expected != null && attribute.Expected.IsInstanceOfType(ex)) + { + result.IsSuccess = true; + } + else + { + result.IsSuccess = false; + result.ErrorMessage = $"{ex.GetType().Name}: {ex.Message}"; } } + RunBeforeAndAfterMethods(testClass, instance, typeof(AfterAttribute)); + } + catch + { + result.IsSuccess = false; + } + finally + { + stopwatch.Stop(); + result.TestTime = stopwatch.Elapsed; + results.Add(result); + } + } + + private void RunBeforeAndAfterMethods(Type type, object instance, Type attributeType) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetCustomAttribute(attributeType) != null); + + foreach (var method in methods) + { + try + { + method.Invoke(instance, null); + } + catch (TargetInvocationException exception) + { + throw; + } + } + } + + private void RunTestsInClass(Type testClass, ConcurrentBag results) + { + if (!RunStaticMethods(testClass, typeof(BeforeClassAttribute), out string beforeClassError)) + { + FailAllTests(testClass, results, $"{beforeClassError}"); + return; + } + + var testMethods = testClass.GetMethods().Where(x => x.GetCustomAttribute() != null); + + foreach (var testMethod in testMethods) + { + RunSingleTest(testClass, testMethod, results); + } + + RunStaticMethods(testClass, typeof(AfterClassAttribute), out _); + } + + private void FailAllTests(Type testClass, ConcurrentBag results, string reason) + { + var methods = testClass.GetMethods().Where(x => x.GetCustomAttribute() != null); + foreach (var method in methods) + { + results.Add(new TestResult + { + ClassName = testClass.Name, + MethodName = method.Name, + IsSuccess = false, + ErrorMessage = reason, + TestTime = TimeSpan.Zero, + }); + } + } + + private void Print(ConcurrentBag results) + { + Console.WriteLine("\nРезультаты тестирования"); + var passed = results.Where(x => x.IsSuccess).OrderBy(x => x.ClassName).ThenBy(x => x.MethodName).ToList(); + var failed = results.Where(x => !x.IsSuccess && !x.IsIgnored).OrderBy(x => x.ClassName).ThenBy(x => x.MethodName).ToList(); + var ignored = results.Where(x => x.IsIgnored).OrderBy(x => x.ClassName).ThenBy(x => x.MethodName).ToList(); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Успешно {passed.Count}"); + foreach (var result in passed) + { + Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F5} мс)"); + } + + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\b Провалено {failed.Count}"); + foreach (var result in failed) + { + Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F5} мс) \n ОШИБКА: {result.ErrorMessage}"); + } + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"Проигнорировано {ignored.Count}"); + foreach (var result in ignored) + { + Console.WriteLine($" Пропущен {result.ClassName}.{result.MethodName} по причине {result.IgnoreReason}"); } + Console.ResetColor(); } -} +} \ No newline at end of file From 95dfeb69334400189dd1a2837e77c0696eadd66a Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Sat, 29 Nov 2025 20:48:18 +0300 Subject: [PATCH 03/12] refactor: correcting warnings --- MyNUnit/Attributes/.editorconfig | 250 +++++++++++++++++++++ MyNUnit/Attributes/AfterAttribute.cs | 13 +- MyNUnit/Attributes/AfterClassAttribute.cs | 13 +- MyNUnit/Attributes/Attributes.csproj | 18 +- MyNUnit/Attributes/BeforeAttribute.cs | 13 +- MyNUnit/Attributes/BeforeClassAttribute.cs | 13 +- MyNUnit/Attributes/TestAttribute.cs | 16 +- MyNUnit/Attributes/stylecop.json | 14 ++ MyNUnit/MyNUnit/.editorconfig | 250 +++++++++++++++++++++ MyNUnit/MyNUnit/MyNUnit.csproj | 25 ++- MyNUnit/MyNUnit/Program.cs | 8 +- MyNUnit/MyNUnit/TestResult.cs | 51 ++++- MyNUnit/MyNUnit/TestRunner.cs | 47 ++-- MyNUnit/MyNUnit/stylecop.json | 8 + 14 files changed, 699 insertions(+), 40 deletions(-) create mode 100644 MyNUnit/Attributes/.editorconfig create mode 100644 MyNUnit/Attributes/stylecop.json create mode 100644 MyNUnit/MyNUnit/.editorconfig create mode 100644 MyNUnit/MyNUnit/stylecop.json diff --git a/MyNUnit/Attributes/.editorconfig b/MyNUnit/Attributes/.editorconfig new file mode 100644 index 0000000..7349b13 --- /dev/null +++ b/MyNUnit/Attributes/.editorconfig @@ -0,0 +1,250 @@ +# Удалите строку ниже, если вы хотите наследовать параметры .editorconfig из каталогов, расположенных выше в иерархии +root = true + +# Файлы C# +[*.cs] + +[*.cs] +dotnet_diagnostic.SA0001.severity = none + +#### Основные параметры EditorConfig #### + +# Отступы и интервалы +indent_size = 4 +indent_style = space +tab_width = 4 + +# Предпочтения для новых строк +end_of_line = crlf +insert_final_newline = false + +#### Действия кода .NET #### + +# Члены типа +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Поиск символов +dotnet_search_reference_assemblies = true + +#### Рекомендации по написанию кода .NET #### + +# Упорядочение Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# Предпочтения для this. и Me. +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Параметры использования ключевых слов языка и типов BCL +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Предпочтения для скобок +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Предпочтения модификатора +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Выражения уровень предпочтения +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Предпочтения для полей +dotnet_style_readonly_field = true + +# Настройки параметров +dotnet_code_quality_unused_parameters = all + +# Параметры подавления +dotnet_remove_unnecessary_suppression_exclusions = none + +# Предпочтения для новых строк +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### Рекомендации по написанию кода C# #### + +# Предпочтения var +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Члены, заданные выражениями +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Настройки соответствия шаблонов +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Настройки проверки на null +csharp_style_conditional_delegate_call = true + +# Предпочтения модификатора +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Предпочтения для блоков кода +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true +csharp_style_prefer_top_level_statements = true + +# Выражения уровень предпочтения +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# предпочтения для директивы using +csharp_using_directive_placement = outside_namespace + +# Предпочтения для новых строк +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### Правила форматирования C# #### + +# Предпочтения для новых строк +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Параметры отступов +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Предпочтения для интервалов +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Параметры переноса +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Стили именования + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/MyNUnit/Attributes/AfterAttribute.cs b/MyNUnit/Attributes/AfterAttribute.cs index 3fa85c0..0f09aad 100644 --- a/MyNUnit/Attributes/AfterAttribute.cs +++ b/MyNUnit/Attributes/AfterAttribute.cs @@ -1,4 +1,13 @@ -namespace Attributes; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace Attributes; + +/// +/// An attribute that marks the method to be executed after each test method. +/// [AttributeUsage(AttributeTargets.Method)] -public class AfterAttribute : Attribute { } +public class AfterAttribute : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/Attributes/AfterClassAttribute.cs b/MyNUnit/Attributes/AfterClassAttribute.cs index 02895c8..50fe5a1 100644 --- a/MyNUnit/Attributes/AfterClassAttribute.cs +++ b/MyNUnit/Attributes/AfterClassAttribute.cs @@ -1,4 +1,13 @@ -namespace Attributes; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace Attributes; + +/// +/// An attribute that marks the method to be executed once after completing all tests in the class. +/// [AttributeUsage(AttributeTargets.Method)] -public class AfterClassAttribute : Attribute { } +public class AfterClassAttribute : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/Attributes/Attributes.csproj b/MyNUnit/Attributes/Attributes.csproj index 90dd746..0da7f57 100644 --- a/MyNUnit/Attributes/Attributes.csproj +++ b/MyNUnit/Attributes/Attributes.csproj @@ -1,12 +1,20 @@  - Exe + Library net9.0 enable enable + + + + + + + + all @@ -14,4 +22,12 @@ + + + + + + + + diff --git a/MyNUnit/Attributes/BeforeAttribute.cs b/MyNUnit/Attributes/BeforeAttribute.cs index c295ac4..bfaf24b 100644 --- a/MyNUnit/Attributes/BeforeAttribute.cs +++ b/MyNUnit/Attributes/BeforeAttribute.cs @@ -1,4 +1,13 @@ -namespace Attributes; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace Attributes; + +/// +/// An attribute that marks the method to be executed before each test method. +/// [AttributeUsage(AttributeTargets.Method)] -public class BeforeAttribute : Attribute { } \ No newline at end of file +public class BeforeAttribute : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/Attributes/BeforeClassAttribute.cs b/MyNUnit/Attributes/BeforeClassAttribute.cs index fde0d0b..210e2e8 100644 --- a/MyNUnit/Attributes/BeforeClassAttribute.cs +++ b/MyNUnit/Attributes/BeforeClassAttribute.cs @@ -1,4 +1,13 @@ -namespace Attributes; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace Attributes; + +/// +/// An attribute that marks the method that must be executed once before starting all tests in the class. +/// [AttributeUsage(AttributeTargets.Method)] -public class BeforeClassAttribute : Attribute { } +public class BeforeClassAttribute : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/Attributes/TestAttribute.cs b/MyNUnit/Attributes/TestAttribute.cs index e80c5dc..d18a25c 100644 --- a/MyNUnit/Attributes/TestAttribute.cs +++ b/MyNUnit/Attributes/TestAttribute.cs @@ -1,8 +1,22 @@ -namespace Attributes; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace Attributes; + +/// +/// An attribute that marks a method as a test method. +/// [AttributeUsage(AttributeTargets.Method)] public class TestAttribute : Attribute { + /// + /// Gets or sets expected exception. + /// public Type? Expected { get; set; } + + /// + /// Gets or sets the reason for ignoring. + /// public string? Ignore { get; set; } } \ No newline at end of file diff --git a/MyNUnit/Attributes/stylecop.json b/MyNUnit/Attributes/stylecop.json new file mode 100644 index 0000000..f764b37 --- /dev/null +++ b/MyNUnit/Attributes/stylecop.json @@ -0,0 +1,14 @@ +{ + // ACTION REQUIRED: This file was automatically added to your project, but it + // will not take effect until additional steps are taken to enable it. See the + // following page for additional information: + // + // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md + + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Kalinin Andrew" + } + } +} diff --git a/MyNUnit/MyNUnit/.editorconfig b/MyNUnit/MyNUnit/.editorconfig new file mode 100644 index 0000000..7349b13 --- /dev/null +++ b/MyNUnit/MyNUnit/.editorconfig @@ -0,0 +1,250 @@ +# Удалите строку ниже, если вы хотите наследовать параметры .editorconfig из каталогов, расположенных выше в иерархии +root = true + +# Файлы C# +[*.cs] + +[*.cs] +dotnet_diagnostic.SA0001.severity = none + +#### Основные параметры EditorConfig #### + +# Отступы и интервалы +indent_size = 4 +indent_style = space +tab_width = 4 + +# Предпочтения для новых строк +end_of_line = crlf +insert_final_newline = false + +#### Действия кода .NET #### + +# Члены типа +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Поиск символов +dotnet_search_reference_assemblies = true + +#### Рекомендации по написанию кода .NET #### + +# Упорядочение Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# Предпочтения для this. и Me. +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Параметры использования ключевых слов языка и типов BCL +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Предпочтения для скобок +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Предпочтения модификатора +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Выражения уровень предпочтения +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Предпочтения для полей +dotnet_style_readonly_field = true + +# Настройки параметров +dotnet_code_quality_unused_parameters = all + +# Параметры подавления +dotnet_remove_unnecessary_suppression_exclusions = none + +# Предпочтения для новых строк +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### Рекомендации по написанию кода C# #### + +# Предпочтения var +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Члены, заданные выражениями +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Настройки соответствия шаблонов +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Настройки проверки на null +csharp_style_conditional_delegate_call = true + +# Предпочтения модификатора +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Предпочтения для блоков кода +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true +csharp_style_prefer_top_level_statements = true + +# Выражения уровень предпочтения +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# предпочтения для директивы using +csharp_using_directive_placement = outside_namespace + +# Предпочтения для новых строк +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### Правила форматирования C# #### + +# Предпочтения для новых строк +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Параметры отступов +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Предпочтения для интервалов +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Параметры переноса +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Стили именования + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/MyNUnit/MyNUnit/MyNUnit.csproj b/MyNUnit/MyNUnit/MyNUnit.csproj index 1dfa517..4219ae0 100644 --- a/MyNUnit/MyNUnit/MyNUnit.csproj +++ b/MyNUnit/MyNUnit/MyNUnit.csproj @@ -7,6 +7,14 @@ enable + + + + + + + + all @@ -14,8 +22,21 @@ - + + + + + + - + + + + + + + + + diff --git a/MyNUnit/MyNUnit/Program.cs b/MyNUnit/MyNUnit/Program.cs index 357ddf2..9816a32 100644 --- a/MyNUnit/MyNUnit/Program.cs +++ b/MyNUnit/MyNUnit/Program.cs @@ -1,4 +1,8 @@ -using MyNUnit; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +using MyNUnit; if (args.Length == 0) { @@ -6,7 +10,7 @@ return; } -string path = args[0]; +string path = args[0]; if (!Directory.Exists(path)) { Console.WriteLine($"Папка {path} не найдена"); diff --git a/MyNUnit/MyNUnit/TestResult.cs b/MyNUnit/MyNUnit/TestResult.cs index c271f1e..e662979 100644 --- a/MyNUnit/MyNUnit/TestResult.cs +++ b/MyNUnit/MyNUnit/TestResult.cs @@ -1,12 +1,47 @@ -namespace MyNUnit; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace MyNUnit; + +/// +/// Represents a data structure for storing the complete result of a single test method. +/// public class TestResult { - public string ClassName { get; set; } - public string MethodName { get; set; } - public bool IsSuccess { get; set; } - public bool IsIgnored { get; set; } - public string IgnoreReason { get; set; } - public string ErrorMessage { get; set; } - public TimeSpan TestTime { get; set; } + /// + /// Gets or sets the name of the test class containing the test method. + /// + public string? ClassName { get; set; } + + /// + /// Gets or sets the name of the test method itself. + /// + public string? MethodName { get; set; } + + /// + /// Gets or sets a value indicating whether gets or sets a flag indicating whether the test passed successfully (true). + /// If the test failed or was ignored, this value is false. + /// + public bool IsSuccess { get; set; } + + /// + /// Gets or sets a value indicating whether gets or sets a flag indicating whether the test was ignored. + /// + public bool IsIgnored { get; set; } + + /// + /// Gets or sets the reason why the test was ignored. + /// + public string? IgnoreReason { get; set; } + + /// + /// Gets or sets the error message or exception details if the test failed. + /// + public string? ErrorMessage { get; set; } + + /// + /// Gets or sets the time taken to execute the test. + /// + public TimeSpan TestTime { get; set; } } \ No newline at end of file diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs index 3280b72..807eb62 100644 --- a/MyNUnit/MyNUnit/TestRunner.cs +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -1,12 +1,23 @@ -namespace MyNUnit; +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// +namespace MyNUnit; + +using Attributes; using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; -using Attributes; +/// +/// The main class responsible for detecting, executing, and delivering tests. +/// public class TestRunner { + /// + /// Scans the specified path for the DLL, downloads the assemblies, and runs all the detected tests. + /// + /// The path to the directory to search for assemblies. public void RunTest(string path) { var dlls = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories); @@ -21,7 +32,7 @@ public void RunTest(string path) Parallel.ForEach(testClasses, (testClass) => { - RunTestsInClass(testClass, results); + this.RunTestsInClass(testClass, results); }); } catch (Exception exception) @@ -31,10 +42,9 @@ public void RunTest(string path) }); this.Print(results); - } - private bool RunStaticMethods(Type type, Type attributeType, out string error) + private bool RunStaticMethods(Type type, Type attributeType, out string? error) { var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Where(x => x.GetCustomAttribute(attributeType) != null); @@ -57,7 +67,7 @@ private bool RunStaticMethods(Type type, Type attributeType, out string error) private void RunSingleTest(Type testClass, MethodInfo methodInfo, ConcurrentBag results) { - var attribute = methodInfo.GetCustomAttribute(); + var attribute = methodInfo.GetCustomAttribute()!; var result = new TestResult { ClassName = testClass.Name, @@ -74,12 +84,12 @@ private void RunSingleTest(Type testClass, MethodInfo methodInfo, ConcurrentBag< } var stopwatch = Stopwatch.StartNew(); - object instance = null; + object? instance = null; try { instance = Activator.CreateInstance(testClass); - RunBeforeAndAfterMethods(testClass, instance, typeof(BeforeAttribute)); + this.RunBeforeAndAfterMethods(testClass, instance!, typeof(BeforeAttribute)); try { @@ -97,26 +107,27 @@ private void RunSingleTest(Type testClass, MethodInfo methodInfo, ConcurrentBag< catch (TargetInvocationException targetInvocationException) { var ex = targetInvocationException.InnerException; - if (attribute.Expected != null && attribute.Expected.IsInstanceOfType(ex)) + if (attribute.Expected != null && attribute.Expected.IsInstanceOfType(ex) && ex != null) { result.IsSuccess = true; } else { result.IsSuccess = false; - result.ErrorMessage = $"{ex.GetType().Name}: {ex.Message}"; + result.ErrorMessage = $"{ex?.GetType().Name ?? "Unknown exception"}: {ex?.Message ?? "No details"}"; } } - RunBeforeAndAfterMethods(testClass, instance, typeof(AfterAttribute)); + this.RunBeforeAndAfterMethods(testClass, instance!, typeof(AfterAttribute)); } - catch + catch (Exception ex) { result.IsSuccess = false; + result.ErrorMessage = result.ErrorMessage ?? $" {ex.Message}"; } finally { - stopwatch.Stop(); + stopwatch.Stop(); result.TestTime = stopwatch.Elapsed; results.Add(result); } @@ -132,7 +143,7 @@ private void RunBeforeAndAfterMethods(Type type, object instance, Type attribute { method.Invoke(instance, null); } - catch (TargetInvocationException exception) + catch (TargetInvocationException) { throw; } @@ -141,9 +152,9 @@ private void RunBeforeAndAfterMethods(Type type, object instance, Type attribute private void RunTestsInClass(Type testClass, ConcurrentBag results) { - if (!RunStaticMethods(testClass, typeof(BeforeClassAttribute), out string beforeClassError)) + if (!this.RunStaticMethods(testClass, typeof(BeforeClassAttribute), out string? beforeClassError)) { - FailAllTests(testClass, results, $"{beforeClassError}"); + this.FailAllTests(testClass, results, $"{beforeClassError}"); return; } @@ -151,10 +162,10 @@ private void RunTestsInClass(Type testClass, ConcurrentBag results) foreach (var testMethod in testMethods) { - RunSingleTest(testClass, testMethod, results); + this.RunSingleTest(testClass, testMethod, results); } - RunStaticMethods(testClass, typeof(AfterClassAttribute), out _); + this.RunStaticMethods(testClass, typeof(AfterClassAttribute), out _); } private void FailAllTests(Type testClass, ConcurrentBag results, string reason) diff --git a/MyNUnit/MyNUnit/stylecop.json b/MyNUnit/MyNUnit/stylecop.json new file mode 100644 index 0000000..c7ed419 --- /dev/null +++ b/MyNUnit/MyNUnit/stylecop.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Kalinin Andrew" + } + } +} From 64079cfde6e4db60ffbf6584e130456571cd6f99 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Sat, 29 Nov 2025 21:10:04 +0300 Subject: [PATCH 04/12] refactor: RunTest now returns a list of results --- MyNUnit/MyNUnit/TestRunner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs index 807eb62..ac5a5f0 100644 --- a/MyNUnit/MyNUnit/TestRunner.cs +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -18,7 +18,8 @@ public class TestRunner /// Scans the specified path for the DLL, downloads the assemblies, and runs all the detected tests. /// /// The path to the directory to search for assemblies. - public void RunTest(string path) + /// List of results. + public List RunTest(string path) { var dlls = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories); var results = new ConcurrentBag(); @@ -42,6 +43,7 @@ public void RunTest(string path) }); this.Print(results); + return results.ToList(); } private bool RunStaticMethods(Type type, Type attributeType, out string? error) From d8a70fda6d5275ffa9cbf57c2d77a987ed5955ba Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Sat, 29 Nov 2025 21:11:01 +0300 Subject: [PATCH 05/12] add: test project --- MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj | 23 ++++++++++++++++++++++ MyNUnit/MyNUnit.Tests/UnitTest1.cs | 11 +++++++++++ MyNUnit/MyNUnit.slnx | 1 + 3 files changed, 35 insertions(+) create mode 100644 MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj create mode 100644 MyNUnit/MyNUnit.Tests/UnitTest1.cs diff --git a/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj b/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj new file mode 100644 index 0000000..2055aa0 --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj @@ -0,0 +1,23 @@ + + + + net9.0 + latest + enable + enable + false + + + + + + + + + + + + + + + diff --git a/MyNUnit/MyNUnit.Tests/UnitTest1.cs b/MyNUnit/MyNUnit.Tests/UnitTest1.cs new file mode 100644 index 0000000..3c9fbe5 --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/UnitTest1.cs @@ -0,0 +1,11 @@ +namespace MyNUnit.Tests +{ + public class Tests + { + [Test] + public void Test1() + { + Assert.Pass(); + } + } +} diff --git a/MyNUnit/MyNUnit.slnx b/MyNUnit/MyNUnit.slnx index 2c85c92..ef1c5e5 100644 --- a/MyNUnit/MyNUnit.slnx +++ b/MyNUnit/MyNUnit.slnx @@ -1,4 +1,5 @@ + From 595628c54b714c721edd5ebd3fc16c8cd80ececf Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Sun, 30 Nov 2025 01:30:07 +0300 Subject: [PATCH 06/12] test: getting started with mock classes --- MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj | 5 +++ MyNUnit/MyNUnit.Tests/UnitTest1.cs | 49 ++++++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj b/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj index 2055aa0..6109495 100644 --- a/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj +++ b/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj @@ -16,6 +16,11 @@ + + + + + diff --git a/MyNUnit/MyNUnit.Tests/UnitTest1.cs b/MyNUnit/MyNUnit.Tests/UnitTest1.cs index 3c9fbe5..c660365 100644 --- a/MyNUnit/MyNUnit.Tests/UnitTest1.cs +++ b/MyNUnit/MyNUnit.Tests/UnitTest1.cs @@ -1,11 +1,46 @@ -namespace MyNUnit.Tests +namespace MyNUnit.Tests; + +using MyTestAttribute = Attributes.TestAttribute; +using MyBeforeAttribute = Attributes.BeforeAttribute; +using MyAfterAttribute = Attributes.AfterAttribute; +using MyBeforeClassAttribute = Attributes.BeforeClassAttribute; +using MyAfterClassAttribute = Attributes.AfterClassAttribute; + +public class TestsForTest { - public class Tests + [MyTest] + public void PassingTest() + { + } + + [MyTest] + public void FailingTest() + { + throw new Exception("aaa"); + } + + [MyTest(Ignore = "qqq")] + public void IgnoredTest() { - [Test] - public void Test1() - { - Assert.Pass(); - } } } + +public class ExceptionTest +{ + [MyTest(Expected = typeof(ArgumentException))] + public void ThrowsExpectedException() + { + throw new ArgumentException(); + } + + [MyTest(Expected = typeof(ArithmeticException))] + public void ThrowsDifferentException() + { + throw new ArgumentNullException(); + } + + [MyTest(Expected = typeof(ArithmeticException))] + public void DoesntThrowException() + { + } +} \ No newline at end of file From 1fe71c4abed9f54d159d48b6ebc7629d2d729894 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Mon, 1 Dec 2025 23:26:58 +0300 Subject: [PATCH 07/12] refactor: test execution time is now rounded to hundredths --- MyNUnit/MyNUnit/TestRunner.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs index ac5a5f0..eeeaa9b 100644 --- a/MyNUnit/MyNUnit/TestRunner.cs +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -197,21 +197,21 @@ private void Print(ConcurrentBag results) Console.WriteLine($"Успешно {passed.Count}"); foreach (var result in passed) { - Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F5} мс)"); + Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F2} мс)"); } Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\b Провалено {failed.Count}"); foreach (var result in failed) { - Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F5} мс) \n ОШИБКА: {result.ErrorMessage}"); + Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F2} мс) \n ОШИБКА: {result.ErrorMessage}"); } Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Проигнорировано {ignored.Count}"); foreach (var result in ignored) { - Console.WriteLine($" Пропущен {result.ClassName}.{result.MethodName} по причине {result.IgnoreReason}"); + Console.WriteLine($" Пропущен {result.ClassName}.{result.MethodName} по причине {result.IgnoreReason} ({result.TestTime.TotalMilliseconds:F2} мс)"); } Console.ResetColor(); From f1b925612b5215ca1e1e458545a24965f14ae758 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Tue, 2 Dec 2025 00:18:38 +0300 Subject: [PATCH 08/12] refactor: minor changes in the display of test results and change editorconfig file --- MyNUnit/MyNUnit/.editorconfig | 2 -- MyNUnit/MyNUnit/TestRunner.cs | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/MyNUnit/MyNUnit/.editorconfig b/MyNUnit/MyNUnit/.editorconfig index 7349b13..81efc3c 100644 --- a/MyNUnit/MyNUnit/.editorconfig +++ b/MyNUnit/MyNUnit/.editorconfig @@ -2,8 +2,6 @@ root = true # Файлы C# -[*.cs] - [*.cs] dotnet_diagnostic.SA0001.severity = none diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs index eeeaa9b..dc96590 100644 --- a/MyNUnit/MyNUnit/TestRunner.cs +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -197,21 +197,21 @@ private void Print(ConcurrentBag results) Console.WriteLine($"Успешно {passed.Count}"); foreach (var result in passed) { - Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F2} мс)"); + Console.WriteLine($"{result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F2} мс)"); } Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"\b Провалено {failed.Count}"); + Console.WriteLine($"\nПровалено {failed.Count}"); foreach (var result in failed) { - Console.WriteLine($" {result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F2} мс) \n ОШИБКА: {result.ErrorMessage}"); + Console.WriteLine($"{result.ClassName}.{result.MethodName} ({result.TestTime.TotalMilliseconds:F2} мс) \nОШИБКА: {result.ErrorMessage}\n"); } Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Проигнорировано {ignored.Count}"); foreach (var result in ignored) { - Console.WriteLine($" Пропущен {result.ClassName}.{result.MethodName} по причине {result.IgnoreReason} ({result.TestTime.TotalMilliseconds:F2} мс)"); + Console.WriteLine($"Пропущен {result.ClassName}.{result.MethodName} по причине {result.IgnoreReason} ({result.TestTime.TotalMilliseconds:F2} мс)"); } Console.ResetColor(); From 21be0c8980433cca36225c82c49050fa1a58f289 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Tue, 2 Dec 2025 00:19:38 +0300 Subject: [PATCH 09/12] minor changes in editorconfig and stylecop.json --- MyNUnit/Attributes/.editorconfig | 1 - MyNUnit/Attributes/stylecop.json | 6 ------ 2 files changed, 7 deletions(-) diff --git a/MyNUnit/Attributes/.editorconfig b/MyNUnit/Attributes/.editorconfig index 7349b13..3582506 100644 --- a/MyNUnit/Attributes/.editorconfig +++ b/MyNUnit/Attributes/.editorconfig @@ -4,7 +4,6 @@ root = true # Файлы C# [*.cs] -[*.cs] dotnet_diagnostic.SA0001.severity = none #### Основные параметры EditorConfig #### diff --git a/MyNUnit/Attributes/stylecop.json b/MyNUnit/Attributes/stylecop.json index f764b37..c7ed419 100644 --- a/MyNUnit/Attributes/stylecop.json +++ b/MyNUnit/Attributes/stylecop.json @@ -1,10 +1,4 @@ { - // ACTION REQUIRED: This file was automatically added to your project, but it - // will not take effect until additional steps are taken to enable it. See the - // following page for additional information: - // - // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "documentationRules": { From e6256094f32da37b36fcd02f769c3c7260889229 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Tue, 2 Dec 2025 00:20:45 +0300 Subject: [PATCH 10/12] test: add full test`s files --- MyNUnit/MyNUnit.Tests/.editorconfig | 257 ++++++++++++++++++ ...UnitTest1.cs => ExpectedExceptionClass.cs} | 27 +- MyNUnit/MyNUnit.Tests/LifecycleHookClass.cs | 36 +++ MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj | 16 ++ .../MyNUnit.Tests/PassFailIgnoreScenarios.cs | 23 ++ MyNUnit/MyNUnit.Tests/TestsForMyNUnit.cs | 75 +++++ MyNUnit/MyNUnit.Tests/stylecop.json | 8 + 7 files changed, 419 insertions(+), 23 deletions(-) create mode 100644 MyNUnit/MyNUnit.Tests/.editorconfig rename MyNUnit/MyNUnit.Tests/{UnitTest1.cs => ExpectedExceptionClass.cs} (51%) create mode 100644 MyNUnit/MyNUnit.Tests/LifecycleHookClass.cs create mode 100644 MyNUnit/MyNUnit.Tests/PassFailIgnoreScenarios.cs create mode 100644 MyNUnit/MyNUnit.Tests/TestsForMyNUnit.cs create mode 100644 MyNUnit/MyNUnit.Tests/stylecop.json diff --git a/MyNUnit/MyNUnit.Tests/.editorconfig b/MyNUnit/MyNUnit.Tests/.editorconfig new file mode 100644 index 0000000..c3685c2 --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/.editorconfig @@ -0,0 +1,257 @@ +# Удалите строку ниже, если вы хотите наследовать параметры .editorconfig из каталогов, расположенных выше в иерархии +root = true + +# Файлы C# +[*.cs] + +# Default severity for analyzer diagnostics with category 'StyleCop.CSharp.DocumentationRules' +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none + +dotnet_diagnostic.SA0001.severity = none + +dotnet_diagnostic.SA1407.severity = none + +#### Основные параметры EditorConfig #### + +# Отступы и интервалы +indent_size = 4 +indent_style = space +tab_width = 4 + +# Предпочтения для новых строк +end_of_line = crlf +insert_final_newline = false + +#### Действия кода .NET #### + +# Члены типа +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Поиск символов +dotnet_search_reference_assemblies = true + +#### Рекомендации по написанию кода .NET #### + +# Упорядочение Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# Предпочтения для this. и Me. +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Параметры использования ключевых слов языка и типов BCL +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Предпочтения для скобок +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Предпочтения модификатора +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Выражения уровень предпочтения +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Предпочтения для полей +dotnet_style_readonly_field = true + +# Настройки параметров +dotnet_code_quality_unused_parameters = all + +# Параметры подавления +dotnet_remove_unnecessary_suppression_exclusions = none + +# Предпочтения для новых строк +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### Рекомендации по написанию кода C# #### + +# Предпочтения var +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Члены, заданные выражениями +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Настройки соответствия шаблонов +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Настройки проверки на null +csharp_style_conditional_delegate_call = true + +# Предпочтения модификатора +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Предпочтения для блоков кода +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true +csharp_style_prefer_top_level_statements = true + +# Выражения уровень предпочтения +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# предпочтения для директивы using +csharp_using_directive_placement = outside_namespace + +# Предпочтения для новых строк +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### Правила форматирования C# #### + +# Предпочтения для новых строк +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Параметры отступов +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Предпочтения для интервалов +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Параметры переноса +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Стили именования + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +# SA1401: Fields should be private +dotnet_diagnostic.SA1401.severity = none diff --git a/MyNUnit/MyNUnit.Tests/UnitTest1.cs b/MyNUnit/MyNUnit.Tests/ExpectedExceptionClass.cs similarity index 51% rename from MyNUnit/MyNUnit.Tests/UnitTest1.cs rename to MyNUnit/MyNUnit.Tests/ExpectedExceptionClass.cs index c660365..adf5dfd 100644 --- a/MyNUnit/MyNUnit.Tests/UnitTest1.cs +++ b/MyNUnit/MyNUnit.Tests/ExpectedExceptionClass.cs @@ -1,29 +1,10 @@ namespace MyNUnit.Tests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; using MyTestAttribute = Attributes.TestAttribute; -using MyBeforeAttribute = Attributes.BeforeAttribute; -using MyAfterAttribute = Attributes.AfterAttribute; -using MyBeforeClassAttribute = Attributes.BeforeClassAttribute; -using MyAfterClassAttribute = Attributes.AfterClassAttribute; - -public class TestsForTest -{ - [MyTest] - public void PassingTest() - { - } - - [MyTest] - public void FailingTest() - { - throw new Exception("aaa"); - } - - [MyTest(Ignore = "qqq")] - public void IgnoredTest() - { - } -} public class ExceptionTest { diff --git a/MyNUnit/MyNUnit.Tests/LifecycleHookClass.cs b/MyNUnit/MyNUnit.Tests/LifecycleHookClass.cs new file mode 100644 index 0000000..7cc6e40 --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/LifecycleHookClass.cs @@ -0,0 +1,36 @@ +namespace MyNUnit.Tests; + +using Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MyAfterAttribute = Attributes.AfterAttribute; +using MyAfterClassAttribute = Attributes.AfterClassAttribute; +using MyBeforeAttribute = Attributes.BeforeAttribute; +using MyBeforeClassAttribute = Attributes.BeforeClassAttribute; +using MyTestAttribute = Attributes.TestAttribute; + +public class LifecycleTest +{ + public static List Loger = new List(); + + [MyBeforeClass] + public static void BeforeClass() => Loger.Add("BeforeClass"); + + [MyAfterClass] + public static void AfterClass() => Loger.Add("AfterClass"); + + [MyBefore] + public static void Before() => Loger.Add("Before"); + + [MyAfter] + public static void After() => Loger.Add("After"); + + [MyTest] + public static void FirstTest() => Loger.Add("Test1"); + + [MyTest] + public static void SecondTest() => Loger.Add("Test2"); +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj b/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj index 6109495..13f2bbd 100644 --- a/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj +++ b/MyNUnit/MyNUnit.Tests/MyNUnit.Tests.csproj @@ -14,6 +14,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -21,8 +25,20 @@ + + + + + + + + + + + + diff --git a/MyNUnit/MyNUnit.Tests/PassFailIgnoreScenarios.cs b/MyNUnit/MyNUnit.Tests/PassFailIgnoreScenarios.cs new file mode 100644 index 0000000..2a00a9c --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/PassFailIgnoreScenarios.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace MyNUnit.Tests; + +using MyTestAttribute = Attributes.TestAttribute; + +public class PassFailIgnoreScenarios +{ + [MyTest] + public void PassingTest() + { + } + + [MyTest] + public void FailingTest() => throw new Exception("aaa"); + + [MyTest(Ignore = "qqq")] + public void IgnoredTest() + { + } +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit.Tests/TestsForMyNUnit.cs b/MyNUnit/MyNUnit.Tests/TestsForMyNUnit.cs new file mode 100644 index 0000000..5895709 --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/TestsForMyNUnit.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace MyNUnit.Tests; + +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using static MyNUnit.Tests.LifecycleTest; + +public class TestsForMyNUnit +{ + private TestRunner? runner; + private string? path; + + [OneTimeSetUp] + public void Setup() + { + this.runner = new TestRunner(); + this.path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + + [SetUp] + public void ResetStat() + { + Loger.Clear(); + } + + [Test] + public void BasicTests_ShouldIdentifySpecificity() + { + var result = this.runner!.RunTest(this.path!); + var classResult = result.Where(x => x.ClassName == nameof(PassFailIgnoreScenarios)); + Assert.That(classResult.Count(), Is.EqualTo(3)); + + var passing = classResult.First(x => x.MethodName == nameof(PassFailIgnoreScenarios.PassingTest)); + Assert.That(passing.IsSuccess, Is.True); + + var failed = classResult.First(x => x.MethodName == nameof(PassFailIgnoreScenarios.FailingTest)); + Assert.That(failed.IsSuccess, Is.False); + + var ignored = classResult.First(x => x.MethodName == nameof(PassFailIgnoreScenarios.IgnoredTest)); + Assert.That(ignored.IsIgnored, Is.True); + } + + [Test] + public void ExceptionTests_ShouldWasCorrectVersus() + { + var result = this.runner!.RunTest(this.path!); + var classResult = result.Where(x => x.ClassName == nameof(ExceptionTest)); + Assert.That(classResult.Count(), Is.EqualTo(3)); + + var success = classResult.First(x => x.MethodName == nameof(ExceptionTest.ThrowsExpectedException)); + Assert.That(success.IsSuccess, Is.True); + + var wrong = classResult.First(x => x.MethodName == nameof(ExceptionTest.ThrowsDifferentException)); + Assert.That(wrong.IsSuccess, Is.False); + + var noException = classResult.First(x => x.MethodName == nameof(ExceptionTest.DoesntThrowException)); + Assert.That(noException.IsSuccess, Is.False); + } + + [Test] + public void LifecycleTests_ShouldRunAllMethodsInCorrectCount() + { + this.runner!.RunTest(this.path!); + + var expectedLog = new List { "BeforeClass", "Before", "Test1", "After", "AfterClass", "BeforeClass", "Before", "Test2", "After", "AfterClass" }; + } +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit.Tests/stylecop.json b/MyNUnit/MyNUnit.Tests/stylecop.json new file mode 100644 index 0000000..c7ed419 --- /dev/null +++ b/MyNUnit/MyNUnit.Tests/stylecop.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Kalinin Andrew" + } + } +} From d159e7164157f7f3f357707146814ce68446ae25 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Sun, 28 Dec 2025 02:22:36 +0300 Subject: [PATCH 11/12] feat: add AssemblyName to TestResult --- MyNUnit/MyNUnit/TestResult.cs | 5 +++++ MyNUnit/MyNUnit/TestRunner.cs | 15 +++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/MyNUnit/MyNUnit/TestResult.cs b/MyNUnit/MyNUnit/TestResult.cs index e662979..6672e18 100644 --- a/MyNUnit/MyNUnit/TestResult.cs +++ b/MyNUnit/MyNUnit/TestResult.cs @@ -9,6 +9,11 @@ namespace MyNUnit; /// public class TestResult { + /// + /// Gets or sets the name of the assembly containing the test. + /// + public string? AssemblyName { get; set; } + /// /// Gets or sets the name of the test class containing the test method. /// diff --git a/MyNUnit/MyNUnit/TestRunner.cs b/MyNUnit/MyNUnit/TestRunner.cs index dc96590..58b49b2 100644 --- a/MyNUnit/MyNUnit/TestRunner.cs +++ b/MyNUnit/MyNUnit/TestRunner.cs @@ -29,11 +29,12 @@ public List RunTest(string path) try { var assembly = Assembly.LoadFrom(dllPath); + var assemblyName = Path.GetFileName(dllPath); var testClasses = assembly.GetTypes().Where(x => x.GetMethods().Any(y => y.GetCustomAttribute() != null)); Parallel.ForEach(testClasses, (testClass) => { - this.RunTestsInClass(testClass, results); + this.RunTestsInClass(testClass, results, assemblyName); }); } catch (Exception exception) @@ -67,11 +68,12 @@ private bool RunStaticMethods(Type type, Type attributeType, out string? error) return true; } - private void RunSingleTest(Type testClass, MethodInfo methodInfo, ConcurrentBag results) + private void RunSingleTest(Type testClass, MethodInfo methodInfo, ConcurrentBag results, string assemblyName) { var attribute = methodInfo.GetCustomAttribute()!; var result = new TestResult { + AssemblyName = assemblyName, ClassName = testClass.Name, MethodName = methodInfo.Name, }; @@ -152,11 +154,11 @@ private void RunBeforeAndAfterMethods(Type type, object instance, Type attribute } } - private void RunTestsInClass(Type testClass, ConcurrentBag results) + private void RunTestsInClass(Type testClass, ConcurrentBag results, string assemblyName) { if (!this.RunStaticMethods(testClass, typeof(BeforeClassAttribute), out string? beforeClassError)) { - this.FailAllTests(testClass, results, $"{beforeClassError}"); + this.FailAllTests(testClass, results, $"{beforeClassError}", assemblyName); return; } @@ -164,19 +166,20 @@ private void RunTestsInClass(Type testClass, ConcurrentBag results) foreach (var testMethod in testMethods) { - this.RunSingleTest(testClass, testMethod, results); + this.RunSingleTest(testClass, testMethod, results, assemblyName); } this.RunStaticMethods(testClass, typeof(AfterClassAttribute), out _); } - private void FailAllTests(Type testClass, ConcurrentBag results, string reason) + private void FailAllTests(Type testClass, ConcurrentBag results, string reason, string assemblyName) { var methods = testClass.GetMethods().Where(x => x.GetCustomAttribute() != null); foreach (var method in methods) { results.Add(new TestResult { + AssemblyName = assemblyName, ClassName = testClass.Name, MethodName = method.Name, IsSuccess = false, From 89a33859439ab94209f695e59514126cbf49001e Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Sun, 28 Dec 2025 07:38:46 +0300 Subject: [PATCH 12/12] add: Assert.cs --- MyNUnit/MyNUnit/Assert.cs | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 MyNUnit/MyNUnit/Assert.cs diff --git a/MyNUnit/MyNUnit/Assert.cs b/MyNUnit/MyNUnit/Assert.cs new file mode 100644 index 0000000..4ca2667 --- /dev/null +++ b/MyNUnit/MyNUnit/Assert.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace MyNUnit; + +using System.Runtime.CompilerServices; + +/// +/// Provides a set of static methods for verifying conditions in tests. +/// +public static class Assert +{ + /// + /// Verifies that two values are equal. + /// + /// The type of values to compare. + /// The expected value. + /// The actual value. + /// Thrown when values are not equal. + public static void AreEqual(T expected, T real) + { + if (!Equals(expected, real)) + { + throw new Exception($"Ожидалось: {expected}, получилось {real}"); + } + } + + /// + /// Verifies that a condition is true. + /// + /// The condition to verify. + /// Thrown when condition is false. + public static void IsTrue(bool condition) + { + if (!condition) + { + throw new Exception("Условие должно быть истинным"); + } + } + + /// + /// Verifies that an object is not null. + /// + /// The object to verify. + /// The expression being verified (auto-captured by compiler). + /// Thrown when object is null. + public static void IsNotNull(object? obj, [CallerArgumentExpression(nameof(obj))] string? expression = null) + { + if (obj == null) + { + throw new Exception($"{expression} ожидался не null"); + } + } + + /// + /// Verifies that an object is null. + /// + /// The object to verify. + /// The expression being verified (auto-captured by compiler). + /// Thrown when object is not null. + public static void IsNull(object? obj, [CallerArgumentExpression(nameof(obj))] string? expression = null) + { + if (obj != null) + { + throw new Exception($"{expression} ожидался null"); + } + } +} \ No newline at end of file