From: Erik V. <eri...@xs...> - 2012-06-08 14:12:22
|
Stefan, Thanks for your informational lecture. Unit testing is an old concept, of which I was well aware, but I have not really used it in practice throughout my professional life, and certainly not in an OO environment. Mocking and stubbing are new to me, but I understand their purpose now. The (superficial) impression I'm getting is that it is easy to test the things that are hard to get wrong, but a lot more difficult to test complex stuff that is easy to get wrong. For instance, how would you test that on a phase change a new train type does become available? Or does such a test already go beyond unit testing? Of course, a good design reduces the existence of such complexities as much as possible. But we all know that Rails has grown in a different way. Hopefully Rails 2.0 will improve that a great deal. Erik. > -----Original Message----- > From: Stefan Frey [mailto:ste...@we...] > Sent: Friday, June 08, 2012 2:56 PM > To: Development list for Rails: an 18xx game > Subject: Re: [Rails-devel] Mocking library chosen > > Brett and Erik: > > Some comments see below: > > * Test location: > Yes as mentioned below in Rails 2.0 there are now two top-level directories: > /src > /junit > > where below src is all of the existing code and below junit everything for > testing will be added. > > The hierarchy in junit will mirror that of src so that the tests are easy to find. > > * Code/Release size: > Code size of Rails (below src) itself is not effected directly, but I hope to > reduce the code by removing code duplication. However this will be easily > offset by new functionality. > > The release size does not change as well as neither the test jars nor the test > source is part of the release package. > > * Some more details for Erik: > > * Unit tests > The main idea behind unit tests is that of "never change a running system". > This is correct observation, however not changing a system is bad too, > because this will easily lead to code duplication. So unit tests allow to "change > a system, but keep it running". > If you have automated tests that run before you changed the systems and > after you changed the system, in fact you kept the system running, but you > are still able to re-factor > > Another nice term I found for unit tests is "necessary waste": > For perfect coders it is a waste of time, but as most of us are not of that kind, > it is necessary. > > Actually unit testing and mocking is not that is not too difficult conceptually. > Someone who got a graphical 1830 moderator working as you did, should > have no problem to understand them. > > Unit testing itself mirrors the tests which are possible in an interpreted > language: > A) Write a function add(x,y) = x+y > B) Call the function with foo(10, 15) in the console > C) Check manually that the result is 25 and that no error was raised > > A unit test is: > A) Write a function add(x,y) = x+y > B) Write a test addTest(): assert(add(x,y),25) > C) Start the test runner which reports passed if add(x,y) == 25 or fails if > add(x,y) != 25 or returns an error. > > If you reverse A and B, thus you write the test before the actual code it is > called TestDrivenDevelopment (TDD) > > * Mocks > Usually you want to test classes as independent entities. And you want to > configure setup the class to test alone. > > However often a method call requires or the construction of a class requires > other objects. > > A mock library allows you to create a "phantom" object that pretends to be a > object of the class, however actually does nothing at all. > > Example: > If you create a Player mock using > Player p = mock(Player.class) > the variable p now can be passed as a player object and all methods of a > player class can be called on p and everything compiles correctly. > But in fact nothing happens if one calls methods on p. > > Or more precise nearly nothing happens: > It is possible to ask the mock later if the methods got called. > > Assume that you test a function that adds a train to a player. It has two tasks: > A) Add the train to the player > B) Change the owner of the train to the player > > Now you can either create fully initialized player and train objects. > > However it might be easier to mock both player and train using Player p = > mock(Player.class) and Train t = mock(Train.class) > > The function is called transferTrain(Player p, Train t). > > You pass both mocks to the function and then check that the function has > called the according methods of player and train: > > verify(player).addTrain(t) > verify(train).setOwner(p) > > Here it is easy to see why a test with fully initialized objects might be a little > stronger: > Then you would check that the train and player really have changed e.g. > by assert(train.getOwner(),p), however this is would be already a joint test > of both the function under test and the train object. > > There is a related technique called stubbing: In that case you not only create > a phantom object, but more a skeleton object. A stub for a Player would be a > minimal but working implementation. > > Mockito supports simple/basic stubbing as well: Assume that function would > only succeed if the addTrain method of player returns a True value. This is > possible by instructing the mock object to return that on a call of addTrain by: > when(p.addTrain(t)).thenReturn(True) > > Feel free to ask more questions if you like. > > Stefan |