xsharp.eu • XIDE, NUNIT and functions.
Page 1 of 1

XIDE, NUNIT and functions.

Posted: Wed Oct 05, 2016 8:38 pm
by Frank Maraite
Hi pearlers of X#,

for all of you coming directly from VO to X#, I will introduce you to something that was not possible with VO: Unit Testing.

For more detailed information I strongly recommend these two books:

Working Effectivly with Legacy Code by Micheal C. Feathers
and
The Art of Unit Testing, by Roy Osherove

Both are available in german, so I think, they are available in many other languages too.

More books to read may be found here:
http://www.codeproject.com/Reference/61 ... ence-Books

Now, what is Unit Testing? In short words:
- It's the first defense line against bugs!
and
- it prevents in many cases the use of a debugger. I feel, and since I do unit testing more than ever, that debugging is mostly a waste of time.
The biggest con: it cannot be repeated automaticly. We have to do it manual. It's like shooting in the dark.
Unit testing is more like engineering, is quality control, beginning from the first line of code to the last one. And at the ends it saves the value of your code base.
Software without unit tests is effectivly unsaleable! Who wants to buy a pig in a poke? And what prevents against unwanted side effects of code changes.

Now, as always, the first step is the hardest. But I promise you, as soon as you have found your first hidden bug, you never will miss the tests. For me it took only one hour.

I do unit testing with XIDE and NUNIT. http://www.nunit.org/

For now I recommend to download and install V 2.6. The simple reason: Until today they do not have a GUI runner for V 3, which I like to use. But be careful, don't use features that are flagged deprecated.

Then we do some settings in XIDE.
First we create a new project configuration. I name it NUNIT. Project->Properties->Configuration->Add

Now we create a new application.

I recommend as a general tip to set one common output directory for all applications of a project. In fact I use the same directory for all my projects.

Application->Properties->General->Output folder:

For example I name it D:InstallSource/Assemblies

We copy the NUnit framework assembly nunit.framework.dll into this directory.

At Application->Properties->General->Compiler->Build configurations->NUnit we insert

/define:NUNIT

At Application->Properties->References->Browse we click 'Browse disk for dll files' and search and select nunit.framework.dll .

The program and the corresponding tests are put into two separate PRG's. For example
Functions.prg and FunctionsTests.prg
This way they are always grouped together.

First I show the general frame, where the business code and the test code is in. My example is a function, where the class is a static class, that cannot be instantiated.
But the schema is always the same.

// Functions.prg, contains the working code, that has to be tested.
PARTIAL STATIC CLASS Funktionen // important: PARTIAL!
...
END CLASS

// FunktionenTests.prg, contains the tests

#ifdef NUNIT // Compile only, if define NUNIT exists, see project configuration and application setting.

#using NUnit.Framework
PARTIAL STATIC CLASS Funktionen // Seems to be senseless. But through using the 'Nested Class' construct allowas the tests access to private members tested class!

[TestFixture] ;
SEALED CLASS FunktionsTests INHERIT AssertionHelper // AssertionHelper is a NUnit class.
...
END CLASS
END CLASS
#endif

I think, this is easier to recognize then embedded in ful example. This frame can be saved as a code snippet.

After the first compile (complete example of course) a double click on the DLL should start the NUnit GUI runner. It has a 'RUN' button. Click once, and the first test run should start showing 'green'. ( Test cycles are named 'Make it green'.)

After the first run the tests starts automaticly after each compile. One or two or thousands some time. We see a few moments after some code changes does it introduce a bug (red)or not (green).
It makes you very confident to your software, when it remains green. And if it changes to red, you found the bug before your customer does. This is better than running debug sessions hours later.

Now the complete example. Some comments on it: I don't claim, it's well designed. These are one of the first tests I wrote 2012. I just copied and pasted.
Purist say: Only one test method for one test case. The reason for this is: If we have more then one tests in a test method and one of these tests fail, the remaining will be skipped. It's in most cases easier to analyse the issue if we know, what test fail and what test passes.
Here I follow this rule for the exceptions test. The other tests are all in one test method. It reads much clearer.

In general I follow the rule, because many test have to be prepared/initialised by setting parameters, environment and so on.

Ok, enough silly words, here is the full example code:


// Functions.prg, contains the business code
PARTIAL STATIC CLASS Functions

STATIC PRIVATE ASC0 := 48 AS INT // Example for a private member

// The example is the well known STUFF function. The parameters are the same as in VO, for example StartPosition is one-based. But instead reading some kind of documentaion
// the test should show what happens. (Sorry for mixing german/english in code)

STATIC METHOD Stuff( SELF InputStr AS STRING, StartPosition AS INT, AnzahlZumLöschen AS INT, ErsatzZeichen AS STRING ) AS STRING // Replacement for runtime Stuff-Function

// Check the input parameter
IF InputStr == NULL
THROW System.ArgumentNullException{ "InputStr is NULL." }
ENDIF

IF StartPosition < 1
THROW System.ArgumentOutOfRangeException{ "Startposition is "+StartPosition:ToString()+". Must be 1 or greater." }
ENDIF

LOCAL RetValue AS System.Text.StringBuilder
RetValue := System.Text.StringBuilder{}

IF StartPosition-1 < 0 // Code, that should never be executed, because it's excluded by parameter checks.
StartPosition := 1 // This should be discovered by a Code Coverage Tool (NCover). This is dead code, but not obvious.
ENDIF

RetValue:Append( InputStr:Substring( 0, StartPosition-1 ) ) // Without the parameter checks we would get generic, means mysterious, error message.

IF ErsatzZeichen != NULL
RetValue:Append( ErsatzZeichen )
ENDIF

IF StartPosition-1+AnzahlZumLöschen < InputStr:Length

RetValue:Append( InputStr:Substring( StartPosition-1+AnzahlZumLöschen ) )

ENDIF

RETURN RetValue:ToString()

END CLASS


// FunctionsTests.prg, contains the tests
#ifdef NUNIT

#using NUnit.Framework
PARTIAL STATIC CLASS Functions

[TestFixture] ;
SEALED CLASS FunctionsTests INHERIT AssertionHelper
[Test] ;
METHOD Private_Asc0( ) AS VOID // Only to show how to access private member of the outer class.
Expect( Functions.Asc0, Is.EqualTo( 48 ) )

[Test] ;
METHOD Stuff_ArgumentNullException_InputString_0( ) AS VOID
Assert.Throws<ArgumentNullException>( TestDelegate{ SELF , @_Stuff_ArgumentNullException_InputString_0() } )

PRIVATE METHOD _Stuff_ArgumentNullException_InputString_0( ) AS VOID
Functions.Stuff(NULL, 2, 0, "xyz")

[Test] ;
METHOD Stuff_ArgumentOutOfRangeException_Startosition_0( ) AS VOID
Assert.Throws<ArgumentOutOfRangeException>( TestDelegate{ SELF , @_Stuff_ArgumentOutOfRangeException_Startosition_0() } )

PRIVATE METHOD _Stuff_ArgumentOutOfRangeException_Startosition_0( ) AS VOID
Functions.Stuff("dummy", 0, 0, NULL ) // Possible through SELF in methoden declaration (extension method).

[Test];
METHOD Stuff( ) AS VOID // All simple tests in one method

Expect( Functions.Stuff("ABCDEF", 2, 0, "xyz") , Is.EqualTo( "AxyzBCDEF" ), "Fall 1" )
Expect( Functions.Stuff("ABCDEF", 2, 3, "xyz") , Is.EqualTo( "AxyzEF" ) , "Fall 2" )
Expect( Functions.Stuff("ABCDEF", 2, 2, NULL) , Is.EqualTo( "ADEF" ) , "Fall 3" )
Expect( Functions.Stuff("ABCDEF", 2, 1, "xyz") , Is.EqualTo( "AxyzCDEF" ) , "Fall 4" )
Expect( Functions.Stuff("ABCDEF", 2, 4, "xyz") , Is.EqualTo( "AxyzF" ) , "Fall 5" )
Expect( Functions.Stuff("ABCDEF", 2, 5, "xyz"), Is.EqualTo( "Axyz" ) , "Fall 6" )
Expect( Functions.Stuff("ABCDEF", 2, 6, "xyz"), Is.EqualTo( "Axyz" ) , "Fall 7" )
Expect( Functions.Stuff("ABCDEF", 2, 7, "xyz"), Is.EqualTo( "Axyz" ) , "Fall 8" )
Expect( Functions.Stuff("ABCDEF", 2, 8, "xyz"), Is.EqualTo( "Axyz" ) , "Fall 9" )
Expect( Functions.Stuff("ABCDEF", 2, 10, "xyz"), Is.EqualTo( "Axyz" ) , "Fall 10" )

Expect( Functions.Stuff("ABCDEF", 1, 0, "xyz") , Is.EqualTo( "xyzABCDEF" ), "Fall 11" )
Expect( Functions.Stuff("ABCDEF", 1, 3, "xyz") , Is.EqualTo( "xyzDEF" ) , "Fall 12" )
Expect( Functions.Stuff("ABCDEF", 1, 2, NULL) , Is.EqualTo( "CDEF" ) , "Fall 13" )
Expect( Functions.Stuff("ABCDEF", 1, 1, "xyz") , Is.EqualTo( "xyzBCDEF" ) , "Fall 14" )
Expect( Functions.Stuff("ABCDEF", 1, 4, "xyz") , Is.EqualTo( "xyzEF" ) , "Fall 15" )
Expect( Functions.Stuff("ABCDEF", 1, 5, "xyz"), Is.EqualTo( "xyzF" ) , "Fall 16" )
Expect( Functions.Stuff("ABCDEF", 1, 6, "xyz"), Is.EqualTo( "xyz" ) , "Fall 17" )
Expect( Functions.Stuff("ABCDEF", 1, 7, "xyz"), Is.EqualTo( "xyz" ) , "Fall 18" )
Expect( Functions.Stuff("ABCDEF", 1, 8, "xyz"), Is.EqualTo( "xyz" ) , "Fall 19" )
Expect( Functions.Stuff("ABCDEF", 1, 10, "xyz"), Is.EqualTo( "xyz" ) , "Fall 20" )
Expect( "ABCDEF":Stuff( 1, 10, "xyz") , Is.EqualTo( "xyz" ) , "Fall 21" )
END CLASS

XIDE, NUNIT and functions.

Posted: Thu Oct 13, 2016 3:50 pm
by robert
Frank,

Nice example code. But what do your coding guidelines say about parameter names with special characters (Umlauts in this case)?

Luckily my German is OK. And I am also glad you're not using Chinese here (but I bet it would work too).

Robert

XIDE, NUNIT and functions.

Posted: Thu Oct 13, 2016 6:12 pm
by Frank Maraite
Robert,

thanks! Good question!

This is code form times before I did sessions. I changed/translated the comments for this post to english. The same with the class name (Funktionen->Functions), but didn't change the parameter names. And I didn't change it in the original code.

Now I know, I should always do it completly in english, even if it is code only for my own use. But you never know: somtimes it may important, to have it in english. The same with comments (which I try to avoid).

In this case I should have used the original names from the VO help file. And organize the test method the same way the help file describes the usuage. But I did want to publish the example ASAP and skiped the refactoring. Maybe I rework end republish it in the next days.

I have to say, I have a messy mix of german and english, even in the same classes. This is something I have to change.

But thanks for bringing this into focus.

Frank

XIDE, NUNIT and functions.

Posted: Fri Oct 14, 2016 9:44 am
by NikolausK
Hello,

Unit testing is a topic I try to get my hands on for some time.

I have difficulties to understand how a unit test for a viewmodel would like like:
1. Most of the methods return void
2. A lot of methods read data from the database and put it into new classes (e.g. DTO Classes)
3. Some methods write to the database by using EF
4. Other methods open a window by using a container (Prism)
5. Reports are printed
6. A lot of properties return existing values by transforming them eg. RGB Codes to Color

I think that I understand the concept of unit testing for functions/methods that have return a specific value after processing the input.

It would be great if there would be a sample or guideline how to unit test.

How would you start unit testing an app with MVVM, Prism, EF?

Thanks

Niko

XIDE, NUNIT and functions.

Posted: Fri Oct 14, 2016 10:15 am
by Frank Maraite
Hi Nikolaus,

the scenario you describe is complex one.

The sense of the test not: do these outside objects what they should do.

What should be tested: are they called correctly.

This means: you need no call the databases directly or so but substitutes for them. Connecting the outside through interfaces is the first step. Then create classes, that have the same interface and connect these to your viewmodel. These classes log only the method call for example. And these logs are subject to test.

HTH a little bit.

The same with the view: create a viewmodel substitute with the same interface of the viewmodel and do something like Console.WriteLine( "ButtonXY clicked" ) in it. Then connect this to the view. You will see this when you click the ButtonXY ... or not.

Frank

PS: look at

http://artofunittesting.com

and especialy

http://artofunittesting.com/storage/cha ... apter3.htm

http://programmers.stackexchange.com/qu ... egacy-code

and of course

http://butunclebob.com/ArticleS.UncleBo ... iplesOfOod

XIDE, NUNIT and functions.

Posted: Wed Nov 09, 2016 12:20 pm
by Phil Hepburn
Hi Frank,

Yes, I would love to get into unit testing with X#.

Can you PLEASE offer a session at Cologne 2017 which is aided at beginners like myself. And make sure Meinhard / Michael does not make your session clash with any of mine.

I know you love using UT and I know it makes good sense - but you need to start slowly for us beginners and hold our hands as we get started on rung one of the testing ladder.

I understand what you say about the debugger, it made me smile ;-0)

If you did the session twice there is less chance of a clash with any I may be asked to do.

Best Regards,
Phil.

XIDE, NUNIT and functions.

Posted: Wed Nov 09, 2016 12:21 pm
by Phil Hepburn
Sorry - for 'aided' read 'aimed'. I wish the target audience to be complete beginners and those using X#. No C# stuff these days ;-0)

Cheers,
Phil.

XIDE, NUNIT and functions.

Posted: Thu Nov 10, 2016 6:24 am
by Frank Maraite
Hi Phil,
thanks for your interest. If Iremember right I did a session at DevShare some years ago.

But you are right, it's an important thing. I would also like to dig deeper into GUI automation for testing it.
What's about refactoring legacy code into more readable and testablw code?

I did not contact Meinhard/Michael so far.

Cheers
Frank
This message was sent from egypt/red sea

XIDE, NUNIT and functions.

Posted: Thu Nov 10, 2016 1:39 pm
by Phil Hepburn
Yes, I also think it is very important for us all to know how to design our code so as to be unit testable from the beginning.

Now that we have X# up and running you could do everything in X# and nothing in C#.

If I could chose I would like you to use Visual Studio as well as XIDE as I need to use VS for the WPF support in the designer etc..

Yes, refactoring old code seems essential for all of us.

If you can get me going then I will try and write some eNotes in 'ClickStart' for X# guys.
Thanks for listening,
Cheers,
Phil.