fbpx
× Share your code snippets, screen shots etc. here

XIDE, NUNIT and functions.

More
2 years 3 months ago #1 by Frank Maraite
XIDE, NUNIT and functions. was created 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:
www.codeproject.com/Reference/617/Useful-Reference-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. 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
END CLASS
#endif

We can write test functions the same way for functions, methods, classes of the runtime (Vulcan, X#, third party tools) and look, how they work, even with extrem values.
And, and this is really a good advice, see if something changes, means leads to bugs, after an update of these external programs.

Happy testing
Frank

Please Log in or Create an account to join the conversation.

More
2 years 3 months ago #2 by Robert van der Hulst
Replied by Robert van der Hulst on topic XIDE, NUNIT and functions.
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

XSharp Development Team
The Netherlands
This email address is being protected from spambots. You need JavaScript enabled to view it.

Please Log in or Create an account to join the conversation.

More
2 years 3 months ago #3 by Frank Maraite
Replied by Frank Maraite on topic XIDE, NUNIT and functions.
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

Please Log in or Create an account to join the conversation.

More
2 years 3 months ago #4 by Nikolaus Kern
Replied by Nikolaus Kern on topic XIDE, NUNIT and functions.
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

Please Log in or Create an account to join the conversation.

More
2 years 3 months ago - 2 years 3 months ago #5 by Frank Maraite
Replied by Frank Maraite on topic XIDE, NUNIT and functions.
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

artofunittesting.com

and especialy

artofunittesting.com/storage/chapters/SampleChapter3.htm

programmers.stackexchange.com/questions/...ely-with-legacy-code

and of course

butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Last edit: 2 years 3 months ago by Frank Maraite.

Please Log in or Create an account to join the conversation.

More
2 years 2 months ago #6 by Phil Hepburn
Replied by Phil Hepburn on topic XIDE, NUNIT and functions.
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.

Please Log in or Create an account to join the conversation.

More
2 years 2 months ago #7 by Phil Hepburn
Replied by Phil Hepburn on topic XIDE, NUNIT and functions.
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.

Please Log in or Create an account to join the conversation.

More
2 years 2 months ago #8 by Frank Maraite
Replied by Frank Maraite on topic XIDE, NUNIT and functions.
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

Please Log in or Create an account to join the conversation.

More
2 years 2 months ago #9 by Phil Hepburn
Replied by Phil Hepburn on topic XIDE, NUNIT and functions.
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.

Please Log in or Create an account to join the conversation.