Skip to content

Commit ff910c8

Browse files
authored
Merge pull request #87 from moosetechnology:blog-post-testing
Thanks :)
2 parents 1bdd178 + 2e052e2 commit ff910c8

File tree

2 files changed

+463
-0
lines changed

2 files changed

+463
-0
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
---
2+
authors:
3+
- CyrilFerlicot
4+
title: "Testing your algo on a java project"
5+
date: 2025-10-08
6+
lastUpdated: 2025-10-08
7+
tags:
8+
- CI
9+
- infrastructure
10+
---
11+
12+
import ProcessSVG from './img/posts/2025-10-08-testing-your-algo-on-a-java-project/process.drawio.svg';
13+
14+
When developping algorithm on top of the Moose platform, we can easily hurt a wall during testing.
15+
16+
To do functional (and sometimes unit) testing, we need to work on a Moose model. Most of the time we are getting this model in two ways:
17+
18+
- We produce a model and save the `.json` to recreate this model in the tests
19+
- We create a model by hand
20+
21+
But those 2 solutions have drawbacks:
22+
23+
- Keeping a JSON will not follow the evolutions of Famix and the model produce will not be representative of the last version of Famix
24+
- Creating a model by hand has the drawback of taking the risk that this model will not be representative of what we could manipulate in reality. For example, we might not think about setting the stubs or the source anchors
25+
26+
In order to avoid those drawbacks I will describe my way of managing such testing cases in this article. In order to do this, I will explain how I set up the tests of a project to build CallGraph of Java projects.
27+
28+
## The idea
29+
30+
The idea I had for testing callgraphs is to implement real java projects in a `resources` folder in the git of the project. Then, we can parse them when launching the tests and manipulate the produced model. This would ensure that we always have a model up to date with the latest version of Famix. If tests breaks, this means that our famix model evolved and that our project does not work anymore for this language.
31+
32+
<ProcessSVG className="svg-theme" />
33+
34+
## Basic setup
35+
36+
### Create your java code
37+
38+
The first step to build tests is to write some example java code.
39+
40+
I will start with a minimal example:
41+
42+
```java
43+
public class Main {
44+
public static void main(String[] args) {
45+
46+
System.out.println("Hello World!");
47+
}
48+
}
49+
```
50+
51+
I'll save this file in the git repository of my project under `Famix-CallGraph/resources/sources/example1/Main.java`.
52+
53+
Now that we have the source code, we need a way to access it in our project.
54+
55+
### Use GitBridge
56+
57+
In order to access our resources, we will use [GitBrigde](https://github.com/jecisc/GitBridge).
58+
59+
You can install it by executing:
60+
61+
```smalltalk
62+
Metacello new
63+
githubUser: 'jecisc' project: 'GitBridge' commitish: 'v1.x.x' path: 'src';
64+
baseline: 'GitBridge';
65+
load
66+
```
67+
68+
But we should add it to our baseline:
69+
70+
```smalltalk
71+
BaselineOfFamixCallGraph >> #gitBridge: spec
72+
73+
spec baseline: 'GitBridge' with: [ spec repository: 'github://jecisc/GitBridge:v1.x.x/src' ]
74+
```
75+
76+
```smalltalk
77+
BaselineOfFamixCallGraph >> #baseline: spec
78+
79+
<baseline>
80+
spec for: #common do: [
81+
"Dependencies"
82+
self gitBridge: spec.
83+
84+
"Packages"
85+
spec
86+
package: 'Famix-CallGraph';
87+
package: 'Famix-CallGraph-Tests' with: [ spec requires: #( 'Famix-CallGraph' 'GitBridge' ) ]. "<== WE ADD GITBRIDGE HERE!"
88+
].
89+
90+
spec for: #NeedsFamix do: [
91+
self famix: spec.
92+
93+
spec package: 'Famix-CallGraph' with: [ spec requires: #( Famix ) ] ]
94+
```
95+
96+
Now that we have the dependency running, we can use this project. We will explain the minimal steps here but you can find the [full documantation here](https://github.com/jecisc/GitBridge/blob/master/resources/documentation/UserGuide.md).
97+
98+
The usage of GitBridge begins with the definition of our `FamixCallGraphBridge`:
99+
100+
```smalltalk
101+
GitBridge << #FamixCallGraphBridge
102+
slots: {};
103+
package: 'Famix-CallGraph-Tests'
104+
```
105+
106+
Now that this class exists we can access our git folder using `FamixCallGraphBridge current root`.
107+
108+
Let's add some syntactic suggar:
109+
110+
```smalltalk
111+
FamixCallGraphBridge class >> #resources
112+
113+
^ self root / 'resources'
114+
```
115+
```smalltalk
116+
FamixCallGraphBridge class >> #sources
117+
118+
^ self resources / 'sources'
119+
```
120+
121+
We can now access our java projects doing `FamixCallGraphBridge current sources`.
122+
123+
This step is almost done, but in order for our tests to work in a github action (for example), we need two little tweaks.
124+
125+
In our `smalltalk.ston` file, we need to register our project in Iceberg (because GitBridge uses Iceberg to access the root folder).
126+
127+
```diff lang="smalltalk"
128+
SmalltalkCISpec {
129+
#loading : [
130+
SCIMetacelloLoadSpec {
131+
#baseline : 'FamixCallGraph',
132+
#directory : 'src',
133+
+ #registerInIceberg : true "<== This line"
134+
}
135+
]
136+
}
137+
```
138+
139+
Also, in our github action we need to be sure that the checkout action will get enough info for git bridge to run and not the minimal ammount (which is the default) adding a `fetch-depth:` option.
140+
141+
```
142+
steps:
143+
- uses: actions/checkout@v4
144+
with:
145+
fetch-depth: '0'
146+
```
147+
148+
### Parse and import your model
149+
150+
Now we need to be able to parse our project. For this, we will use a Java utility thaht is directly in Moose: `FamixJavaFoldersImporter`.
151+
152+
We can parse and receive a model doing:
153+
154+
```smalltalk
155+
model := (FamixJavaFoldersImporter importFolders: { FamixCallGraphBridge sources / 'example1' }) anyOne.
156+
```
157+
158+
### Tests implementation
159+
160+
Now that we can access the model it is possible to implement our tests.
161+
162+
I'm starting by an abstract class:
163+
164+
```smalltalk
165+
TestCase << #FamixAbstractJavaCallGraphBuilderTestCase
166+
slots: { #model . #graph };
167+
package: 'Famix-CallGraph-Tests'
168+
```
169+
170+
Now I will create a TestCase that needs my java model
171+
172+
```smalltalk
173+
FamixAbstractJavaCallGraphBuilderTestCase << #FamixJavaCHAExample1Test
174+
slots: {};
175+
package: 'Famix-CallGraph-Tests'
176+
```
177+
178+
And now I will create a setup importing the model and creating a call graph:
179+
180+
```smalltalk
181+
FamixAbstractJavaCallGraphBuilderTestCase >> #setUp
182+
183+
super setUp.
184+
model := (FamixJavaFoldersImporter importFolders: { self javaSourcesFolder }) anyOne.
185+
graph := (FamixJavaCHABuilder entryPoints: self entryPoints) build
186+
```
187+
188+
```smalltalk
189+
FamixJavaCHAExample1Test >> #javaSourcesFolder
190+
"Return the java folder containing the sources to parse for those tests"
191+
192+
| folder |
193+
folder := FamixCallGraphBridge sources / 'example1'.
194+
195+
folder ifAbsent: [ self error: 'Folder does not exists ' , folder pathString ].
196+
197+
^ folder
198+
```
199+
200+
And now you have your model available for the testing!
201+
202+
## Optimization
203+
204+
I am using this technic to tests multiple projects such as parsers or call graph builders. In those projects I do touch my model and the setup can take time. So I optimize this setup in order to build a model only once for all the test case using a `TestResource`.
205+
206+
In order to do this we can remove the slots we added to `FamixAbstractJavaCallGraphBuilderTestCase` and create a test resource that will hold them
207+
208+
```smalltalk
209+
TestResource << #FamixAbstractJavaCallGraphBuilderTestResource
210+
slots: { #model . #graph };
211+
package: 'Famix-CallGraph-Tests'
212+
```
213+
214+
Then we can move the setup to this class
215+
216+
```smalltalk
217+
FamixAbstractJavaCallGraphBuilderTestResource >> #setUp
218+
219+
super setUp.
220+
model := (FamixJavaFoldersImporter importFolders: { self javaSourcesFolder }) anyOne.
221+
graph := (FamixJavaCHABuilder entryPoints: self entryPoints) build
222+
```
223+
224+
Personally I'm also adding a tearDown cleaning the vars because TestResources are singletons and I do not want to hold a model in memory all the time.
225+
226+
Then I'm creating my test resource for the `example1` project.
227+
228+
```smalltalk
229+
FamixAbstractJavaCallGraphBuilderTestResource << #FamixJavaCHAExample1Resource
230+
slots: {};
231+
package: 'Famix-CallGraph-Tests'
232+
```
233+
234+
```smalltalk
235+
FamixJavaCHAExample1Resource >> #javaSourcesFolder
236+
"Return the java folder containing the sources to parse for those tests"
237+
238+
| folder |
239+
folder := FamixCallGraphBridge sources / 'example1'.
240+
241+
folder ifAbsent: [ self error: 'Folder does not exists ' , folder pathString ].
242+
243+
^ folder
244+
```
245+
246+
And now we can declare that the TestCase will use this resource:
247+
248+
```smalltalk
249+
FamixJavaCHAExample1Test class >> #resources
250+
^ { FamixJavaCHAExample1Resource }
251+
```
252+
253+
The model then become accessible like this:
254+
255+
```smalltalk
256+
FamixJavaCHAExample1Resource >> #model
257+
258+
^ self resources anyOne current model
259+
```
260+
261+
## Simplify your life
262+
263+
Here is a few tricks I use to simplify even better the setting of my tests cases
264+
265+
### Automatic java source folder detection
266+
267+
The first one is to make automatic the detection of the java source folder by using the name of the test cases:
268+
269+
```smalltalk
270+
FamixAbstractJavaCallGraphBuilderTestResource >> #javaSourcesFolder
271+
^ self class javaSourcesFolder
272+
```
273+
274+
```smalltalk
275+
FamixAbstractJavaCallGraphBuilderTestResource class >> #javaSourcesFolder
276+
"Return the java folder containing the sources to parse for those tests"
277+
278+
| folder |
279+
folder := FamixCallGraphBridge sources / ((self name withoutPrefix: 'FamixJavaCHA') withoutSuffix: 'Resource') uncapitalized.
280+
281+
folder ifAbsent: [ self error: 'Folder does not exists ' , folder pathString ].
282+
283+
^ folder
284+
```
285+
286+
We can now remove this method from all subclasses! But makes sure the name of your source folder matches the name of the tests ressource ;)
287+
288+
### Automatic test resource detection and access
289+
290+
We can do the same with the detection of the test resource in the test case.
291+
292+
```smalltalk
293+
FamixAbstractJavaCallGraphBuilderTestCase class >> #resources
294+
295+
^ self environment
296+
at: ((self name withoutSuffix: 'Test') , 'Resource') asSymbol
297+
ifPresent: [ :class | { class } ]
298+
ifAbsent: [ { } ]
299+
```
300+
301+
```smalltalk
302+
FamixAbstractJavaCallGraphBuilderTestCase class >> #sourceResource
303+
304+
^ self resources anyOne current
305+
```
306+
307+
```smalltalk
308+
FamixAbstractJavaCallGraphBuilderTestCase >> #sourceResource
309+
"I return the instance of the test resource I'm using to build the sources of a java project"
310+
311+
^ self class sourceResource
312+
```
313+
314+
```smalltalk
315+
FamixAbstractJavaCallGraphBuilderTestCase >> #model
316+
317+
^ self sourceResource model
318+
```
319+
320+
Et voila ! Now adding a test case ready to use on a new java project is equivalent to create a test case:
321+
322+
```smalltalk
323+
FamixAbstractJavaCallGraphBuilderTestCase << #FamixJavaCHAExample2Test
324+
slots: {};
325+
package: 'Famix-CallGraph-Tests'
326+
```
327+
328+
And the resource associated!
329+
330+
```smalltalk
331+
FamixAbstractJavaCallGraphBuilderTestResource << #FamixJavaCHAExample2Resource
332+
slots: {};
333+
package: 'Famix-CallGraph-Tests'
334+
```
335+
336+
Nothing much.
337+
338+
### Easily find the sources of the tested project
339+
340+
A last thing I am doing to simplify thing is to implement a method to access easily the sources.
341+
342+
```smalltalk
343+
FamixJavaCHAExample1Test >> #openSources
344+
345+
<script: 'self new openSources'>
346+
self resources anyOne javaSourcesFolder openInOSFileBrowser
347+
```
348+
349+
### Other languages than Java
350+
351+
It is possible to do the same thing for other languages than java but maybe not exactly in the same way than in this blogpost for the section "Parse and import your model". But this article is meant to be an inspiration!
352+
353+
I hope this helps improve the robustness of our projects :)

0 commit comments

Comments
 (0)