Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions MyNUnit/MyNUnit.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33403.182
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyNUnit", "MyNUnit\MyNUnit.csproj", "{8A4A83AC-5456-4F54-9183-B053A0C05926}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestMyNUnit", "TestMyNUnit\TestMyNUnit.csproj", "{9508BABD-7DF4-4B36-B442-020EB706C229}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectForTest", "ProjectForTest\ProjectForTest.csproj", "{F9103AED-6C96-471A-9431-4F2E37A33EC9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8A4A83AC-5456-4F54-9183-B053A0C05926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A4A83AC-5456-4F54-9183-B053A0C05926}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A4A83AC-5456-4F54-9183-B053A0C05926}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A4A83AC-5456-4F54-9183-B053A0C05926}.Release|Any CPU.Build.0 = Release|Any CPU
{9508BABD-7DF4-4B36-B442-020EB706C229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9508BABD-7DF4-4B36-B442-020EB706C229}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9508BABD-7DF4-4B36-B442-020EB706C229}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9508BABD-7DF4-4B36-B442-020EB706C229}.Release|Any CPU.Build.0 = Release|Any CPU
{F9103AED-6C96-471A-9431-4F2E37A33EC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9103AED-6C96-471A-9431-4F2E37A33EC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9103AED-6C96-471A-9431-4F2E37A33EC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9103AED-6C96-471A-9431-4F2E37A33EC9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {97E09C33-070C-4D39-BDE1-D7C045292CC6}
EndGlobalSection
EndGlobal
158 changes: 158 additions & 0 deletions MyNUnit/MyNUnit/ApplicationForTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System.Diagnostics;
using System.Reflection;

using MyNUnit.Atributes;

namespace MyNUnit;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace лучше первой строкой файла, сразу после шапки с лицензией


public class ApplicationForTests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надо комментарии

{
public readonly List<ResultsTests> listOfResults = new();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public-всё в .NET именуется с заглавной. Но вообще, плохая идея делать мутабельный ссылочный тип readonly public-полем. Любой извне сможет взять и положить в список какой-то элемент — потому что readonly только ссылка, а не то, на что она указывает.

private readonly object locker = new();

public ApplicationForTests(Assembly assembly)
{
var classes = assembly.GetExportedTypes()
.Where(t => t.IsClass)
.Where(t => t.GetMethods()
.Where(m => m.GetCustomAttributes(true)
.Any(a => a is TestAttribute))
.Any());

Parallel.ForEach(classes, StartTests);
}

private static MethodInfo[]? GetMethodsByAtributeAndClass(Type _class, Type atribute)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attribute пишется с двумя t, прошу прощения за придирку :)

{
return _class.GetMethods()
.Where(m => m.GetCustomAttributes(atribute, false).Length > 0)
.ToArray();
}

private static void WorkWithClassMethods(Type _class, Type atribute)
{
var methods = GetMethodsByAtributeAndClass(_class, atribute);
LaunchMethods(_class, methods);
}

private void WorkWithTestMethods(Type _class)
{
var instance = Activator.CreateInstance(_class);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

var methodsBefore = GetMethodsByAtributeAndClass(_class, typeof(BeforeAttribute));
var methodsAfter = GetMethodsByAtributeAndClass(_class, typeof(AfterAttribute));
var methods = _class.GetMethods();
foreach(var method in methods)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach(var method in methods)
foreach (var method in methods)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тесты внутри класса надо запускать параллельно, а то если у пользователя всего один класс с тысячей методов, всё в одном потоке будет работать, что не очень

{
if (method.IsDefined(typeof(TestAttribute), true))
{
RunMethodsBeforeAndAfter(methodsBefore, instance);
RunMethod(_class, method);
RunMethodsBeforeAndAfter(methodsAfter, instance);
}
}
}
private void StartTests(Type _class)
Comment on lines +53 to +54

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
private void StartTests(Type _class)
}
private void StartTests(Type _class)

{
WorkWithClassMethods(_class, typeof(BeforeClassAttribute));
WorkWithTestMethods(_class);
WorkWithClassMethods(_class, typeof(AfterClassAttribute));
}

private static void LaunchMethods(Type _class, MethodInfo[]? methods)
{
if (_class == null || methods == null)
{
throw new NullReferenceException();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NullReferenceException вообще нельзя бросать, это исключение для .NET-машины. Тут правильно было бы ArgumentNullException (и пользуйтесь его static-методом ThrowIfNull)

}

foreach (var method in methods)
{
if (!method.IsStatic)
{
throw new InvalidOperationException();
}

method.Invoke(null, null);
}
}

private static void RunMethodsBeforeAndAfter(MethodInfo[]? methods, object? instance)
{
if (instance == null || methods == null)
{
throw new NullReferenceException();
}
foreach(var method in methods)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach(var method in methods)
foreach (var method in methods)

{
method.Invoke(instance, null);
}
}

private void RunMethod(object? instance, MethodInfo? method)
{
if (instance == null || method == null || locker == null)
{
throw new InvalidOperationException();
}

var atribute = Attribute.GetCustomAttribute(method, typeof(TestAttribute));

if (atribute == null)
{
throw new InvalidOperationException();
}

var testAttribute = (TestAttribute)atribute;

if (testAttribute.Ignored != null)
{
lock (locker)
{
listOfResults.Add(new ResultsTests(method.Name, 0, ResultsTests.Status.Ignored));
}
return;
}

var stopWatch = new Stopwatch();

try
{
stopWatch.Start();
method.Invoke(instance, null);
stopWatch.Stop();
lock (locker)
{
if (testAttribute != null && testAttribute.Expected != null)
{
listOfResults.Add(new ResultsTests(method.Name, stopWatch.ElapsedMilliseconds, ResultsTests.Status.Failed));
}
else
{
listOfResults.Add(new ResultsTests(method.Name, stopWatch.ElapsedMilliseconds, ResultsTests.Status.Passed));
}
}
}
catch(Exception ex)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
catch(Exception ex)
catch (Exception ex)

{
stopWatch.Stop();
var exceptionType = ex.GetType();

if (testAttribute != null && testAttribute.Expected != null && testAttribute.Expected.IsAssignableFrom(exceptionType) ||
(ex.InnerException != null && testAttribute != null && testAttribute.Expected != null && testAttribute.Expected.IsAssignableFrom(ex.InnerException.GetType())))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сложные условия стоит выносить в отдельный метод

{
lock (locker)
{
listOfResults.Add(new ResultsTests(method.Name, stopWatch.ElapsedMilliseconds, ex, ResultsTests.Status.Passed));
}
}
else
{
lock (locker)
{
listOfResults.Add(new ResultsTests(method.Name, stopWatch.ElapsedMilliseconds, ex, ResultsTests.Status.Failed));
}
}
}
}

}
14 changes: 14 additions & 0 deletions MyNUnit/MyNUnit/Attributes/AfterAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace MyNUnit.Atributes;

[AttributeUsage(AttributeTargets.Method)]
public class AfterAttribute : Attribute
{
public AfterAttribute() { }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это не нужно, пустой конструктор сгенерируется сам, если в классе нет конструкторов



[AfterAttribute]
public void Method()
{
;
}
Comment on lines +9 to +13

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это вдвойне не нужно :)

}
7 changes: 7 additions & 0 deletions MyNUnit/MyNUnit/Attributes/AfterClassAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MyNUnit.Atributes;

[AttributeUsage(AttributeTargets.Method)]
public class AfterClassAttribute : Attribute
{
public AfterClassAttribute() { }
}
14 changes: 14 additions & 0 deletions MyNUnit/MyNUnit/Attributes/BeforeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace MyNUnit.Atributes;

[AttributeUsage(AttributeTargets.Method)]
public class BeforeAttribute : Attribute
{
public BeforeAttribute() { }


[BeforeAttribute]
public void Method()
{
;
}
}
7 changes: 7 additions & 0 deletions MyNUnit/MyNUnit/Attributes/BeforeClassAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MyNUnit.Atributes;

[AttributeUsage(AttributeTargets.Method)]
public class BeforeClassAttribute : Attribute
{
public BeforeClassAttribute() { }
}
27 changes: 27 additions & 0 deletions MyNUnit/MyNUnit/Attributes/TestAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace MyNUnit.Atributes;

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class TestAttribute : Attribute
{
public TestAttribute() { }

public TestAttribute(Type? expected)
{
Expected = expected;
}

public TestAttribute(string ignored)
{
Ignored = ignored;
}

public TestAttribute(Type? expected, string? ignored)
{
Expected = expected;
Ignored = ignored;
}

public Type? Expected { get; set; }

public string? Ignored { get; set; }
}
10 changes: 10 additions & 0 deletions MyNUnit/MyNUnit/MyNUnit.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
36 changes: 36 additions & 0 deletions MyNUnit/MyNUnit/ResultsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using static MyNUnit.ApplicationForTests;

namespace MyNUnit;

public class ResultsTests
{
public enum Status
{
Passed,
Failed,
Ignored
}

public string Name { get; }

public long WorkTime { get; }

public Exception? ReasonFail { get; }

public Status StatusTest { get; }

public ResultsTests(string name, long workTime, Exception reasonFail, Status statusTest)
{
Name = name;
WorkTime = workTime;
ReasonFail = reasonFail;
StatusTest = statusTest;
}

public ResultsTests(string name, long workTime, Status statusTest)
{
Name = name;
WorkTime = workTime;
StatusTest = statusTest;
}
}
62 changes: 62 additions & 0 deletions MyNUnit/ProjectForTest/ClassForTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
namespace ProjectForTest;

using MyNUnit.Atributes;

public class ClassForTests
{
public static int counter = 0;

[TestAttribute(Expected = typeof(IndexOutOfRangeException))]
public void InvalidMethod()
{
throw new NotImplementedException();
}

[TestAttribute(Expected = typeof(FileNotFoundException))]
public int CorrectMethod()
{
throw new FileNotFoundException();
}

[BeforeClassAttribute]
public static void BeforeClass()
{
counter += 1;
}

[AfterClassAttribute]
public static void AfterClass()
{
counter += 1;
}

[AfterAttribute]
public void BeforeMethod()
{
counter += 1;
}

[BeforeAttribute]
public void AfterMethod()
{
counter += 1;
}

[TestAttribute(Ignored = "Ignore")]
public void IgnoreTest()
{
;
}

[TestAttribute(Expected = typeof(InvalidCastException))]
public void OneMoreCorrectMethod()
{
throw new InvalidCastException();
}

[TestAttribute(Expected = typeof(InvalidOperationException))]
public void OneMoreIncorrectMethod()
{
throw new InvalidProgramException();
}
}
Loading