Hi,

I did not yet find the time to implement test cases for time-based or
concurrent functionalities but it is still on my list for the near
future (next 1-3 weeks). However, I already created interfaces and test
cases for several classes. Since quite a few changes were necessary for
that, I created a merge request for the current state of my code [1], so
that you can let me know if you are fine with it.

Best regards,
Martin

[1] https://github.com/freenet/fred/pull/660

On 4/26/19 1:33 PM, Steve Dougherty wrote:
> Thanks for writing this! Your approaches seem reasonable to me.
>
> Yes, mocking time will be helpful. It’s not  clear to me how many
> places that will be of use, or whether mocking would still be viable
> in integration tests instead of just unit tests, but it’s definitely a
> good tool to have. Do you know if JUnit has anything that can help
> with that? It seems like a common enough need that it might.
>
> Testing concurrent things may require more Conditions for threads to
> wait on. It’s not clear to me how much effort that will require to be
> viable, or how best to expose them.
>
> - Steve
>
> On Fri, Apr 26, 2019 at 6:34 AM, Martin Byrenheid
> <martin.byrenh...@tu-dresden.de
> <mailto:martin.byrenh...@tu-dresden.de>> wrote:
>> Hello everyone,
>>
>> I've sent this mail at the end of last year to this list but it seems
>> that it never made it to its subscribers. Thus, I'm trying again:
>>
>>
>> I started to refactor several parts of the Freenet Code to make the it
>> easier to test and understand the different parts of it in isolation.
>> Before I continue doing so, I would like to know your thoughts on my
>> approach and whether you would actually welcome these changes. You can
>> find the latest version of the partially refactored code on Github [1].
>>
>> Essentially, I did the following three changes to the Node-,
>> NodeCrypto-,PeerManager-,UdpSocketHandler-,NodeIPPortDetector- and
>> IPAddressDetector-class:
>>
>> 1. Create corresponding Interfaces for each class. This allows us to
>> replace actual class instances (e.g. of the Node class) with a
>> test-specific dummy implementation. This dummy can then be given as
>> parameter to a constructor.
>>
>> 2. Encapsulate instantiation of class members within protected methods.
>> By overriding these protected methods, we can replace class members with
>> test-specific dummy implementations.
>>
>> 3. Create getter-Methods to access class members. Many classes in
>> Freenet hold a reference to a Node-instance and some directly access its
>> members. Besides the fact that this is not a good practice, it kept me
>> from creating an interface for the node class.
>>
>> One obstacle with point (1) was that there are several cases where a
>> class calls a non-public method from another class in the same package.
>> To illustrate my changes, consider the following three example classes:
>>
>> package example.A;
>> class MyClass {
>>
>>     boolean getBool() { ... }
>>
>>     public String getString() { ... }   
>>
>> }
>>
>> package example.A;
>> class SamePackageClass {
>>   public SamePackageClass(MyClass ref) {
>>
>>     if (ref.getBool()) ...
>>
>>   }
>>
>> }
>>
>> package example.B;
>> class OtherPackageClass {
>>
>>   public OtherPackageClass(MyClass ref) {
>>
>>     process(ref.getString());
>>
>>   }
>>
>> }
>>
>> Because SamePackageClass is within the same package as MyClass, the
>> getBool-method is visible to the former wheras only getString() is
>> visible for the OtherPackageClass. After my refactoring, we now have the
>> following 2 interfaces:
>>
>> package example.A;
>> public interface MyClass {
>>
>>     String getString();
>>
>> }
>>
>> package example.A;
>> interface ProtectedMyClass extends MyClass {
>>
>>     boolean getBool();
>>
>> }
>>
>> Please note that the first interface is a public interface while the
>> second interface is package-local. The three example classes now look as
>> follows:
>>
>> package example.A;
>> class MyClassImpl implements ProtectedMyClass {
>>
>>     public boolean getBool() { ... }
>>
>>     public String getString() { ... }   
>>
>> }
>>
>> package example.A;
>> class SamePackageClass {
>>   public SamePackageClass(ProtectedMyClass ref) {
>>
>>     if (ref.getBool()) ...
>>
>>   }
>>
>> }
>>
>> package example.B;
>> class OtherPackageClass {
>>
>>   public OtherPackageClass(MyClass ref) {
>>
>>     process(ref.getString());
>>
>>   }
>>
>> }
>>
>> The getBool-Method of the MyClassImpl-class is now public. However, if
>> instances of this class are only accessed via the interfaces it is only
>> visible to classes that use the ProtectedMyClass interface. This way, we
>> maintain the package-local view for the different methods. What do you
>> think about this solution?
>>
>>
>> Given the above classes, an example for a point (2)-refactoring is as
>> follows:
>>
>> class AnotherClass {
>>
>>     MyClass ref;
>>
>>     public AnotherClass(int param) {
>>
>>       ref = newMyClass(param);
>>
>>     }
>>
>>     protected MyClass newMyClass(int param) {
>>
>>         return new MyClassImpl(param);
>>
>>     }
>>
>> }
>>
>> Instead of calling "new MyClassImpl(param)" directly in the constructor,
>> I use a protected method newMyClass. When writing a test case, I can now
>> easily test AnotherClass without needing an instance of MyClassImpl. To
>> do so, I create a minimal class MyClassStub that implements the
>> MyClass-interface and derive the following class:
>>
>> class InstrumentedAnotherClass extends AnotherClass {
>>
>>     public InstrumentedAnotherClass(int param) {
>>       super(param);
>>     }
>>
>>     @Override
>>     protected MyClass newMyClass(int param) {
>>
>>       return new MyClassStub();
>>
>>     }
>>
>> }
>>
>> As a proof of concept for this approach, I created some initial unit
>> tests for the NodeCrypto[2]- and NodeIPPortDetector-class[3].
>>
>> To me, two points that are still open when it comes to tests are:
>>
>> - Testing of time-based conditions: To test functionalities such as
>> rate-limiting of requests, the calls to System.currentTimeMillis()
>> probably need to be replaced by a call to a non-system-interface with a
>> similar method, whose implementation can then be adapted to test cases.
>>
>> - Concurrency: I didn't yet spend much time thinking about how classes
>> can be tested that wait for other threads to operate (e.g. location
>> swapping) but I am optimistic that this can be done.
>>
>>
>> Kind regards,
>> Martin
>>
>>
>> [1] https://github.com/yadevel/fred/commits/testing-refactoring
>>
>> [2]
>> https://github.com/yadevel/fred/blob/testing-refactoring/test/freenet/node/NodeCryptoTest.java
>>
>> [3]
>> https://github.com/yadevel/fred/blob/testing-refactoring/test/freenet/node/NodeIPPortDetectorTest.java
>>
>> --
>> Martin Byrenheid
>> Scientific Assistant
>>
>> Technische Universität Dresden
>> Faculty of Computer Science
>> Institute of System Architecture
>> Chair of Privacy and Data Security
>> 01062 Dresden
>>
>> Tel.: +49 351 463 38235
>> GPG : F144 FBCD F0C2 257A 87ED CFDE 24EB E684 D0BE E785
>>
>>
>
>
-- 
Martin Byrenheid
Scientific Assistant

Technische Universität Dresden
Faculty of Computer Science
Institute of System Architecture
Chair of Privacy and Data Security
01062 Dresden

Tel.: +49 351 463 38235
GPG : F144 FBCD F0C2 257A 87ED 
      CFDE 24EB E684 D0BE E785

Reply via email to