diff --git a/MyNUnit/AttributesMyNunit/After.cs b/MyNUnit/AttributesMyNunit/After.cs new file mode 100644 index 0000000..d837b06 --- /dev/null +++ b/MyNUnit/AttributesMyNunit/After.cs @@ -0,0 +1,11 @@ +namespace AttributesMyNUnit; + +using System; + +/// +/// Атрибут, которым обозначают методы, которые вызываются после каждого теста +/// +[AttributeUsage(AttributeTargets.Method)] +public class After : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/AttributesMyNunit/AfterClass.cs b/MyNUnit/AttributesMyNunit/AfterClass.cs new file mode 100644 index 0000000..227ba29 --- /dev/null +++ b/MyNUnit/AttributesMyNunit/AfterClass.cs @@ -0,0 +1,11 @@ +namespace AttributesMyNUnit; + +using System; + +/// +/// Атрибут, которым обозначают методы, которые вызываются после тестов +/// +[AttributeUsage(AttributeTargets.Method)] +public class AfterClass : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/AttributesMyNunit/AttributesMyNunit.csproj b/MyNUnit/AttributesMyNunit/AttributesMyNunit.csproj new file mode 100644 index 0000000..8bf737c --- /dev/null +++ b/MyNUnit/AttributesMyNunit/AttributesMyNunit.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + enable + enable + Attributes + + + diff --git a/MyNUnit/AttributesMyNunit/Before.cs b/MyNUnit/AttributesMyNunit/Before.cs new file mode 100644 index 0000000..837c3a5 --- /dev/null +++ b/MyNUnit/AttributesMyNunit/Before.cs @@ -0,0 +1,11 @@ +namespace AttributesMyNUnit; + +using System; + +/// +/// Атрибут, которым обозначают методы, которые вызываются перед каждым тестом +/// +[AttributeUsage(AttributeTargets.Method)] +public class Before : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/AttributesMyNunit/BeforeClass.cs b/MyNUnit/AttributesMyNunit/BeforeClass.cs new file mode 100644 index 0000000..67c202a --- /dev/null +++ b/MyNUnit/AttributesMyNunit/BeforeClass.cs @@ -0,0 +1,11 @@ +namespace AttributesMyNUnit; + +using System; + +/// +/// Атрибут, которым обозначают методы, которые вызываются перед тестами +/// +[AttributeUsage(AttributeTargets.Method)] +public class BeforeClass : Attribute +{ +} \ No newline at end of file diff --git a/MyNUnit/AttributesMyNunit/Test.cs b/MyNUnit/AttributesMyNunit/Test.cs new file mode 100644 index 0000000..a009520 --- /dev/null +++ b/MyNUnit/AttributesMyNunit/Test.cs @@ -0,0 +1,20 @@ +namespace AttributesMyNUnit; + +using System; + +/// +/// Атрибут, которым обозначают методы, которые являются тестами +/// +[AttributeUsage(AttributeTargets.Method)] +public class Test : Attribute +{ + public Type Expected { get; set; } + + public string? Ignore { get; set; } + + public Test(Type expected, string? ignore = null) + { + Expected = expected; + Ignore = ignore; + } +} \ No newline at end of file diff --git a/MyNUnit/ForTests/ForTests.csproj b/MyNUnit/ForTests/ForTests.csproj new file mode 100644 index 0000000..f532cbb --- /dev/null +++ b/MyNUnit/ForTests/ForTests.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + 10 + ClassLibrary1 + + + + + + + diff --git a/MyNUnit/ForTests/Test1.cs b/MyNUnit/ForTests/Test1.cs new file mode 100644 index 0000000..4a666c2 --- /dev/null +++ b/MyNUnit/ForTests/Test1.cs @@ -0,0 +1,77 @@ +namespace ForTests; + +using System; +using AttributesMyNUnit; + +public class Test1 +{ + private int nonStaticCounter = 0; + + [Before] + public void NonStaticIncrement() => nonStaticCounter++; + + [Test(null)] + public void TestWithoutExpected() + { + + } + + [Test(null, "yes")] + public void TestShouldBeIgnored() + { + + } + + [Test(typeof(ArgumentException))] + public void TestWithExpectedException() + { + throw new ArgumentException(); + } + + + [Test(null)] + public void TestBefore() + { + if (nonStaticCounter != 1) + { + throw new ArgumentException(); + } + } + + [Test(null)] + public void NullExpectedButThrowException() + { + throw new ArgumentException(); + } + + [Test(typeof(ArgumentException))] + public void ExceptionExpectedButWasNull() + { + + } + + [Test(typeof(ArgumentException))] + public void OneExceptionExpectedButWasAnother() + { + throw new AggregateException(); + } + + [BeforeClass] + public void NonStaticBeforeClass() + { + + } + + [AfterClass] + public static void ExceptionInAfterClass() + { + throw new AggregateException(); + } + + [After] + [Test(null)] + public void TestWithIncompatibleAttributes() + { + + } +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit.Test/MyNUnit.Test.cs b/MyNUnit/MyNUnit.Test/MyNUnit.Test.cs new file mode 100644 index 0000000..eb5015d --- /dev/null +++ b/MyNUnit/MyNUnit.Test/MyNUnit.Test.cs @@ -0,0 +1,33 @@ +namespace MyNUnit.Test; + +using System.Linq; +using NUnit.Framework; + +public class Tests +{ + private readonly MyNUnit myNUnit = new (); + private readonly string[] answer = new string[] + { + "Проверка тестов из Test1", + "Метод NonStaticBeforeClass содержит атрибут BeforeClass, но не является статическим", + "Метод TestWithIncompatibleAttributes имеет два несовместимых атрибута", + "В методе ExceptionInAfterClass возникло исключение: System.AggregateException", + "Тест TestWithExpectedException пройден успешно", + "Тест TestShouldBeIgnored был игнорирован", + "Тест TestWithoutExpected пройден успешно", + "Тест TestBefore пройден успешно", + "Тест ExceptionExpectedButWasNull не пройден: ожидалось исключение типа System.ArgumentException", + "Тест OneExceptionExpectedButWasAnother не пройден: ожидалось исключение типа System.ArgumentException, возникло System.AggregateException", + "Тест NullExpectedButThrowException не пройден: возникло исключение System.ArgumentException" + }; + + [NUnit.Framework.Test] + public void TestForMessages() + { + var result = myNUnit.RunTests("../../../../ForTests/bin/Debug/net6.0/"); + foreach (var m in result) + { + Assert.IsTrue(answer.Contains(m.Item1)); + } + } +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit.Test/MyNUnit.Test.csproj b/MyNUnit/MyNUnit.Test/MyNUnit.Test.csproj new file mode 100644 index 0000000..6cd73e0 --- /dev/null +++ b/MyNUnit/MyNUnit.Test/MyNUnit.Test.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + enable + + false + + 10 + + + + + + + + + + + + + + + diff --git a/MyNUnit/MyNUnit.sln b/MyNUnit/MyNUnit.sln new file mode 100644 index 0000000..f460980 --- /dev/null +++ b/MyNUnit/MyNUnit.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyNUnit", "MyNUnit\MyNUnit.csproj", "{1DB5464C-6E66-40C9-87A3-6511DD33C324}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyNUnit.Test", "MyNUnit.Test\MyNUnit.Test.csproj", "{E9C716B6-D491-4B60-BA36-063C4902B355}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForTests", "ForTests\ForTests.csproj", "{05442C5E-59D8-4BC3-805B-9FF732926D2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributesMyNunit", "AttributesMyNunit\AttributesMyNunit.csproj", "{9EE0AFA0-1759-431B-9810-E49A29F36499}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1DB5464C-6E66-40C9-87A3-6511DD33C324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DB5464C-6E66-40C9-87A3-6511DD33C324}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DB5464C-6E66-40C9-87A3-6511DD33C324}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DB5464C-6E66-40C9-87A3-6511DD33C324}.Release|Any CPU.Build.0 = Release|Any CPU + {E9C716B6-D491-4B60-BA36-063C4902B355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9C716B6-D491-4B60-BA36-063C4902B355}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9C716B6-D491-4B60-BA36-063C4902B355}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9C716B6-D491-4B60-BA36-063C4902B355}.Release|Any CPU.Build.0 = Release|Any CPU + {05442C5E-59D8-4BC3-805B-9FF732926D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05442C5E-59D8-4BC3-805B-9FF732926D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05442C5E-59D8-4BC3-805B-9FF732926D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05442C5E-59D8-4BC3-805B-9FF732926D2B}.Release|Any CPU.Build.0 = Release|Any CPU + {9EE0AFA0-1759-431B-9810-E49A29F36499}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EE0AFA0-1759-431B-9810-E49A29F36499}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EE0AFA0-1759-431B-9810-E49A29F36499}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EE0AFA0-1759-431B-9810-E49A29F36499}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/MyNUnit/MyNUnit/MyNUnit.cs b/MyNUnit/MyNUnit/MyNUnit.cs new file mode 100644 index 0000000..996e784 --- /dev/null +++ b/MyNUnit/MyNUnit/MyNUnit.cs @@ -0,0 +1,206 @@ +namespace MyNUnit; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using AttributesMyNUnit; + +/// +/// Класс MyNUnit для запуска и проверки тестов +/// +public class MyNUnit +{ + private ConcurrentBag<(string, string)> messages; + + /// + /// запуск тестов из .dll + /// + /// путь до .dll + /// Кортеж из (названия теста, результат о нем) + public (string, string)[] RunTests(string path) + { + var dlls = Directory.GetFiles(path, "*.dll"); + var tasks = new Task>[dlls.Length]; + messages = new(); + Parallel.ForEach(dlls, dll => DoTestFromDll(dll)); + return messages.ToArray(); + } + + private void DoTestFromDll(string dllPath) + { + var dll = Assembly.LoadFrom(dllPath); + var classes = dll.ExportedTypes.Where(t => t.IsClass); + Parallel.ForEach(classes, c => DoWorkInClass(c)); + } + + private void DoWorkInClass(Type classFromDll) + { + var testAttributes = new TestAttributes(); + var methods = classFromDll.GetMethods(); + Parallel.ForEach(methods, method => GetAttributesAndDoBeforeAndAfterClass(method, testAttributes)); + RunMethodsWithAttributes(testAttributes.BeforeClass, null); + + if (testAttributes.Tests.Count < 1) + { + return; + } + messages.Add(($"Проверка тестов из {classFromDll.Name}", "")); + + Parallel.ForEach(testAttributes.Tests, test => DoTest(test, testAttributes, classFromDll)); + RunMethodsWithAttributes(testAttributes.AfterClass, null); + } + + private void DoTest(MethodInfo test, TestAttributes testAttributes, Type classDll) + { + var attribute = (Test)Attribute.GetCustomAttribute(test, typeof(Test)); + if (attribute.Ignore != null) + { + messages.Add(($"Тест {test.Name} был игнорирован", "")); + return; + } + object classInstance = Activator.CreateInstance(classDll); + RunMethodsWithAttributes(testAttributes.Before, classInstance); + var message = ("", ""); + var watch = new Stopwatch(); + watch.Start(); + try + { + test.Invoke(classInstance, null); + } + catch (Exception exception) + { + watch.Stop(); + if (attribute.Expected == null) + { + message = ($"Тест {test.Name} не пройден: возникло исключение {exception.InnerException.GetType()}", $"Время: {watch.ElapsedMilliseconds} ms"); + } + else if (exception.InnerException.GetType() != attribute.Expected) + { + message = ($"Тест {test.Name} не пройден: ожидалось исключение типа {attribute.Expected}, возникло {exception.InnerException.GetType()}", $"Время: {watch.ElapsedMilliseconds} ms"); + } + else + { + message = ($"Тест {test.Name} пройден успешно", $"Время: {watch.ElapsedMilliseconds} ms"); + } + } + finally + { + watch.Stop(); + if (message.Item1 == "") + { + if (attribute.Expected == null) + { + message = ($"Тест {test.Name} пройден успешно", $"Время: {watch.ElapsedMilliseconds} ms"); + } + else + { + message = ($"Тест {test.Name} не пройден: ожидалось исключение типа {attribute.Expected}", $"Время: {watch.ElapsedMilliseconds} ms"); + } + } + } + messages.Add(message); + + RunMethodsWithAttributes(testAttributes.After, classInstance); + } + + private void AddMethod(Attribute[] attributes, MethodInfo method, List list) + { + if (attributes.Length != 0) + { + list.Add(method); + } + } + + private void RunMethodsWithAttributes(List methods, object classIntstase) + { + foreach (MethodInfo method in methods) + { + try + { + method.Invoke(classIntstase, null); + } + catch (Exception e) + { + messages.Add(($"В методе {method.Name} возникло исключение: {e.InnerException.GetType()}", "")); + } + } + } + + private Attribute[][] GetCountAttributes(MethodInfo method) + { + var types = new Type[] { typeof(Before), typeof(BeforeClass), typeof(Test), typeof(AfterClass), typeof(After)}; + var typesCount = new Attribute[5][]; + for (int i = 0; i < types.Length; i++) + { + typesCount[i] = Attribute.GetCustomAttributes(method).Where(t => t.GetType() == types[i]).ToArray(); + } + return typesCount; + } + + private int GetSumOfAttributesInMethod(Attribute[][] array) + { + var count = 0; + for (int i = 0;i < array.Length;i++) + { + count += array[i].Length; + } + return count; + } + + private void GetAttributesAndDoBeforeAndAfterClass(MethodInfo method, TestAttributes testAttributes) + { + var countOfAttributes = GetCountAttributes(method); + if (GetSumOfAttributesInMethod(countOfAttributes) > 1) + { + messages.Add(($"Метод {method.Name} имеет два несовместимых атрибута", "")); + return; + } + AddMethod(countOfAttributes[0], method, testAttributes.Before); + AddMethod(countOfAttributes[2], method, testAttributes.Tests); + AddMethod(countOfAttributes[4], method, testAttributes.After); + if (countOfAttributes[1].Length != 0) + { + if (method.IsStatic) + { + testAttributes.BeforeClass.Add(method); + } + else + { + messages.Add(($"Метод {method.Name} содержит атрибут BeforeClass, но не является статическим", "")); + } + } + if (countOfAttributes[3].Length != 0) + { + if (method.IsStatic) + { + testAttributes.AfterClass.Add(method); + } + else + { + messages.Add(($"Метод {method.Name} содержит атрибут AfterClass, но не является статическим", "")); + } + } + } + private class TestAttributes + { + public TestAttributes() + { + After = new(); + Before = new(); + BeforeClass = new(); + AfterClass = new(); + Tests = new(); + } + + public List After { get; set; } + public List Before { get; set; } + public List BeforeClass { get; set; } + public List AfterClass { get; set; } + public List Tests { get; set; } + } +} \ No newline at end of file diff --git a/MyNUnit/MyNUnit/MyNUnit.csproj b/MyNUnit/MyNUnit/MyNUnit.csproj new file mode 100644 index 0000000..5756fd0 --- /dev/null +++ b/MyNUnit/MyNUnit/MyNUnit.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + Windows + 10 + + + + + + + diff --git a/MyNUnit/MyNUnit/Program.cs b/MyNUnit/MyNUnit/Program.cs new file mode 100644 index 0000000..8001d21 --- /dev/null +++ b/MyNUnit/MyNUnit/Program.cs @@ -0,0 +1,33 @@ +namespace MyNUnit; + +using System; +using System.IO; + +class Program +{ + static void Main(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Путь к папке не указан"); + return; + } + + if (Directory.Exists(args[0])) + { + Console.WriteLine("По данному пути ниичего не найдено."); + return; + } + + var myNUnit = new MyNUnit(); + var result = myNUnit.RunTests(args[0]); + foreach (var test in result) + { + Console.WriteLine(test.Item1); + if (test.Item2 != null) + { + Console.WriteLine(test.Item2); + } + } + } +} \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 9809100..ec8ac0d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,5 @@ +image: Visual Studio 2022 + build_script: - For /R %%I in (*.sln) do dotnet test %%I test: of \ No newline at end of file