Press space to go to the next slide.
It is best viewed with latest browsers (do not use Internet Explorer please!).
If you are not yet convinced of the Advantages of Unit Testing!
Originaly, I developed Visual T#, an enhanced Visual C# for Unit Testing.
It did not succeeed as expected. Problems were:
Recently, I decided to try to port as much as possible Visual T# ideas in a library, instead of a different language: Smart Tests: What's that?
It was based on C# (2.0 at that time) with Keywords added to simplify Unit Tests coding.
Visual T# was developed using C#, tested with NUnit and... Visual T# itself... (About 30.000 Unit Tests)
As it was difficult for pepole to switch from C# to T#, I decided to put as much T# features as possible in:
For the library part, Smart Tests has the same features as Visual T#:
For The Visual Studio Analyzer part:
But, unlike Visual T#, you keep your favorite language: C# and your favorite Testing Framework: NUnit, MSTests...
Very different responses exist, depending on the people you ask:
Problem is that when a test fails somewhere, you do not know what is wrong after the failing line of code!
If your tests are at a high level:
If your tests are at a low level:
Important: The Act should be 1 line of code only; for the best balance between knowledge and maintenance
You write the test to run this line of code.
The Act is the second/third part of three/four parts...
var p1 = new Person("Ludovic", "Dubois"); var p2 = new Person("FirstName", "LastName"); var a1 = new Account(p1, 100); var a2 = new Account(p2, 200); var t = new Transfer(a2, a1, 50); var ts1 = a1.Transactions; var ts2 = a2.Transactions; var t1 = ts1[ts1.Count - 1]; var t2 = ts2[ts2.Count - 1]; Assert.AreEqual(t1, t); Assert.AreEqual(t2, t); Assert.AreEqual(150, a1.Balance); Assert.AreEqual(150, a2.Balance);
Where is the Act here?
var p1 = new Person("Ludovic", "Dubois"); var p2 = new Person("You", "Ubisoft"); var a1 = new Account(p1, 100); var a2 = new Account(p2, 200); var t = RunTest( ..., () => new Transfer(a2, a1, 50)); var ts1 = a1.Transactions; var ts2 = a2.Transactions; var t1 = ts1[ts1.Count - 1]; var t2 = ts2[ts2.Count - 1]; Assert.AreEqual(t1, t); Assert.AreEqual(t2, t); Assert.AreEqual(150, a1.Balance); Assert.AreEqual(150, a2.Balance);
As you can see, the RunTest
method call enables you to easily retrieve the most important line of your test.
Thus, everything that is before RunTest
is for the Arrange/Assume parts and everything that is after the RunTest
is for the Assert part.
To know what unit tests are missing, you can use Code Coverage Tools.
But it is no sufficient:
For example, here is a test:
// Arrange var p = new Person("Ludovic", "Dubois"); // Act p.ClientType = ClientType.VIP; // Assert Assert.AreEqual(ClientType.VIP, p.ClientType);
A criteria declares several criterions:
For example, ValidValue
criteria has 2 criterions:
IsValid
IsInvalid
Thus, as the first argument of your RunTest
call, specify a criterion for your intent:
var p = new Person("Ludovic", "Dubois"); RunTest( ValidValue.IsValid, Assign( () => p.ClientType = ClientType.VIP ) ); Assert.AreEqual(ClientType.VIP, p.ClientType);
ValidValue.IsInvalid
is not yet tested for Person.ClientType
setter!
Thanks to the Visual Studio Analyzer.
Smart Tests defines lots of Criteria, and you can define your own.
// Arrange var p1 = new Person("Ludovic", "Dubois"); var p2 = new Person("You", "Ubisoft"); var a1 = new Account(p1, 100); var a2 = new Account(p2, 200); // Act var t = new Transfer(a2, a1, 50); // Assert var ts1 = a1.Transactions; var ts2 = a2.Transactions; var t1 = ts1[ts1.Count - 1]; var t2 = ts2[ts2.Count - 1]; Assert.AreEqual(t1, t); Assert.AreEqual(t2, t); Assert.AreEqual(150, a1.Balance); Assert.AreEqual(150, a2.Balance);
var p1 = new Person("Ludovic", "Dubois"); var p2 = new Person("You", "Ubisoft"); var a1 = new Account(p1, 100); var a2 = new Account(p2, 200); var t = RunTest( Case("fromAccount", ValidValue.IsValid) && Case("toAccount", ValidValue.IsValid) && Case((double amount) => amount.Above(0, out var value), () => new Transfer(a2, a1, value)); var ts1 = a1.Transactions; var ts2 = a2.Transactions; var t1 = ts1[ts1.Count - 1]; var t2 = ts2[ts2.Count - 1]; Assert.AreEqual(t1, t); Assert.AreEqual(t2, t); Assert.AreEqual(100 + value, a1.Balance); Assert.AreEqual(200 - value, a2.Balance);
Look at available criteria and their usage here
With implications on Arrange, Assume and/or Assert parts of your tests.
You will learn:
PropertyChanged
is raised?PropertyChanged
is not raised?Testing an object property changed as expected is good, but testing no property changed is good too!
How do you do that?
No secret: take a backup of all the properties before, and compare them with the values after!
// Arrange var p = new Person("Ludovic", "Dubois"); var fn = p.FirstName; var ln = p.LastName; var ct = p.ClientType; ... // Act var se = p.SendEmail(...); // Assert Assert.IsTrue(se); Assert.AreEqual(fn, p.FirstName); Assert.AreEqual(ln, p.LastName); Assert.AreEqual(ct, p.ClientType); ...
NotChanged
var p = new Person("Ludovic", "Dubois"); var se = RunTest(..., () => p.SendEmail(...), SmartAssert.NotChanged() ); Assert.IsTrue(se);
Here is what this assertion do:
// Arrange var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var fn = p.FirstName; var ln = p.LastName; ... // Act var t = new Deposit(a, 50); // Assert var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d); Assert.AreEqual(150, a.Balance); Assert.AreSame(p, a.Owner); Assert.AreEqual(fn, p.FirstName); Assert.AreEqual(ln, p.LastName); ...
NotChanged
var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var t = RunTest(..., () => new Deposit(a, 50), SmartAssert.NotChanged(p) ); var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d); Assert.AreEqual(150, a.Balance); Assert.AreSame(p, a.Owner);
Testing that property has the right value in the Assert part is good, but testing that this property did not have this value previously is far better.
How do you do that?
No secret: Ensure the value is not the final one in the Assume part
// Arrange var p = new Person("Ludovic", "Dubois"); // Assume Assert.AreNotEqual(ClientType.VIP, p.ClientType); // Act p.ClientType = ClientType.VIP; // Assert Assert.AreEqual(ClientType.VIP, p.ClientType);
ChangedTo
var p = new Person("Ludovic", "Dubois"); RunTest(..., Assign(() => p.ClientType, ClientType.VIP), SmartAssert.ChangedTo());
Here is what this assertion do:
// Arrange var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); // Assume Assert.AreNotEqual(150, a.Balance); // Act var t = new Deposit(a, 50); // Assert var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d); Assert.AreEqual(150, a.Balance);
ChangedTo
var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var t = RunTest(..., () => new Deposit(a, 50), SmartAssert.ChangedTo(() => a.Balance, 150)); var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d);
Testing an object property changed as expected is good, but testing no other property changed is good too!
How do you do that?
No secret: take a backup of all the properties (except the one that should change) before, and compare them with the values after!
// Arrange var p = new Person("Ludovic", "Dubois"); var fn = p.FirstName; var ln = p.LastName; ... // Assume Assert.AreNotEqual(ClientType.VIP, p.ClientType); // Act p.ClientType = ClientType.VIP; // Assert Assert.AreEqual(ClientType.VIP, p.ClientType); Assert.AreEqual(fn, p.FirstName); Assert.AreEqual(ln, p.LastName); ...
NotChangedExcept
var p = new Person("Ludovic", "Dubois"); RunTest(..., Assign(() => p.ClientType, ClientType.VIP), SmartAssert.NotChangedExcept(), SmartAssert.ChangedTo()); // test more!
Here is what this assertion do:
// Arrange var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var pid = a.Id; var pCD = a.CreationDate; ... // Act var t = new Deposit(a, 50); // Assert var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d); Assert.AreEqual(150, a.Balance); Assert.AreEqual(pId, a.Id); Assert.AreEqual(pCD, a.CreationDate); Assert.AreSame(p, a.Owner); ...
NotChangedExcept
var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var t = RunTest(..., () => new Deposit(a, 50), SmartAssert.NotChangedExcept(a, nameof(Account.Balance)), SmartAssert.ChangedTo(() => a.Balance, 150) ); // test more! var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d);
PropertyChanged
is raised?
Testing an object property changed as expected is good, but testing that PropertyChanged
event is raised is good too!
How do you do that?
No secret:
false
.PropertyChanged
event.true
.args.PropertyName
is the expected one. PropertyChanged
event.true
.// Arrange var p = new Person("Ludovic", "Dubois"); pcRaised = False; p.PropertyChanged += PC; // Act p.ClientType = ClientType.VIP; // Assert p.PropertyChanged -= PC; Assert.IsTrue(pcRaised); Assert.AreEqual(ClientType.VIP, p.ClientType);
// In PC method pcRaised = true Assert.AreEqual(nameof(Person.ClientType), args.PropertyName); Assert.AreEqual(ClientType.VIP, p.ClientType);
PropertyChanged
is raised!
Raised_PropertyChanged
var p = new Person("Ludovic", "Dubois"); RunTest(..., Assign(() => p.ClientType, ClientType.VIP), SmartAssert.Raised_PropertyChanged(), SmartAssert.NotChangedExcept(), // test more! SmartAssert.ChangedTo()); // test more!
PropertyChanged
is raised?
// Arrange var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); pcRaised = False; a.PropertyChanged += PC; // Assume Assert.AreNotEqual(150, a.Balance); // Act var t = new Deposit(a, 50); // Assert a.PropertyChanged -= PC; Assert.IsTrue(pcRaised); var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d); Assert.AreEqual(150, a.Balance);
// In PC method pcRaised = true Assert.AreEqual(nameof(Account.Balance), args.PropertyName); Assert.AreEqual(150, ((Account)sender).Balance);
PropertyChanged
is raised!
Raised_PropertyChanged
var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var t = RunTest(..., () => new Deposit(a, 50), SmartAssert.Raised_PropertyChanged(() => a.Balance, 150), SmartAssert.ChangedTo(() => a.Balance, 150), SmartAssert.NotChangedExcept(() => a.Balance)); // test more! var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d);
PropertyChanged
is not raised?
Testing an object property changed as expected is good, but testing that PropertyChanged
event is not raised is good too!
How do you do that?
No secret:
false
.PropertyChanged
event.true
.PropertyChanged
event.false
.// Arrange var p = new Person("Ludovic", "Dubois"); pcRaised = False; p.PropertyChanged += PC; // Act p.SendEmail(...); // Assert p.PropertyChanged -= PC; Assert.IsFalse(pcRaised);
// In PC method pcRaised = true
PropertyChanged
is not raised!
Not_Raised_PropertyChanged
var p = new Person("Ludovic", "Dubois"); RunTest(..., () => p.SendEmail(...), SmartAssert.NotRaised_PropertyChanged(), SmartAssert.NotChanged()); // test more!
PropertyChanged
is not raised?
// Arrange var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); pcRaised = False; p.PropertyChanged += PC; // Assume Assert.AreNotEqual(150, a.Balance); // Act var t = new Deposit(a, 50); // Assert p.PropertyChanged -= PC; Assert.IsFalse(pcRaised); var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d); Assert.AreEqual(150, a.Balance);
// In PC method pcRaised = true;
PropertyChanged
is not raised!
Not_Raised_PropertyChanged
var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var t = RunTest(..., () => new Deposit(a, 50), SmartAssert.ChangedTo(() => a.Balance, 150), SmartAssert.NotRaised_PropertyChanged(p)); var d = a.Transactions[a.Transactions.Count - 1]; Assert.AreEqual(t, d);
Testing an object property changed as expected is good, but testing any event is good too!
How do you do that?
No secret: quite the same as PropertyChanged, except that the check in the event handler do not test the PropertyName
, but anything else.
Raise/NotRaised
.
// Arrange var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); // Act var t = new Deposit(a, 50); // Assert var ts = a.Transactions; Assert.AreEqual(1, ts.Count); // Is it always one? Always from 0 to 1? Or one more than before? Assert.AreEqual(ts[0], t); Assert.AreEqual(150, a.Balance);
Change
var p = new Person("Ludovic", "Dubois"); var a = new Account(p1, 100); var t = RunTest(..., () => new Deposit(a, 50), SmartAssert.Change(() => a.Transactions.Count + 1)); // It is one more than before! var ts = a.Transactions; Assert.AreEqual(ts[ts.Count - 1], t); Assert.AreEqual(150, a.Balance);
Here is what this assertion do:
You have to start a StopWatch
before your Act and stop it after your Act.
// Arrange var p = new Person("Ludovic", "Dubois"); var watch = new StopWatch(); watch.Start(); // Act p.SendEmail(...); // Assert watch.Stop(); Assert.Less(watch.TotalMilliseconds, 100);
Within
var p = new Person("Ludovic", "Dubois"); RunTest(..., () => p.SendEmail(...), SmartAssert.Within(100));
Here is what this assertion do:
StopWatch
.
StopWatch
and verifies that the elapsed milliseconds is less than the specified value.
You have to wait for a handle hat is set within the asynchronous call.
// Arrange var p = new Person("Ludovic", "Dubois"); var evt = new AutoResetEvent(false); // Act p.SendEmail(..., sent:() => evt.Set()); // Assert Assert.IsTrue(evt.WaitOne(1000));
Within
var p = new Person("Ludovic", "Dubois"); RunTest(..., ctx => p.SendEmail(..., done:ctx.SetHandle), SmartAssert.WaitContextHandle(1000));
Here is what this assertion do:
AutoResetEvent
.
AutoResetEvent
for the specified time.
For more information: