Skip to content
Draft
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
12 changes: 12 additions & 0 deletions MyNUnitWeb/MyNUnit/Assert/Assert.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace MyNUnit.Assert;

public static class Assert
{
public static void That(bool condition, string? message = null)
{
if (!condition)
{
throw message is null ? new FailedAssertionException() : new FailedAssertionException(message);
}
}
}
14 changes: 14 additions & 0 deletions MyNUnitWeb/MyNUnit/Assert/FailedAssertionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace MyNUnit.Assert;

public class FailedAssertionException : Exception
{
public FailedAssertionException()
{

}

public FailedAssertionException(string message) : base(message)
{

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

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

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

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

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

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

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

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

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

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class TestAttribute : Attribute
{
public TestAttribute(string? ignoreMessage = null, Type? expected = null)
{
IgnoreMessage = ignoreMessage;
ExpectedException = expected;
}

public TestAttribute(Type expected)
{
IgnoreMessage = null;
ExpectedException = expected;
}

public Type? ExpectedException { get; private set; }

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

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.8.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/AssemblyTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Concurrent;
using System.Reflection;
using MyNUnit.Tests.TestsResult;

namespace MyNUnit.Tests;

public class AssemblyTest
{
private readonly string name;

private readonly List<ClassTest> classTests;

public AssemblyTest(string assemblyPath) : this(Assembly.LoadFrom(Path.GetFileName(assemblyPath)))
{

}

public AssemblyTest(Assembly assembly)
{
name = assembly.GetName().Name!;

classTests = assembly.ExportedTypes
.Where(type => type.IsClass)
.Select(type => new ClassTest(type.Name, type.GetTypeInfo(), name)).ToList();
}

public AssemblyTestResult Run()
{
var result = new ConcurrentBag<ClassTestResult>();

Parallel.ForEach(classTests, test => result.Add(test.Run()));

return new AssemblyTestResult(result.ToList(), name, result.Sum(r => r.TestDuration));
}

public static AssemblyTestResult RunTestByAssembly(Assembly assembly) => new AssemblyTest(assembly).Run();
}
76 changes: 76 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/ClassTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Collections.Concurrent;
using System.Reflection;
using FluentValidation;
using FluentValidation.Results;
using MyNUnit.Attributes;
using MyNUnit.Tests.TestsResult;
using MyNUnit.Validators;

namespace MyNUnit.Tests;

public class ClassTest
{
private readonly string className;

private readonly string assemblyName;

private readonly List<Test> testMethods = new();

private readonly List<MethodInfo> afterClassMethods ;

private readonly List<ValidationFailure> errors = new();

public ClassTest(string className, TypeInfo classInfo, string assemblyName)
{
this.className = className;
this.assemblyName = assemblyName;

var generalValidator = new TestMethodsValidator();
var beforeAndAfterClassValidator = new BeforeAndAfterClassMethodsValidator(generalValidator);

var methods = classInfo.GetMethods().ToList();

List<MethodInfo> ValidateAndGet(IValidator<MethodInfo> validator, Type type)
{
var neededMethods = Utils.GetMethodsWithAttributes(type, methods);
neededMethods.ForEach(method => errors.AddRange(validator.Validate(method).Errors));

return neededMethods;
}

afterClassMethods = ValidateAndGet(beforeAndAfterClassValidator, typeof(AfterClassAttribute));
var beforeMethods = ValidateAndGet(generalValidator, typeof(BeforeAttribute));
var afterMethods = ValidateAndGet(generalValidator, typeof(AfterAttribute));
var tests = ValidateAndGet(generalValidator, typeof(TestAttribute));
var beforeClassMethods = ValidateAndGet(beforeAndAfterClassValidator, typeof(BeforeClassAttribute));

if (errors.Count == 0)
{
Parallel.ForEach(beforeClassMethods, method =>
method.Invoke(null, null));

var classInstance = Activator.CreateInstance(classInfo) ?? throw new ArgumentNullException(nameof(classInfo));

testMethods = tests.Select(method => new Test(classInstance, method, beforeMethods, afterMethods)).ToList();
}
}

public ClassTestResult Run()
{
if (errors.Count != 0)
{
return new ClassTestResult(new List<TestResult>(), className, assemblyName, 0, errors);
}

var testResults = new ConcurrentBag<TestResult>();

Parallel.ForEach(testMethods, testMethod => testResults.Add(testMethod.Run()));

var result = new ClassTestResult(testResults.ToList(), className, assemblyName, testResults.Sum(r => r.TestDuration));

Parallel.ForEach(afterClassMethods, method =>
method.Invoke(null, null));

return result;
}
}
87 changes: 87 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/Test.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Diagnostics;
using System.Reflection;
using MyNUnit.Assert;
using MyNUnit.Attributes;
using MyNUnit.Tests.TestsResult;

namespace MyNUnit.Tests;

public class Test
{
private readonly Type? expectedExceptionType;

private readonly string? ignoreReason;

private readonly object classInstance;

private readonly MethodInfo testMethodInfo;

private readonly List<MethodInfo> beforeTestsMethodInfo;

private readonly List<MethodInfo> afterTestsMethodInfo;

private readonly string testName;

private bool isStarted;

public Test(object classInstance, MethodInfo testMethodInfo, List<MethodInfo> beforeTestsMethodInfo, List<MethodInfo> afterTestsMethodInfo)
{
this.classInstance = classInstance;
this.testMethodInfo = testMethodInfo;
this.beforeTestsMethodInfo = beforeTestsMethodInfo;
this.afterTestsMethodInfo = afterTestsMethodInfo;

testName = testMethodInfo.Name;

var testAttribute = (TestAttribute)testMethodInfo.GetCustomAttribute(typeof(TestAttribute))!;
ignoreReason = testAttribute.IgnoreMessage;
expectedExceptionType = testAttribute.ExpectedException;
}

public TestResult Run()
{
isStarted = true;

if (ignoreReason is not null)
{
return new IgnoredTestResult(testName, ignoreReason);
}

InvokeMethods(beforeTestsMethodInfo);

var stopWatch = new Stopwatch();
stopWatch.Start();

try
{
testMethodInfo.Invoke(classInstance, Array.Empty<object>());

stopWatch.Stop();

var result = new PassedTestResult(testName, stopWatch.ElapsedMilliseconds);

InvokeMethods(afterTestsMethodInfo);

return result;
}
catch (Exception e)
{
stopWatch.Stop();

var exceptionType = e.InnerException?.GetType();

TestResult result = exceptionType == typeof(FailedAssertionException)
? new FailedTestResult(testName, stopWatch.ElapsedMilliseconds, true, AdditionalInfo: e.Message)
: exceptionType == expectedExceptionType
? new PassedTestResult(testName, stopWatch.ElapsedMilliseconds)
: new FailedTestResult(testName, stopWatch.ElapsedMilliseconds, false, expectedExceptionType, exceptionType);

InvokeMethods(afterTestsMethodInfo);

return result;
}
}

private void InvokeMethods(IEnumerable<MethodInfo> methods)
=> Parallel.ForEach(methods, methodInfo => methodInfo.Invoke(classInstance, null));
}
7 changes: 7 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/TestsResult/AssemblyTestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MyNUnit.Tests.TestsResult;

public record AssemblyTestResult(
List<ClassTestResult> ClassTestResults,
string AssemblyName,
long TestDuration
);
11 changes: 11 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/TestsResult/ClassTestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using FluentValidation.Results;

namespace MyNUnit.Tests.TestsResult;

public record ClassTestResult(
List<TestResult> TestResults,
string ClassName,
string AssemblyName,
long TestDuration,
List<ValidationFailure>? MethodsValidationErrors = null
);
10 changes: 10 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/TestsResult/FailedTestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace MyNUnit.Tests.TestsResult;

public record FailedTestResult(
string TestName,
long TestDuration,
bool IsAssertionFailed,
Type? ExpectedException = null,
Type? ActualException = null,
string? AdditionalInfo = null
) : TestResult(TestName, TestDuration, AdditionalInfo);
7 changes: 7 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/TestsResult/IgnoredTestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MyNUnit.Tests.TestsResult;

public record IgnoredTestResult(
string TestName,
string IgnoredReason,
string? AdditionalInfo = null
) : TestResult(TestName, 0, AdditionalInfo);
7 changes: 7 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/TestsResult/PassedTestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MyNUnit.Tests.TestsResult;

public record PassedTestResult(
string TestName,
long TestDuration,
string? AdditionalInfo = default
) : TestResult(TestName, TestDuration, AdditionalInfo);
7 changes: 7 additions & 0 deletions MyNUnitWeb/MyNUnit/Tests/TestsResult/TestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MyNUnit.Tests.TestsResult;

public record TestResult(
string TestName,
long TestDuration,
string? AdditionalInfo = default
);
13 changes: 13 additions & 0 deletions MyNUnitWeb/MyNUnit/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Reflection;
using MyNUnit.Tests.TestsResult;

namespace MyNUnit;

public static class Utils
{
public static List<MethodInfo> GetMethodsWithAttributes(Type attributeType, IEnumerable<MethodInfo> methods)
=> methods.Where(method => Attribute.IsDefined(method, attributeType)).ToList();

public static string GetFailReason(FailedTestResult result)
=> result.IsAssertionFailed ? "Some Assertion Failed" : $"Expected exception was: {result.ExpectedException?.Name}, but actually was {result.ActualException?.Name}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Reflection;
using FluentValidation;

namespace MyNUnit.Validators;

public class BeforeAndAfterClassMethodsValidator : AbstractValidator<MethodInfo>
{
public BeforeAndAfterClassMethodsValidator(TestMethodsValidator testMethodsValidator)
{
RuleFor(tm => tm.IsStatic)
.Equal(true)
.WithMessage(tm => $"Method: '{tm.Name}' should be static");

Include(testMethodsValidator);
}
}
Loading