Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
pcsikos committed Nov 1, 2015
1 parent 4055cde commit 7307317
Showing 1 changed file with 153 additions and 13 deletions.
166 changes: 153 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) [![Build status](https://ci.appveyor.com/api/projects/status/5its63lms49x9cih?svg=true)](https://ci.appveyor.com/project/pcsikos/unittestgenerator) [![Coverage Status](https://coveralls.io/repos/pcsikos/UnitTestGenerator/badge.svg?branch=master&service=github)](https://coveralls.io/github/pcsikos/UnitTestGenerator?branch=master) [![NuGet](https://img.shields.io/nuget/v/UnitTestGenerator.svg)](https://www.nuget.org/packages/UnitTestGenerator)


The primary purpose of **Unit Test Generator** is to generate repetative and boring copy/paste unit tests such as null argument check. Tests are generated by means of [T4 text template](https://msdn.microsoft.com/en-us/library/bb126445.aspx). Process of the code generation consists from three parts.
The primary purpose of **Unit Test Generator** is to generate repetative and boring copy/paste unit tests such as null argument check. Tests mostly validate whether an ArgumentNullException was thrown. Tests are generated by means of [T4 text template](https://msdn.microsoft.com/en-us/library/bb126445.aspx). Process of the code generation consists from three parts.

1. Method body generation
2. Method name generation
3. Test Class source code generation (T4)

Each part of code generation can be customized. Tests mostly validate whether an ArgumentNullException was thrown. Therefore T4 template and method body generation will fit for most developers and unit test frameworks(changing test method attribute in T4 does not counts or hurts :). With the generation of method name it is a different case. For the test method naming was the [Roy Osherove's naming strategy](http://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html) used. However this can be changed by your own implementation. About that later.
You can find more examples of generated tests in the [repository](src/UnitTestGenerator.Tests/ArgumentTest.cs).
Each part of code generation can be customized. Test method body generation is done by converting Expression tree to source code. In the process of building the body of a test method an Expression is constructed to satisfy the goal of test. This can be a method call or an object instance creation. Method body generation and test class generation (T4) should fit for most developers and unit test frameworks(changing test method attribute in T4 does not counts or hurts :). With the generation of method name it is a different case. For the test method naming was the [Roy Osherove's naming strategy](http://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html) used. However this can be changed by your own implementation. About that later.
You can find more examples of generated tests in the [repository](src/UnitTestGenerator.Tests/ArgumentTest.cs) or in [samples](src/Samples).

Generated test may look like:
```c#
Expand Down Expand Up @@ -58,27 +58,167 @@ For the syntax above you will need to install package UnitTestGenerator.Extensio

It will install basic T4 template or you can use direct [link](nuget/ArgumentCheck.tt).

# Why
# Why generate tests?

Are you you asking yourself why on Earth would you generate tests at all? Yes, it is againts the [idea](http://stackoverflow.com/questions/357059/unit-test-case-generator) of unit tests. Discussions about generating unit tests always tends to end with conclusions that it is a bad idea. Yes, that is true, however this is not that case. **Unit Test Generator** only analyzes the signature of methods not their bodies and regarding that generates test methods to validate whether null arguments are handled properly. It does exactly that, what developer would do when they would write argument validation tests. Benefit from **Unit Test Generator** is time saving(each nullable parameter leads to another test) and it avoids human errors when tests are manually written (forgeting writing test for new methods, argument checks for new parameters in existing methods, etc.).

# When
# When generate tests?

Most suitable for Unit Test Generator is [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) when you need test arguments of methods and constructors as well for null reference. Service classes needs mostly be tested for null refenrence only in methods, because constructors are handled by IoC containers and thus do not need argument validation. At least not for null reference.
Most suitable scenario for generating tests is [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) when you need test arguments of methods and constructors as well for null reference. Service classes needs mostly be tested for null refenrence only in methods, because constructors are handled by IoC containers and thus do not need argument validation. At least not for null reference.

# Advanced Scenarios

##Custom source code generation
>Comming soon
##Custom method name generation
>Comming soon
When you need only change a part of the generation, for example the method name generation, you do not need create classes for everything else.

```
class MyCustomCodeGenerator : NullArgumentMethodTestMethodSourceCodeGenerator
{
public MyCustomCodeGenerator(IExpressionBuilder expressionBuilder,
ITestMethodValueProvider testMethodValueProvider) : base(expressionBuilder, testMethodValueProvider)
{
}
public override string BuildMethodName(MethodSourceCodeGenerationRequest request)
{
return request.Method.Name + "_ShouldAlwaysSucceed";
}
}
```

Configuring the generator is then pretty easy:

```
var testClasses = typeof(CustomAssembly.Class1).Assembly
.ComposeTestClassBuilder("CustomAssembly.Tests",
container =>
container.Register<INullArgumentMethodTestMethodSourceCodeGenerator, MyCustomCodeGenerator2>(),
configure => configure.AddGenerator<CustomTestMethodGenerator>())
.BuildTestClasses();
```

Check the [sample](src/Samples/FooBarLibrary.Tests/ArgumentCheck_NameChange.tt).

##Custom source code and method name generation

Most advanced customization is your own source code generation. This can be done by inheriting from existing Code Generator like [NullArgumentMethodTestMethodSourceCodeGenerator](src/UnitTestGenerator/CodeGeneration/Generators/NullArgumentMethodTestMethodSourceCodeGenerator.cs).

```
class MyCustomCodeGenerator : NullArgumentMethodTestMethodSourceCodeGenerator
{
public MyCustomCodeGenerator(IExpressionBuilder expressionBuilder,
ITestMethodValueProvider testMethodValueProvider) : base(expressionBuilder, testMethodValueProvider)
{
}
protected override void BuildArrangeSourceCode(MethodSourceCodeGenerationRequest request)
{
AppendLine("//TODO: Arrange");
}
protected override void BuildActSourceCode(MethodSourceCodeGenerationRequest request)
{
AppendLine("//TODO: Act");
}
protected override void BuildAssertSourceCode(MethodSourceCodeGenerationRequest request)
{
AppendLine("//TODO: Assert");
}
}
```

Or by implementing your own ITestMethodSourceCodeGenerator.

```
class MyCustomCodeGenerator2 : ITestMethodSourceCodeGenerator<MethodSourceCodeGenerationRequest>
{
public string BuildMethodName(MethodSourceCodeGenerationRequest request)
{
return request.Method.Name + "_ShouldAlwaysSucceed";
}
public string BuildSourceCode(MethodSourceCodeGenerationRequest request)
{
var source = @"
//TODO: Arrange
//TODO: Act
//TODO: Assert
";
return source;
}
}
```

Do not forget. Generation of source code is just a part of the job. You must also create class which will analyze the passed type whether it can be used or on what members.

```
public class CustomTestMethodGenerator : ITestMethodGenerator
{
public IEnumerable<TestMethod> GenerateTestMethods(TypeContext typeContext)
{
var sourceCodeGenerator = new MyCustomCodeGenerator2();
foreach (var property in typeContext.TargetType.GetProperties())
{
var request = new MethodSourceCodeGenerationRequest(property);
var methodName = sourceCodeGenerator.BuildMethodName(request);
var sourceCode = sourceCodeGenerator.BuildSourceCode(request);
yield return new TestMethod(property, methodName, sourceCode);
}
}
}
```
Check the working [sample](src/Samples/FooBarLibrary.Tests/ArgumentCheck_CustomGenerator.tt).
```
var testClassBuilder = typeof(CustomAssembly.Class1).Assembly
.ComposeTestClassBuilder("CustomAssembly.Tests", configure: configure => configure.AddGenerator<CustomTestMethodGenerator>())
.BuildTestClasses();
```

##Using own unit test generator
>Comming soon

##Configuring ClassBuilder composition
>Comming soon
You can customize configuration using UnitTestGenerator.Extensions.Composition.

###Reusing tested instance
In a case you have an instance of tested class as a member field in you own test, you can reuse it in generated tests.
Your hand written test may look like this:
```
[TestClass]
public partial class MyClassTests
{
private MyClass myClassInstance;
...
//your test methods
...
}
```

And to reuse the myClassInstance you can register the name of the field member:
```
var testClassBuilder = typeof(CustomAssembly.Class1).Assembly
.ComposeTestClassBuilder("CustomAssembly.Tests", configure: configure => configure.IncludeBuiltinGenerators()
.ParameterTypeMapping(new Dictionary<Type, string> {
{ typeof(MyClass), "myClassInstance" }
}))
.BuildTestClasses();
```
Do not forget that the generated class must be partial and must be placed in the same namespace as your own to be visible from generated class. Check [sample](src/Samples/FooBarLibrary.Tests/ArgumentCheckReuse.tt).

###Defining default values
To test arguments for null values, you need specify the tested argument as null and the remaining parameters with valid non null value. Therefore the other fields are proxied or dynamically generated at the execution of test. However, you may have classes which cannot be instantiated with any of the mentioned ways. For example the class MemberInfo cannot be instantiated nor proxied. To solve this issue Expression registration was introduced, which replace dynamic Expression generation which is used to source code generation. Therefore the registered expression will be converted to source code as well. Check [sample](src/Samples/FooBarLibrary.Tests/ArgumentCheckDefaults.tt).

```
var testClassBuilder = typeof(CustomAssembly.Class1).Assembly
.ComposeTestClassBuilder("CustomAssembly.Tests", configure: configure => configure.IncludeBuiltinGenerators()
.WithDefaultValues(new[] {
(Expression<Func<MemberInfo>>)(() => ((Func<string, string>)string.Copy).Method)
}))
.BuildTestClasses();
```




# Issues

Expand Down

0 comments on commit 7307317

Please sign in to comment.