-
Notifications
You must be signed in to change notification settings - Fork 0
N unit #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
N unit #25
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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; | ||||||||||||
|
|
||||||||||||
| public class ApplicationForTests | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Надо комментарии |
||||||||||||
| { | ||||||||||||
| public readonly List<ResultsTests> listOfResults = new(); | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| { | ||||||||||||
| 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(); | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| { | ||||||||||||
| 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) | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| { | ||||||||||||
| 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()))) | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| } | ||||||||||||
| 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() { } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это не нужно, пустой конструктор сгенерируется сам, если в классе нет конструкторов |
||
|
|
||
|
|
||
| [AfterAttribute] | ||
| public void Method() | ||
| { | ||
| ; | ||
| } | ||
|
Comment on lines
+9
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это вдвойне не нужно :) |
||
| } | ||
| 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() { } | ||
| } |
| 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() | ||
| { | ||
| ; | ||
| } | ||
| } |
| 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() { } | ||
| } |
| 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; } | ||
| } |
| 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> |
| 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; | ||
| } | ||
| } |
| 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(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
namespace лучше первой строкой файла, сразу после шапки с лицензией